diff --git a/.clang-format b/.clang-format index aafa8a0420..e5f6fc2257 100644 --- a/.clang-format +++ b/.clang-format @@ -1,16 +1,18 @@ AccessModifierOffset: -4 -AlignAfterOpenBracket: Align +AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: None AlignConsecutiveDeclarations: None AlignEscapedNewlines: DontAlign AlignOperands: AlignAfterOperator AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: Never +AllowAllArgumentsOnNextLine: false +AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false +AllowShortLambdasOnASingleLine: Empty AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false @@ -33,15 +35,15 @@ BraceWrapping: SplitEmptyNamespace: true SplitEmptyRecord: true BreakAfterJavaFieldAnnotations: true -BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBinaryOperators: All BreakBeforeBraces: Allman BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakStringLiterals: true -ColumnLimit: 240 +ColumnLimit: 180 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false -PackConstructorInitializers: CurrentLine +PackConstructorInitializers: Never ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false @@ -54,13 +56,14 @@ ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH +IncludeBlocks: Regroup IncludeCategories: - Priority: 2 - Regex: ^"(llvm|llvm-c|clang|clang-c)/ + Regex: '^<(scripta|spdlog|range|fmt|Arcus|agrpc|grpc|boost)/.*' - Priority: 3 - Regex: ^(<|"(gtest|gmock|isl|json)/) + Regex: '^((<|")(gtest|gmock|isl|json)/)' - Priority: 1 - Regex: .* + Regex: '^<.*' IncludeIsMainRegex: (Test)?$ IndentCaseLabels: false IndentWidth: 4 @@ -86,11 +89,14 @@ SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 -SpacesInAngles: false +SpacesInAngles: Never SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: c++17 +Standard: c++20 TabWidth: 4 UseTab: Never diff --git a/.clang-tidy b/.clang-tidy index 77b35faafa..e516426d03 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,3 @@ ---- Checks: > *, -llvmlibc-*, @@ -10,21 +9,47 @@ Checks: > -altera-struct-pack-align, -android-*, -misc-non-private-member-variables-in-classes, - -fuchsia-overloaded-operator + -fuchsia-overloaded-operator, + -cppcoreguidelines-avoid-capturing-lambda-coroutines, + -llvm-header-guard, + -bugprone-easily-swappable-parameters WarningsAsErrors: '-*' HeaderFilterRegex: '' FormatStyle: none CheckOptions: - - { key: readability-identifier-naming.NamespaceCase, value: lower_case } - - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } - - { key: readability-identifier-naming.ClassCase, value: CamelCase } - - { key: readability-identifier-naming.FunctionCase, value: camelBack } - - { key: readability-identifier-naming.MethodCase, value: camelBack } - - { key: readability-identifier-naming.ParameterCase, value: lower_case } - - { key: readability-identifier-naming.VariableCase, value: lower_case } - - { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE } - - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } - - { key: readability-identifier-naming.GlobalVariableCase, value: UPPER_CASE } - - { key: readability-identifier-length.IgnoredParameterNames, value: 'i|j|k|x|y|z|a|b|aa|bb|ip|os' } - - { key: readability-identifier-length.IgnoredVariableNames, value: 'i|j|k|x|y|z|a|b|aa|bb|ip|os' } - - { key: readability-identifier-length.IgnoredLoopCounterNames, value: 'i|j|k|x|y|z|a|b|aa|bb|ip' } \ No newline at end of file + - key: google-build-namespaces.HeaderFileExtensions + value: h + - key: readability-function-size.LineThreshold + value: 100 + - key: readability-function-size.BranchThreshold + value: 10 + - key: readability-function-size.ParameterThreshold + value: 6 + - key: readability-function-size.NestingThreshold + value: 4 + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.MethodCase + value: camelBack + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ClassConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.GlobalConstantCase + value: lower_case + - key: readability-identifier-naming.GlobalVariableCase + value: UPPER_CASE + - key: readability-identifier-length.IgnoredParameterNames + value: 'p|p0|p1|i|j|k|x|X|y|Y|z|Z|a|A|b|B|c|C|d|D|ab|AB|ba|BA|bc|BC|cb|CB|cd|CD|dc|DC|ad|AD|da|DA|ip|os' + - key: readability-identifier-length.IgnoredVariableNames + value: '_p|p0|p1|i|j|k|x|X|y|Y|z|Z|a|A|b|B|c|C|d|D|ab|AB|ba|BA|bc|BC|cb|CB|cd|CD|dc|DC|ad|AD|da|DA|ip|os' + - key: readability-identifier-length.IgnoredLoopCounterNames + value: '_p|p0|p1|i|j|k|x|X|y|Y|z|Z|a|A|b|B|c|C|d|D|ab|AB|ba|BA|bc|BC|cb|CB|cd|CD|dc|DC|ad|AD|da|DA|ip|os' \ No newline at end of file diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7f9b8f126b..e3b80ad8ce 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,135 +1,170 @@ name: Benchmark on: - schedule: - - cron: '15 6,12,17 * * *' - - # FIXME: remove the path trigger after merge - push: - paths: - - 'include/**' - - 'src/**' - - 'cmake/**' - - 'benchmark/**' - - 'conanfile.py' - - 'conandata.yml' - - 'CMakeLists.txt' - - '.github/workflows/benchmark.yml' - branches: - - main - - CURA-9906_benchmark - tags: - - '[0-9].[0-9].[0-9]*' + push: + paths: + - 'include/**' + - 'src/**' + - 'benchmark/**' + - '.github/workflows/benchmark.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + tags: + - '[0-9].[0-9].[0-9]*' + pull_request: + types: [ opened, reopened, synchronize ] + paths: + - 'include/**' + - 'src/**' + - 'benchmark/**' + - '.github/workflows/benchmark.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + - 'CURA-*' + - '[0-9]+.[0-9]+' + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write + deployments: write env: - CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }} - CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }} - CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }} - CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }} - CONAN_LOG_RUN_TO_OUTPUT: 1 - CONAN_LOGGING_LEVEL: info - CONAN_NON_INTERACTIVE: 1 + CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }} + CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }} + CONAN_LOG_RUN_TO_OUTPUT: 1 + CONAN_LOGGING_LEVEL: info + CONAN_NON_INTERACTIVE: 1 jobs: - conan-recipe-version: - uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + check_actor: + runs-on: ubuntu-latest + outputs: + proceed: ${{ steps.skip_check.outputs.proceed }} + steps: + - id: skip_check + run: | + if [[ "${{ github.actor }}" == *"[bot]"* ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.pull_request }}" == "" ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.pull_request.head.repo.fork }}" == "false" ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + else + echo "proceed=false" >> $GITHUB_OUTPUT + fi + shell: bash + + conan-recipe-version: + needs: [ check_actor ] + if: ${{ needs.check_actor.outputs.proceed == 'true' }} + uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + with: + project_name: curaengine + + benchmark: + needs: [ conan-recipe-version ] + name: Run C++ benchmark + runs-on: ubuntu-22.04 + steps: + - name: Checkout CuraEngine + uses: actions/checkout@v3 + + - name: Setup Python and pip + uses: actions/setup-python@v4 with: - project_name: curaengine - - benchmark: - needs: [ conan-recipe-version ] - name: Run C++ benchmark - runs-on: ubuntu-22.04 - steps: - - name: Checkout CuraEngine - uses: actions/checkout@v3 - - - name: Setup Python and pip - uses: actions/setup-python@v4 - with: - python-version: '3.11.x' - architecture: 'x64' - cache: 'pip' - cache-dependency-path: .github/workflows/requirements-conan-package.txt - - - name: Cache Benchmark library - uses: actions/cache@v1 - with: - path: ./cache - key: ${{ runner.os }}-googlebenchmark-v1.5.0 - - - name: Install Python requirements and Create default Conan profile - run: | - pip install -r https://raw.githubusercontent.com/Ultimaker/Cura/main/.github/workflows/requirements-conan-package.txt - # Note the runner requirements are always installed from the main branch in the Ultimaker/Cura repo - - - name: Use Conan download cache (Bash) - if: ${{ runner.os != 'Windows' }} - run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - - - name: Cache Conan local repository packages (Bash) - uses: actions/cache@v3 - if: ${{ runner.os != 'Windows' }} - with: - path: | - $HOME/.conan/data - $HOME/.conan/conan_download_cache - key: conan-${{ runner.os }}-${{ runner.arch }} - - # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. - # This is maybe because grub caches the disk it uses last time, which is recreated each time. - - name: Install Linux system requirements - if: ${{ runner.os == 'Linux' }} - run: | - sudo rm /var/cache/debconf/config.dat - sudo dpkg --configure -a - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - sudo apt update - sudo apt upgrade - sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y - - - name: Install GCC-12 on ubuntu-22.04 - run: | - sudo apt install g++-12 gcc-12 -y - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 - - - name: Get Conan configuration - run: | - conan profile new default --detect - conan config install https://github.com/Ultimaker/conan-config.git - - - name: Install dependencies - run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -o enable_benchmarks=True -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv - - - name: Upload the Dependency package(s) - run: conan upload "*" -r cura --all -c - - - name: Set Environment variables from Conan install (bash) - if: ${{ runner.os != 'Windows' }} - run: | - . ./activate_github_actions_runenv.sh - . ./activate_github_actions_buildenv.sh - working-directory: build/generators - - - name: Build CuraEngine and tests - run: | - cmake --preset release - cmake --build --preset release - - - name: Run benchmark CuraEngine - id: run-test - run: ./benchmarks --benchmark_format=json --benchmark_context=version=`${{ needs.conan-recipe-version.outputs.recipe_semver_full }}` | tee benchmark_result.json - working-directory: build/Release/benchmark - - - name: Store benchmark result - uses: benchmark-action/github-action-benchmark@v1 - with: - name: C++ Benchmark - tool: 'googlecpp' - output-file-path: build/Release/benchmark/benchmark_result.json - github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: true - # Show alert with commit comment on detecting possible performance regression - alert-threshold: '200%' - comment-on-alert: true - fail-on-alert: true + python-version: '3.11.x' + architecture: 'x64' + cache: 'pip' + cache-dependency-path: .github/workflows/requirements-conan-package.txt + + - name: Cache Benchmark library + uses: actions/cache@v1 + with: + path: ./cache + key: ${{ runner.os }}-googlebenchmark-v1.5.0 + + - name: Install Python requirements and Create default Conan profile + run: | + pip install -r .github/workflows/requirements-conan-package.txt + + # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. + # This is maybe because grub caches the disk it uses last time, which is recreated each time. + - name: Install Linux system requirements + if: ${{ runner.os == 'Linux' }} + run: | + sudo rm /var/cache/debconf/config.dat + sudo dpkg --configure -a + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt update + sudo apt upgrade + sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y + + - name: Install GCC-132 on ubuntu + run: | + sudo apt install g++-13 gcc-13 -y + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13 + + - name: Create the default Conan profile + run: conan profile new default --detect + + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + + - name: Use Conan download cache (Bash) + if: ${{ runner.os != 'Windows' }} + run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" + + - name: Cache Conan local repository packages (Bash) + uses: actions/cache@v3 + if: ${{ runner.os != 'Windows' }} + with: + path: | + $HOME/.conan/data + $HOME/.conan/conan_download_cache + key: conan-${{ runner.os }}-${{ runner.arch }} + + - name: Install dependencies + run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -o enable_benchmarks=True -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv + + - name: Upload the Dependency package(s) + run: conan upload "*" -r cura --all -c + + - name: Set Environment variables from Conan install (bash) + if: ${{ runner.os != 'Windows' }} + run: | + . ./activate_github_actions_runenv.sh + . ./activate_github_actions_buildenv.sh + working-directory: build/Release/generators + + - name: Build CuraEngine and tests + run: | + cmake --preset release + cmake --build --preset release + + - name: Run benchmark CuraEngine + id: run-test + run: ./benchmarks --benchmark_format=json --benchmark_out=benchmark_result.json + working-directory: build/Release/benchmark + + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: C++ Benchmark + output-file-path: build/Release/benchmark/benchmark_result.json + gh-repository: github.com/Ultimaker/CuraEngineBenchmarks + gh-pages-branch: main + benchmark-data-dir-path: dev/bench + tool: 'googlecpp' + github-token: ${{ secrets.CURA_BENCHMARK_PAT }} + auto-push: true + # alert-threshold: '175%' + # summary-always: true + # comment-on-alert: true + max-items-in-chart: 250 diff --git a/.github/workflows/conan-package.yml b/.github/workflows/conan-package.yml index 922a549aff..749c67ac9d 100644 --- a/.github/workflows/conan-package.yml +++ b/.github/workflows/conan-package.yml @@ -7,151 +7,137 @@ name: conan-package # It should run on pushes against main or CURA-* branches, but it will only create the binaries for main and release branches on: - workflow_dispatch: - inputs: - # FIXME: Not yet implemented - conan_id: - required: false - type: string - description: 'The full conan package ID, e.g. "curaengine/1.2.3@ultimaker/stable"' - create_latest_alias: - required: true - default: false - type: boolean - description: 'Create latest alias' - create_binaries_windows: - required: true - default: false - type: boolean - description: 'create binaries Windows' - create_binaries_linux: - required: true - default: false - type: boolean - description: 'create binaries Linux' - create_binaries_macos: - required: true - default: false - type: boolean - description: 'create binaries Macos' - - push: - paths: - - 'include/**' - - 'src/**' - - 'cmake/**' - - 'tests/**' - - 'test_package/**' - - 'conanfile.py' - - 'conandata.yml' - - 'CMakeLists.txt' - - '.github/workflows/conan-package.yml' - - '.github/worflows/requirements-conan-package.txt' - branches: - - main - - 'CURA-*' - - '[1-9].[0-9]*' - - '[1-9].[0-9][0-9]*' - tags: - - '[1-9]+.[0-9]+.[0-9]*' - - '[1-9]+.[0-9]+.[0-9]' + workflow_dispatch: + inputs: + # FIXME: Not yet implemented + conan_id: + required: false + type: string + description: 'The full conan package ID, e.g. "curaengine/1.2.3@ultimaker/stable"' + create_latest_alias: + required: true + default: false + type: boolean + description: 'Create latest alias' + create_binaries_windows: + required: true + default: false + type: boolean + description: 'create binaries Windows' + create_binaries_linux: + required: true + default: false + type: boolean + description: 'create binaries Linux' + create_binaries_macos: + required: true + default: false + type: boolean + description: 'create binaries Macos' + + push: + paths: + - 'include/**' + - 'src/**' + - 'cmake/**' + - 'tests/**' + - 'test_package/**' + - 'conanfile.py' + - 'conandata.yml' + - 'CMakeLists.txt' + - '.github/workflows/conan-package.yml' + - '.github/worflows/requirements-conan-package.txt' + branches: + - main + - 'CURA-*' + - '[1-9].[0-9]*' + - '[1-9].[0-9][0-9]*' + tags: + - '[1-9]+.[0-9]+.[0-9]*' + - '[1-9]+.[0-9]+.[0-9]' jobs: - conan-recipe-version: - uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main - with: - project_name: curaengine - - conan-package-export: - needs: [ conan-recipe-version ] - uses: ultimaker/cura/.github/workflows/conan-recipe-export.yml@main - with: - recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} - recipe_id_latest: ${{ needs.conan-recipe-version.outputs.recipe_id_latest }} - runs_on: 'ubuntu-22.04' - python_version: '3.11.x' - conan_logging_level: 'info' - secrets: inherit - - conan-package-create-macos: - if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_macos) }} - needs: [ conan-recipe-version, conan-package-export ] - - uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main - with: - project_name: ${{ needs.conan-recipe-version.outputs.project_name }} - recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} - build_id: 3 - runs_on: 'macos-11' - python_version: '3.11.x' - conan_logging_level: 'info' - secrets: inherit - - conan-package-create-windows: - if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true' )) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_windows) }} - needs: [ conan-recipe-version, conan-package-export ] - - uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main - with: - project_name: ${{ needs.conan-recipe-version.outputs.project_name }} - recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} - build_id: 4 - runs_on: 'windows-2022' - python_version: '3.11.x' - conan_config_branch: '' - conan_logging_level: 'info' - secrets: inherit - - conan-package-create-linux: - if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux) }} - needs: [ conan-recipe-version, conan-package-export ] - - uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main - with: - project_name: ${{ needs.conan-recipe-version.outputs.project_name }} - recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} - build_id: 1 - runs_on: 'ubuntu-20.04' - python_version: '3.11.x' - conan_logging_level: 'info' - secrets: inherit - - conan-package-create-linux-modern: - if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux) }} - needs: [ conan-recipe-version, conan-package-export ] - - uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main - with: - project_name: ${{ needs.conan-recipe-version.outputs.project_name }} - recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} - build_id: 2 - runs_on: 'ubuntu-22.04' - python_version: '3.11.x' - conan_logging_level: 'info' - secrets: inherit - - notify-export: - if: ${{ always() }} - needs: [ conan-recipe-version, conan-package-export ] - - uses: ultimaker/cura/.github/workflows/notify.yml@main - with: - success: ${{ contains(join(needs.*.result, ','), 'success') }} - success_title: "New Conan recipe exported in ${{ github.repository }}" - success_body: "Exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" - failure_title: "Failed to export Conan Export in ${{ github.repository }}" - failure_body: "Failed to exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" - secrets: inherit - - notify-create: - if: ${{ always() && ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux)) }} - needs: [ conan-recipe-version, conan-package-create-macos, conan-package-create-windows, conan-package-create-linux, conan-package-create-linux-modern ] - - uses: ultimaker/cura/.github/workflows/notify.yml@main - with: - success: ${{ contains(join(needs.*.result, ','), 'success') }} - success_title: "New binaries created in ${{ github.repository }}" - success_body: "Created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" - failure_title: "Failed to create binaries in ${{ github.repository }}" - failure_body: "Failed to created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" - secrets: inherit + conan-recipe-version: + uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + with: + project_name: curaengine + + conan-package-export: + needs: [ conan-recipe-version ] + uses: ultimaker/cura/.github/workflows/conan-recipe-export.yml@main + with: + recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} + recipe_id_latest: ${{ needs.conan-recipe-version.outputs.recipe_id_latest }} + runs_on: 'ubuntu-22.04' + python_version: '3.11.x' + conan_logging_level: 'info' + secrets: inherit + + conan-package-create-macos: + if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_macos) }} + needs: [ conan-recipe-version, conan-package-export ] + + uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main + with: + project_name: ${{ needs.conan-recipe-version.outputs.project_name }} + recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} + build_id: 3 + runs_on: 'macos-11' + python_version: '3.11.x' + conan_logging_level: 'info' + secrets: inherit + + conan-package-create-windows: + if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true' )) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_windows) }} + needs: [ conan-recipe-version, conan-package-export ] + + uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main + with: + project_name: ${{ needs.conan-recipe-version.outputs.project_name }} + recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} + build_id: 4 + runs_on: 'windows-2022' + python_version: '3.11.x' + conan_config_branch: '' + conan_logging_level: 'info' + secrets: inherit + + conan-package-create-linux: + if: ${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux) }} + needs: [ conan-recipe-version, conan-package-export ] + + uses: ultimaker/cura/.github/workflows/conan-package-create.yml@main + with: + project_name: ${{ needs.conan-recipe-version.outputs.project_name }} + recipe_id_full: ${{ needs.conan-recipe-version.outputs.recipe_id_full }} + build_id: 2 + runs_on: 'ubuntu-22.04' + python_version: '3.11.x' + conan_logging_level: 'info' + secrets: inherit + + notify-export: + if: ${{ always() }} + needs: [ conan-recipe-version, conan-package-export ] + + uses: ultimaker/cura/.github/workflows/notify.yml@main + with: + success: ${{ contains(join(needs.*.result, ','), 'success') }} + success_title: "New Conan recipe exported in ${{ github.repository }}" + success_body: "Exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" + failure_title: "Failed to export Conan Export in ${{ github.repository }}" + failure_body: "Failed to exported ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" + secrets: inherit + + notify-create: + if: ${{ always() && ((github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'master' || needs.conan-recipe-version.outputs.is_release_branch == 'true')) || (github.event_name == 'workflow_dispatch' && inputs.create_binaries_linux)) }} + needs: [ conan-recipe-version, conan-package-create-macos, conan-package-create-windows, conan-package-create-linux ] + + uses: ultimaker/cura/.github/workflows/notify.yml@main + with: + success: ${{ contains(join(needs.*.result, ','), 'success') }} + success_title: "New binaries created in ${{ github.repository }}" + success_body: "Created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" + failure_title: "Failed to create binaries in ${{ github.repository }}" + failure_body: "Failed to created binaries for ${{ needs.conan-recipe-version.outputs.recipe_id_full }}" + secrets: inherit diff --git a/.github/workflows/gcodeanalyzer.yml b/.github/workflows/gcodeanalyzer.yml new file mode 100644 index 0000000000..123aa31d1b --- /dev/null +++ b/.github/workflows/gcodeanalyzer.yml @@ -0,0 +1,312 @@ +name: GcodeAnalyzer +on: + push: + paths: + - 'include/**' + - 'src/**' + - '.github/workflows/gcodeanalyzer.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + pull_request: + types: [ opened, reopened, synchronize ] + paths: + - 'include/**' + - 'src/**' + - '.github/workflows/gcodeanalyzer.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + - 'CURA-*' + - '[0-9]+.[0-9]+' + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write + deployments: write + +env: + CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }} + CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }} + CONAN_LOG_RUN_TO_OUTPUT: 1 + CONAN_LOGGING_LEVEL: info + CONAN_NON_INTERACTIVE: 1 + +jobs: + check_actor: + runs-on: ubuntu-latest + outputs: + proceed: ${{ steps.skip_check.outputs.proceed }} + steps: + - id: skip_check + run: | + if [[ "${{ github.actor }}" == *"[bot]"* ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.pull_request }}" == "" ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.pull_request.head.repo.fork }}" == "false" ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + else + echo "proceed=false" >> $GITHUB_OUTPUT + fi + shell: bash + + conan-recipe-version: + needs: [ check_actor ] + if: ${{ needs.check_actor.outputs.proceed == 'true' }} + uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + with: + project_name: curaengine + + gcodeanalyzer: + needs: [ conan-recipe-version ] + name: Run GCodeAnalyzer on the engine + runs-on: ubuntu-22.04 + steps: + - name: Checkout CuraEngine + uses: actions/checkout@v3 + with: + path: 'CuraEngine' + + - name: Checkout GCodeAnalyzer + uses: actions/checkout@v3 + with: + repository: 'Ultimaker/GCodeAnalyzer' + ref: 'main' + path: 'GCodeAnalyzer' + token: ${{ secrets.CURA_BENCHMARK_PAT }} + + - name: Checkout Test Models + uses: actions/checkout@v3 + with: + repository: 'Ultimaker/NightlyTestModels' + ref: 'main' + path: 'NightlyTestModels' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine the corresponding Cura branch + id: curabranch + run: | + status_code=$(curl -s -o /dev/null -w "%{http_code}" "https://api.github.com/repos/ultimaker/cura/branches/${{ github.head_ref }}") + if [ "$status_code" -eq 200 ]; then + echo "The branch exists in Cura" + echo "branch=${{ github.head_ref }}" >> $GITHUB_OUTPUT + else + echo "branch=main" >> $GITHUB_OUTPUT + fi + + - name: Checkout Cura + uses: actions/checkout@v3 + with: + repository: 'Ultimaker/Cura' + ref: ${{ steps.curabranch.outputs.branch}} + path: 'Cura' + sparse-checkout: | + resources/definitions + resources/extruders + + - name: Setup Python and pip + uses: actions/setup-python@v4 + with: + python-version: '3.10.x' + architecture: 'x64' + cache: 'pip' + cache-dependency-path: CuraEngine/.github/workflows/requirements-conan-package.txt + + - name: Install Python requirements and Create default Conan profile + run: | + pip install -r CuraEngine/.github/workflows/requirements-conan-package.txt + pip install wheel numpy pandas python-dateutil pytz six + pip install git+https://github.com/ultimaker/libcharon@CURA-9495_analyzer_requisites#egg=charon + pip install pytest pytest-benchmark + + # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. + # This is maybe because grub caches the disk it uses last time, which is recreated each time. + - name: Install Linux system requirements + if: ${{ runner.os == 'Linux' }} + run: | + sudo rm /var/cache/debconf/config.dat + sudo dpkg --configure -a + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt update + sudo apt upgrade + sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y + + - name: Install GCC-132 on ubuntu + run: | + sudo apt install g++-13 gcc-13 -y + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13 + + - name: Create the default Conan profile + run: conan profile new default --detect + + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + + - name: Use Conan download cache (Bash) + run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" + + - name: Install dependencies + run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -o enable_benchmarks=False -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv -c tools.build:skip_test=True + working-directory: CuraEngine + + - name: Upload the Dependency package(s) + run: conan upload "*" -r cura --all -c + + - name: Set Environment variables from Conan install (bash) + if: ${{ runner.os != 'Windows' }} + run: | + . ./activate_github_actions_runenv.sh + . ./activate_github_actions_buildenv.sh + working-directory: CuraEngine/build/Release/generators + + - name: Build CuraEngine and tests + run: | + cmake --preset release + cmake --build --preset release + working-directory: CuraEngine + + - name: Collect STL-files, run CuraEngine, output GCode-files + run: | + export CURA_ENGINE_SEARCH_PATH=../Cura/resources/definitions:../Cura/resources/extruders + for file in `ls ../NightlyTestModels/*.stl`; + do + ./build/Release/CuraEngine slice --force-read-parent --force-read-nondefault -v -p -j ../Cura/resources/definitions/ultimaker_s3.def.json -l $file -o ../`basename $file .stl`.gcode + done + working-directory: CuraEngine + + - name: Run GCodeAnalyzer on generated GCode files + id: gcode_out + run: | + import sys + import os + import json + from Charon.filetypes.GCodeFile import GCodeFile + + sys.path.append(".") + import GCodeAnalyzer + GCodeFile.SkipHeaderValidation = True + + folder_path = ".." + jzon = [] + for filename in os.listdir(folder_path): + basename = os.path.basename(filename) + _base, ext = os.path.splitext(basename) + if ext.lower() != ".gcode": + continue + infilename = os.path.join(folder_path, filename) + + frame = GCodeAnalyzer.DataFrame(infilename) + + line_lengths = frame.gc.extrusions['length'].describe() + all_lengths = frame.gc.travels['length'].describe() + extrusion_values = frame.gc.extrusions['E'].describe() + temperatures = frame['T_0_nozzle'].describe() + print_time = frame.time.max() + no_retractions = len(frame.gc.retractions) + total_travel_length = frame.gc.travels.length.sum() + + # microsegments violations + queue = 16 + seg_sec = 80 + violation_threshold = queue / seg_sec + microsegments_wall_skin = frame.gc.extrusions.duration[(frame.type == 'WALL-OUTER') | (frame.type == 'WALL-INNER') | (frame.type == 'SKIN')].rolling(queue).sum() + no_violations_wall_skin = microsegments_wall_skin[microsegments_wall_skin < violation_threshold].count() + + microsegments_infill = frame.gc.extrusions.duration[frame.type == 'FILL'].rolling(queue).sum() + no_violations_infill = microsegments_infill[microsegments_infill < violation_threshold].count() + + jzon += [ + { + "name": f"Print time {basename}", + "unit": "s", + "value": print_time, + }, + { + "name": f"Microsegment violations in wall-skin {basename}", + "unit": "-", + "value": int(no_violations_wall_skin), + }, + { + "name": f"Microsegment violations in infill {basename}", + "unit": "-", + "value": int(no_violations_infill), + }, + { + "name": f"Number of retractions {basename}", + "unit": "-", + "value": no_retractions, + }, + { + "name": f"Total travel length {basename}", + "unit": "mm", + "value": total_travel_length, + }, + { + "name": f"Minimum Line Length {basename}", + "unit": "mm", + "value": line_lengths["min"], + }, + { + "name": f"Line Lengths 25 Percentile {basename}", + "unit": "mm", + "value": line_lengths["25%"], + }, + { + "name": f"Minimum All Distances {basename}", + "unit": "mm", + "value": all_lengths["min"], + }, + { + "name": f"All Distances 25 Percentile {basename}", + "unit": "mm", + "value": all_lengths["25%"], + }, + { + "name": f"Extrusion-Axis {basename}", + "unit": "mm", + "value": extrusion_values["min"], + }, + { + "name": f"Extrusion Lengths 25 Percentile {basename}", + "unit": "mm", + "value": extrusion_values["25%"], + }, + { + "name": f"Number Of Temperature Commands {basename}", + "unit": "#", + "value": temperatures["count"], + }, + { + "name": f"Mean Temperature {basename}", + "unit": "Celcius", + "value": temperatures["50%"], + }, + ] + + with open("../output.json", "w") as outfile: + outfile.write(json.dumps(jzon)) + shell: python + working-directory: GCodeAnalyzer + + - name: Store benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: CGcodeAnalyzer + output-file-path: output.json + gh-repository: github.com/Ultimaker/CuraEngineBenchmarks + gh-pages-branch: main + benchmark-data-dir-path: dev/gcodeanalyzer + tool: customBiggerIsBetter + github-token: ${{ secrets.CURA_BENCHMARK_PAT }} + auto-push: true + # alert-threshold: '110%' + # summary-always: true + # comment-on-alert: true + max-items-in-chart: 250 diff --git a/.github/workflows/lint-formatter.yml b/.github/workflows/lint-formatter.yml new file mode 100644 index 0000000000..9ae18563b5 --- /dev/null +++ b/.github/workflows/lint-formatter.yml @@ -0,0 +1,46 @@ +name: lint-formatter + +on: + workflow_dispatch: + + push: + paths: + - 'include/**/*.h*' + - 'src/**/*.c*' + +jobs: + lint-formatter-job: + name: Auto-apply clang-format + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: technote-space/get-diff-action@v6 + with: + PATTERNS: | + include/**/*.h* + src/**/*.c* + + - name: Setup Python and pip + if: env.GIT_DIFF && !env.MATCHED_FILES # If nothing happens with python and/or pip after, the clean-up crashes. + uses: actions/setup-python@v4 + with: + python-version: 3.11.x + cache: 'pip' + cache-dependency-path: .github/workflows/requirements-linter.txt + + - name: Install Python requirements for runner + if: env.GIT_DIFF && !env.MATCHED_FILES + run: pip install -r .github/workflows/requirements-linter.txt + + - name: Format file + if: env.GIT_DIFF && !env.MATCHED_FILES + run: | + clang-format -i ${{ env.GIT_DIFF_FILTERED }} + + - uses: stefanzweifel/git-auto-commit-action@v4 + if: env.GIT_DIFF && !env.MATCHED_FILES + with: + commit_message: "Applied clang-format." diff --git a/.github/workflows/lint-poster.yml b/.github/workflows/lint-poster.yml new file mode 100644 index 0000000000..a016599fec --- /dev/null +++ b/.github/workflows/lint-poster.yml @@ -0,0 +1,81 @@ +name: lint-poster + +on: + workflow_run: + workflows: [ "lint-tidier" ] + types: [ completed ] + +jobs: + lint-poster-job: + # Trigger the job only if the previous (insecure) workflow completed successfully + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Download analysis results + uses: actions/github-script@v3.1.0 + with: + script: | + let artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + let matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "linter-result" + })[0]; + let download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: "zip", + }); + let fs = require("fs"); + fs.writeFileSync("${{github.workspace}}/linter-result.zip", Buffer.from(download.data)); + + - name: Set environment variables + run: | + mkdir linter-result + unzip linter-result.zip -d linter-result + echo "pr_id=$(cat linter-result/pr-id.txt)" >> $GITHUB_ENV + echo "pr_head_repo=$(cat linter-result/pr-head-repo.txt)" >> $GITHUB_ENV + echo "pr_head_ref=$(cat linter-result/pr-head-ref.txt)" >> $GITHUB_ENV + + - uses: actions/checkout@v3 + with: + repository: ${{ env.pr_head_repo }} + ref: ${{ env.pr_head_ref }} + persist-credentials: false + + - name: Redownload analysis results + uses: actions/github-script@v3.1.0 + with: + script: | + let artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + let matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "linter-result" + })[0]; + let download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: "zip", + }); + let fs = require("fs"); + fs.writeFileSync("${{github.workspace}}/linter-result.zip", Buffer.from(download.data)); + + - name: Extract analysis results + run: | + mkdir linter-result + unzip linter-result.zip -d linter-result + + - name: Run clang-tidy-pr-comments action + uses: platisd/clang-tidy-pr-comments + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + clang_tidy_fixes: linter-result/fixes.yml + pull_request_id: ${{ env.pr_id }} + request_changes: true diff --git a/.github/workflows/lint-tidier.yml b/.github/workflows/lint-tidier.yml new file mode 100644 index 0000000000..d178f52f3a --- /dev/null +++ b/.github/workflows/lint-tidier.yml @@ -0,0 +1,107 @@ +name: lint-tidier + +on: + workflow_dispatch: + + pull_request: + paths: + - 'include/**/*.h*' + - 'src/**/*.c*' + +jobs: + lint-tidier-job: + name: Auto-apply clang-tidy + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - uses: technote-space/get-diff-action@v6 + with: + PATTERNS: | + include/**/*.h* + src/**/*.c* + + - name: Setup Python and pip + if: env.GIT_DIFF && !env.MATCHED_FILES # If nothing happens with python and/or pip after, the clean-up crashes. + uses: actions/setup-python@v4 + with: + python-version: 3.11.x + cache: "pip" + cache-dependency-path: .github/workflows/requirements-linter.txt + + - name: Install Python requirements for runner + if: env.GIT_DIFF && !env.MATCHED_FILES + run: pip install -r .github/workflows/requirements-linter.txt + + # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. + # This is maybe because grub caches the disk it uses last time, which is recreated each time. + - name: Install Linux system requirements + if: ${{ runner.os == 'Linux' }} + run: | + sudo rm /var/cache/debconf/config.dat + sudo dpkg --configure -a + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt update + sudo apt upgrade + sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y + + - name: Install GCC-132 on ubuntu + run: | + sudo apt install g++-13 gcc-13 -y + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13 + + - name: Create the default Conan profile + run: conan profile new default --detect + + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + + - name: Install dependencies + run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -o enable_testing=True -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv + + - name: Set Environment variables from Conan install (bash) + if: ${{ runner.os != 'Windows' }} + run: | + . ./activate_github_actions_runenv.sh + . ./activate_github_actions_buildenv.sh + working-directory: build/Release/generators + + - name: Build CuraEngine and tests + run: | + cmake --preset release + cmake --build --preset release + + - name: Create results directory + run: mkdir linter-result + + - name: Diagnose file(s) + if: env.GIT_DIFF && !env.MATCHED_FILES + continue-on-error: true + run: | + clang-tidy -p ./build/Release/ --config-file=.clang-tidy ${{ env.GIT_DIFF_FILTERED }} --export-fixes=linter-result/fixes.yml + + - name: Save PR metadata + run: | + echo ${{ github.event.number }} > linter-result/pr-id.txt + echo ${{ github.event.pull_request.head.repo.full_name }} > linter-result/pr-head-repo.txt + echo ${{ github.event.pull_request.head.ref }} > linter-result/pr-head-ref.txt + + - uses: actions/upload-artifact@v2 + with: + name: linter-result + path: linter-result/ + + - name: Run clang-tidy-pr-comments action + uses: platisd/clang-tidy-pr-comments@1.4.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + clang_tidy_fixes: linter-result/fixes.yml + request_changes: true + suggestions_per_comment: 30 diff --git a/.github/workflows/process-pull-request.yml b/.github/workflows/process-pull-request.yml index 56fb015b9d..f4a5921214 100644 --- a/.github/workflows/process-pull-request.yml +++ b/.github/workflows/process-pull-request.yml @@ -1,15 +1,15 @@ name: process-pull-request on: - pull_request_target: - types: [opened, reopened, edited, synchronize, review_requested, ready_for_review, assigned] + pull_request_target: + types: [ opened, reopened, edited, synchronize, review_requested, ready_for_review, assigned ] jobs: - add_label: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-ecosystem/action-add-labels@v1 - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} - with: - labels: 'PR: Community Contribution :crown:' + add_label: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-ecosystem/action-add-labels@v1 + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + with: + labels: 'PR: Community Contribution :crown:' diff --git a/.github/workflows/requirements-conan-package.txt b/.github/workflows/requirements-conan-package.txt index 0471cc9eac..42669ee348 100644 --- a/.github/workflows/requirements-conan-package.txt +++ b/.github/workflows/requirements-conan-package.txt @@ -1 +1 @@ -conan==1.56.0 +conan>=1.60.2,<2.0.0 diff --git a/.github/workflows/requirements-linter.txt b/.github/workflows/requirements-linter.txt new file mode 100644 index 0000000000..a2fa4b7dd2 --- /dev/null +++ b/.github/workflows/requirements-linter.txt @@ -0,0 +1,2 @@ +pyyaml +conan>=1.60.2,<2.0.0 \ No newline at end of file diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 40f69c3384..04e325df79 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -1,148 +1,145 @@ --- name: unit-test -# FIXME: This should be a reusable workflow - on: - push: - paths: - - 'include/**' - - 'src/**' - - 'cmake/**' - - 'tests/**' - - 'test_package/**' - - 'conanfile.py' - - 'conandata.yml' - - 'CMakeLists.txt' - - '.github/workflows/unit-test.yml' - - '.github/worflows/requirements-conan-package.txt' - branches: - - main - - 'CURA-*' - - '[0-9]+.[0-9]+' - tags: - - '[0-9]+.[0-9]+.[0-9]+' - pull_request: - types: [opened, reopened, synchronize] - paths: - - 'include/**' - - 'src/**' - - 'cmake/**' - - 'tests/**' - - 'test_package/**' - - 'conanfile.py' - - 'conandata.yml' - - 'CMakeLists.txt' - - '.github/workflows/unit-test.yml' - - '.github/worflows/requirements-conan-package.txt' - branches: - - main - - 'CURA-*' - - '[0-9]+.[0-9]+' - tags: - - '[0-9]+.[0-9]+.[0-9]+' + push: + paths: + - 'include/**' + - 'src/**' + - 'cmake/**' + - 'tests/**' + - 'test_package/**' + - 'conanfile.py' + - 'CMakeLists.txt' + - '.github/workflows/unit-test.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + - '[0-9]+.[0-9]+' + tags: + - '[0-9]+.[0-9]+.[0-9]+' + pull_request: + types: [ opened, reopened, synchronize ] + paths: + - 'include/**' + - 'src/**' + - 'cmake/**' + - 'tests/**' + - 'test_package/**' + - 'conanfile.py' + - 'CMakeLists.txt' + - '.github/workflows/unit-test.yml' + - '.github/workflows/requirements-conan-package.txt' + branches: + - main + - 'CURA-*' + - '[0-9]+.[0-9]+' + tags: + - '[0-9]+.[0-9]+.[0-9]+' env: - CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }} - CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }} - CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }} - CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }} - CONAN_LOG_RUN_TO_OUTPUT: 1 - CONAN_LOGGING_LEVEL: info - CONAN_NON_INTERACTIVE: 1 + CONAN_LOGIN_USERNAME_CURA: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA: ${{ secrets.CONAN_PASS }} + CONAN_LOGIN_USERNAME_CURA_CE: ${{ secrets.CONAN_USER }} + CONAN_PASSWORD_CURA_CE: ${{ secrets.CONAN_PASS }} + CONAN_LOG_RUN_TO_OUTPUT: 1 + CONAN_LOGGING_LEVEL: info + CONAN_NON_INTERACTIVE: 1 jobs: - conan-recipe-version: - uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + conan-recipe-version: + uses: ultimaker/cura/.github/workflows/conan-recipe-version.yml@main + with: + project_name: curaengine + + testing: + runs-on: ubuntu-22.04 + needs: [ conan-recipe-version ] + + steps: + - name: Checkout CuraEngine + uses: actions/checkout@v3 + + - name: Setup Python and pip + uses: actions/setup-python@v4 with: - project_name: curaengine - - testing: - runs-on: ubuntu-22.04 - needs: [ conan-recipe-version ] - - steps: - - name: Checkout CuraEngine - uses: actions/checkout@v3 - - - name: Setup Python and pip - uses: actions/setup-python@v4 - with: - python-version: '3.11.x' - architecture: 'x64' - cache: 'pip' - cache-dependency-path: .github/workflows/requirements-conan-package.txt - - - name: Install Python requirements and Create default Conan profile - run: | - pip install -r https://raw.githubusercontent.com/Ultimaker/Cura/main/.github/workflows/requirements-conan-package.txt - # Note the runner requirements are always installed from the main branch in the Ultimaker/Cura repo - - - name: Use Conan download cache (Bash) - if: ${{ runner.os != 'Windows' }} - run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" - - - name: Cache Conan local repository packages (Bash) - uses: actions/cache@v3 - if: ${{ runner.os != 'Windows' }} - with: - path: | - $HOME/.conan/data - $HOME/.conan/conan_download_cache - key: conan-${{ runner.os }}-${{ runner.arch }} - - # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. - # This is maybe because grub caches the disk it uses last time, which is recreated each time. - - name: Install Linux system requirements - if: ${{ runner.os == 'Linux' }} - run: | - sudo rm /var/cache/debconf/config.dat - sudo dpkg --configure -a - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y - sudo apt update - sudo apt upgrade - sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y - - - name: Install GCC-12 on ubuntu-22.04 - run: | - sudo apt install g++-12 gcc-12 -y - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 - - - name: Get Conan configuration - run: | - conan profile new default --detect - conan config install https://github.com/Ultimaker/conan-config.git - - - name: Install dependencies - run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -o enable_testing=True -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv - - - name: Upload the Dependency package(s) - run: conan upload "*" -r cura --all -c - - - name: Set Environment variables from Conan install (bash) - if: ${{ runner.os != 'Windows' }} - run: | - . ./activate_github_actions_runenv.sh - . ./activate_github_actions_buildenv.sh - working-directory: build/generators - - - name: Build CuraEngine and tests - run: | - cmake --preset release - cmake --build --preset release - - - name: Run Unit Test CuraEngine - id: run-test - run: ctest --output-junit engine_test.xml - working-directory: build/Release - - - name: Publish Unit Test Results - id: test-results - uses: EnricoMi/publish-unit-test-result-action@v1 - if: ${{ always() }} - with: - files: | - **/*.xml - - - name: Conclusion - run: echo "Conclusion is ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}" + python-version: '3.10.x' + architecture: 'x64' + cache: 'pip' + cache-dependency-path: .github/workflows/requirements-conan-package.txt + + - name: Install Python requirements and Create default Conan profile + run: | + pip install -r .github/workflows/requirements-conan-package.txt + + # NOTE: Due to what are probably github issues, we have to remove the cache and reconfigure before the rest. + # This is maybe because grub caches the disk it uses last time, which is recreated each time. + - name: Install Linux system requirements + if: ${{ runner.os == 'Linux' }} + run: | + sudo rm /var/cache/debconf/config.dat + sudo dpkg --configure -a + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt update + sudo apt upgrade + sudo apt install build-essential checkinstall libegl-dev zlib1g-dev libssl-dev ninja-build autoconf libx11-dev libx11-xcb-dev libfontenc-dev libice-dev libsm-dev libxau-dev libxaw7-dev libxcomposite-dev libxcursor-dev libxdamage-dev libxdmcp-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libxkbfile-dev libxmu-dev libxmuu-dev libxpm-dev libxrandr-dev libxrender-dev libxres-dev libxss-dev libxt-dev libxtst-dev libxv-dev libxvmc-dev libxxf86vm-dev xtrans-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-xkb-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xinerama0-dev xkb-data libxcb-dri3-dev uuid-dev libxcb-util-dev libxkbcommon-x11-dev pkg-config -y + + - name: Install GCC-132 on ubuntu + run: | + sudo apt install g++-13 gcc-13 -y + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13 + + - name: Create the default Conan profile + run: conan profile new default --detect + + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + + - name: Use Conan download cache (Bash) + if: ${{ runner.os != 'Windows' }} + run: conan config set storage.download_cache="$HOME/.conan/conan_download_cache" + + - name: Cache Conan local repository packages (Bash) + uses: actions/cache@v3 + if: ${{ runner.os != 'Windows' }} + with: + path: | + $HOME/.conan/data + $HOME/.conan/conan_download_cache + key: conan-${{ runner.os }}-${{ runner.arch }} + + - name: Install dependencies + run: conan install . ${{ needs.conan-recipe-version.outputs.recipe_id_full }} -s build_type=Release --build=missing --update -g GitHubActionsRunEnv -g GitHubActionsBuildEnv + + - name: Upload the Dependency package(s) + run: conan upload "*" -r cura --all -c + + - name: Set Environment variables from Conan install (bash) + if: ${{ runner.os != 'Windows' }} + run: | + . ./activate_github_actions_runenv.sh + . ./activate_github_actions_buildenv.sh + working-directory: build/Release/generators + + - name: Build CuraEngine and tests + run: | + cmake --preset release + cmake --build --preset release + + - name: Run Unit Test CuraEngine + id: run-test + run: ctest --output-junit engine_test.xml + working-directory: build/Release + + - name: Publish Unit Test Results + id: test-results + uses: EnricoMi/publish-unit-test-result-action@v1 + if: ${{ always() }} + with: + files: | + **/*.xml + + - name: Conclusion + run: echo "Conclusion is ${{ fromJSON( steps.test-results.outputs.json ).conclusion }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 43096caa11..caaf3f45dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,15 +10,27 @@ AssureOutOfSourceBuilds() option(ENABLE_ARCUS "Enable support for ARCUS" ON) option(ENABLE_TESTING "Build with unit tests" OFF) option(EXTENSIVE_WARNINGS "Build with all warnings" ON) +option(ENABLE_PLUGINS "Build with plugins" ON) +option(ENABLE_REMOTE_PLUGINS "Build with all warnings" OFF) option(ENABLE_MORE_COMPILER_OPTIMIZATION_FLAGS "Enable more optimization flags" ON) option(USE_SYSTEM_LIBS "Use the system libraries if available" OFF) +option(OLDER_APPLE_CLANG "Apple Clang <= 13 used" OFF) + +# Generate the plugin types +find_package(protobuf REQUIRED) +find_package(asio-grpc REQUIRED) +find_package(gRPC REQUIRED) +find_package(curaengine_grpc_definitions REQUIRED) +option(OLDER_APPLE_CLANG "Apple Clang <= 13 used" OFF) + +MESSAGE(STATUS "Compiling with plugins support: ${ENABLE_PLUGINS}") +if (${ENABLE_PLUGINS}) + MESSAGE(STATUS "Plugin secure remotes allowed: ${ENABLE_REMOTE_PLUGINS}") +endif () -# Create Protobuf files if Arcus is used if (ENABLE_ARCUS) message(STATUS "Building with Arcus") - find_package(arcus REQUIRED) - find_package(protobuf REQUIRED) protobuf_generate_cpp(engine_PB_SRCS engine_PB_HEADERS Cura.proto) endif () @@ -29,6 +41,7 @@ set(engine_SRCS # Except main.cpp. src/Application.cpp src/bridge.cpp src/ConicalOverhang.cpp + src/ExtruderPlan.cpp src/ExtruderTrain.cpp src/FffGcodeWriter.cpp src/FffPolygonGenerator.cpp @@ -96,19 +109,23 @@ set(engine_SRCS # Except main.cpp. src/pathPlanning/GCodePath.cpp src/pathPlanning/LinePolygonsCrossings.cpp src/pathPlanning/NozzleTempInsert.cpp - src/pathPlanning/TimeMaterialEstimates.cpp + src/pathPlanning/SpeedDerivatives.cpp + + src/plugins/converters.cpp src/progress/Progress.cpp src/progress/ProgressStageEstimator.cpp src/settings/AdaptiveLayerHeights.cpp src/settings/FlowTempGraph.cpp + src/settings/MeshPathConfigs.cpp src/settings/PathConfigStorage.cpp src/settings/Settings.cpp src/settings/ZSeamConfig.cpp src/utils/AABB.cpp src/utils/AABB3D.cpp + src/utils/channel.cpp src/utils/Date.cpp src/utils/ExtrusionJunction.cpp src/utils/ExtrusionLine.cpp @@ -146,9 +163,13 @@ target_include_directories(_CuraEngine PRIVATE $ # Include Cura.pb.h ) + target_compile_definitions(_CuraEngine PUBLIC $<$:ARCUS> + $<$:ENABLE_PLUGINS> + $<$,$>:ENABLE_REMOTE_PLUGINS> + $<$:OLDER_APPLE_CLANG> CURA_ENGINE_VERSION=\"${CURA_ENGINE_VERSION}\" $<$:BUILD_TESTS> PRIVATE @@ -168,7 +189,7 @@ if (${EXTENSIVE_WARNINGS}) endif () if (ENABLE_ARCUS) - target_link_libraries(_CuraEngine PRIVATE arcus::arcus protobuf::libprotobuf) + target_link_libraries(_CuraEngine PUBLIC arcus::arcus ) endif () find_package(clipper REQUIRED) @@ -179,6 +200,7 @@ find_package(spdlog REQUIRED) find_package(fmt REQUIRED) find_package(range-v3 REQUIRED) find_package(scripta REQUIRED) +find_package(neargye-semver REQUIRED) if (ENABLE_TESTING) find_package(GTest REQUIRED) @@ -194,6 +216,11 @@ target_link_libraries(_CuraEngine stb::stb boost::boost scripta::scripta + neargye-semver::neargye-semver + curaengine_grpc_definitions::curaengine_grpc_definitions + asio-grpc::asio-grpc + grpc::grpc + protobuf::libprotobuf $<$:GTest::gtest>) if (NOT WIN32) @@ -211,11 +238,39 @@ else () add_executable(CuraEngine src/main.cpp ${RES_FILES}) # ..., but don't forget the glitter! endif (NOT WIN32) -# Create the executable +use_threads(CuraEngine) target_link_libraries(CuraEngine PRIVATE _CuraEngine) target_compile_definitions(CuraEngine PRIVATE VERSION=\"${CURA_ENGINE_VERSION}\") # Compiling the test environment. +if (ENABLE_TESTING OR ENABLE_BENCHMARKS) + set(TESTS_HELPERS_SRC tests/ReadTestPolygons.cpp) + + set(TESTS_SRC_ARCUS) + if (ENABLE_ARCUS) + list(APPEND TESTS_SRC_ARCUS + ArcusCommunicationTest + ArcusCommunicationPrivateTest) + list(APPEND TESTS_HELPERS_SRC tests/arcus/MockSocket.cpp) + endif () + + add_library(test_helpers ${TESTS_HELPERS_SRC}) + target_compile_definitions(test_helpers PUBLIC $<$:BUILD_TESTS> $<$:ARCUS>) + target_include_directories(test_helpers PUBLIC "include" ${CMAKE_BINARY_DIR}/generated) + target_link_libraries(test_helpers PRIVATE + _CuraEngine + GTest::gtest + GTest::gmock + clipper::clipper + curaengine_grpc_definitions::curaengine_grpc_definitions + asio-grpc::asio-grpc + grpc::grpc + protobuf::libprotobuf) + if (ENABLE_ARCUS) + target_link_libraries(test_helpers PUBLIC arcus::arcus) + endif () +endif () + if (ENABLE_TESTING) enable_testing() add_subdirectory(tests) @@ -223,4 +278,4 @@ endif () if (ENABLE_BENCHMARKS) add_subdirectory(benchmark) -endif() \ No newline at end of file +endif () \ No newline at end of file diff --git a/Cura.proto b/Cura.proto index 284b4eb9d5..960396c5a8 100644 --- a/Cura.proto +++ b/Cura.proto @@ -8,12 +8,35 @@ message ObjectList repeated Setting settings = 2; // meshgroup settings (for one-at-a-time printing) } +// Resevered IDs: +// 0 ... 99: Broadcasts +// 100 ... 199: Modify +// 200 ... 299: Generate +enum SlotID { + SETTINGS_BROADCAST = 0; + SIMPLIFY_MODIFY = 100; + POSTPROCESS_MODIFY = 101; + INFILL_MODIFY = 102; + GCODE_PATHS_MODIFY = 103; + INFILL_GENERATE = 200; +} + +message EnginePlugin +{ + SlotID id = 1; + string address = 2; + uint32 port = 3; + string plugin_name = 4; + string plugin_version = 5; +} + message Slice { repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another SettingList global_settings = 2; // The global settings used for the whole print job repeated Extruder extruders = 3; // The settings sent to each extruder object repeated SettingExtruder limit_to_extruder = 4; // From which stack the setting would inherit if not defined per object + repeated EnginePlugin engine_plugins = 5; } message Extruder diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000000..2d108a74e1 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: [ultimaker] diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 289b634dd7..4cb27361e2 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -5,6 +5,7 @@ message(STATUS "Building benchmarks...") find_package(benchmark REQUIRED) + add_executable(benchmarks main.cpp) -target_link_libraries(benchmarks PRIVATE _CuraEngine benchmark::benchmark) -target_include_directories(benchmarks PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file +target_link_libraries(benchmarks PRIVATE _CuraEngine benchmark::benchmark test_helpers) +target_include_directories(benchmarks PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/generated) \ No newline at end of file diff --git a/benchmark/main.cpp b/benchmark/main.cpp index 6d519d5de7..cdea597f35 100644 --- a/benchmark/main.cpp +++ b/benchmark/main.cpp @@ -1,8 +1,9 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher #include "infill_benchmark.h" #include "wall_benchmark.h" +#include "simplify_benchmark.h" #include // Run the benchmark diff --git a/benchmark/simplify_benchmark.h b/benchmark/simplify_benchmark.h new file mode 100644 index 0000000000..d3726bce65 --- /dev/null +++ b/benchmark/simplify_benchmark.h @@ -0,0 +1,76 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_BENCHMARK_SIMPLIFY_BENCHMARK_H +#define CURAENGINE_BENCHMARK_SIMPLIFY_BENCHMARK_H + +#include "../tests/ReadTestPolygons.h" +#include "plugins/slots.h" +#include "utils/Simplify.h" +#include "utils/channel.h" + +#include + +#include +#include +#include + +namespace cura +{ +class SimplifyTestFixture : public benchmark::Fixture +{ +public: + const std::vector POLYGON_FILENAMES = { std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_concave.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_concave_hole.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_square.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_square_hole.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_triangle.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/polygon_two_squares.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/slice_polygon_1.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/slice_polygon_2.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/slice_polygon_3.txt").string(), + std::filesystem::path(__FILE__).parent_path().append("tests/resources/slice_polygon_4.txt").string() }; + + std::vector shapes; + + void SetUp(const ::benchmark::State& state) + { + readTestPolygons(POLYGON_FILENAMES, shapes); + } + + void TearDown(const ::benchmark::State& state) + { + } +}; + +BENCHMARK_DEFINE_F(SimplifyTestFixture, simplify_local)(benchmark::State& st) +{ + Simplify simplify(MM2INT(0.25), MM2INT(0.025), 50000); + for (auto _ : st) + { + Polygons simplified; + for (const auto& polys : shapes) + { + benchmark::DoNotOptimize(simplified = simplify.polygon(polys)); + } + } +} + +BENCHMARK_REGISTER_F(SimplifyTestFixture, simplify_local); + +BENCHMARK_DEFINE_F(SimplifyTestFixture, simplify_slot_noplugin)(benchmark::State& st) +{ + for (auto _ : st) + { + Polygons simplified; + for (const auto& polys : shapes) + { + benchmark::DoNotOptimize(simplified = slots::instance().modify(polys, MM2INT(0.25), MM2INT(0.025), 50000)); + } + } +} + +BENCHMARK_REGISTER_F(SimplifyTestFixture, simplify_slot_noplugin); + +} // namespace cura +#endif // CURAENGINE_BENCHMARK_SIMPLIFY_BENCHMARK_H diff --git a/benchmark/wall_benchmark.h b/benchmark/wall_benchmark.h index 2af20fc862..314377c911 100644 --- a/benchmark/wall_benchmark.h +++ b/benchmark/wall_benchmark.h @@ -58,6 +58,7 @@ class WallTestFixture : public benchmark::Fixture settings.add("meshfix_maximum_deviation", "0.1"); settings.add("meshfix_maximum_extrusion_area_deviation", "0.01"); settings.add("meshfix_maximum_resolution", "0.01"); + settings.add("meshfix_fluid_motion_enabled", "false"); settings.add("min_wall_line_width", "0.3"); settings.add("min_bead_width", "0"); settings.add("min_feature_size", "0"); diff --git a/conanfile.py b/conanfile.py index db8f50331e..b3e464843a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -5,12 +5,12 @@ from conan import ConanFile from conan.errors import ConanInvalidConfiguration -from conan.tools.files import copy +from conan.tools.files import copy, mkdir from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake, cmake_layout from conan.tools.build import check_min_cppstd from conan.tools.scm import Version -required_conan_version = ">=1.56.0" +required_conan_version = ">=1.58.0 <2.0.0" class CuraEngineConan(ConanFile): @@ -25,17 +25,23 @@ class CuraEngineConan(ConanFile): options = { "enable_arcus": [True, False], - "enable_testing": [True, False], "enable_benchmarks": [True, False], - "enable_extensive_warnings": [True, False] + "enable_extensive_warnings": [True, False], + "enable_plugins": [True, False], + "enable_remote_plugins": [True, False], } default_options = { "enable_arcus": True, - "enable_testing": False, "enable_benchmarks": False, "enable_extensive_warnings": False, + "enable_plugins": True, + "enable_remote_plugins": False, } + def set_version(self): + if not self.version: + self.version = "5.6.0-beta.1" + def export_sources(self): copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder) copy(self, "Cura.proto", self.recipe_folder, self.export_sources_folder) @@ -47,12 +53,17 @@ def export_sources(self): copy(self, "*", path.join(self.recipe_folder, "benchmark"), path.join(self.export_sources_folder, "benchmark")) copy(self, "*", path.join(self.recipe_folder, "tests"), path.join(self.export_sources_folder, "tests")) + def config_options(self): + if not self.options.enable_plugins: + del self.options.enable_remote_plugins + def configure(self): self.options["boost"].header_only = True self.options["clipper"].shared = True + + self.options["protobuf"].shared = False if self.options.enable_arcus: self.options["arcus"].shared = True - self.options["protobuf"].shared = True def validate(self): if self.settings.compiler.get_safe("cppstd"): @@ -63,25 +74,30 @@ def validate(self): def build_requirements(self): self.test_requires("standardprojectsettings/[>=0.1.0]@ultimaker/stable") - if self.options.enable_arcus: - self.test_requires("protobuf/3.21.4") - if self.options.enable_testing: + self.test_requires("protobuf/3.21.9") + if not self.conf.get("tools.build:skip_test", False, check_type=bool): self.test_requires("gtest/1.12.1") if self.options.enable_benchmarks: self.test_requires("benchmark/1.7.0") def requirements(self): if self.options.enable_arcus: - self.requires("arcus/5.2.2") - self.requires("zlib/1.2.12") + self.requires("arcus/5.3.0") + self.requires("asio-grpc/2.6.0") + self.requires("grpc/1.50.1") + self.requires("curaengine_grpc_definitions/(latest)@ultimaker/testing") self.requires("clipper/6.4.2") - self.requires("boost/1.79.0") + self.requires("boost/1.82.0") self.requires("rapidjson/1.1.0") self.requires("stb/20200203") self.requires("spdlog/1.10.0") self.requires("fmt/9.0.0") self.requires("range-v3/0.12.0") self.requires("scripta/0.1.0@ultimaker/testing") + self.requires("neargye-semver/0.3.0") + self.requires("protobuf/3.21.9") + self.requires("zlib/1.2.12") + self.requires("openssl/1.1.1l") def generate(self): deps = CMakeDeps(self) @@ -90,14 +106,36 @@ def generate(self): tc = CMakeToolchain(self) tc.variables["CURA_ENGINE_VERSION"] = self.version tc.variables["ENABLE_ARCUS"] = self.options.enable_arcus - tc.variables["ENABLE_TESTING"] = self.options.enable_testing + tc.variables["ENABLE_TESTING"] = not self.conf.get("tools.build:skip_test", False, check_type=bool) tc.variables["ENABLE_BENCHMARKS"] = self.options.enable_benchmarks tc.variables["EXTENSIVE_WARNINGS"] = self.options.enable_extensive_warnings + tc.variables["OLDER_APPLE_CLANG"] = self.settings.compiler == "apple-clang" and Version(self.settings.compiler.version) < "14" + if self.options.enable_plugins: + tc.variables["ENABLE_PLUGINS"] = True + tc.variables["ENABLE_REMOTE_PLUGINS"] = self.options.enable_remote_plugins + else: + tc.variables["ENABLE_PLUGINS"] = self.options.enable_plugins tc.generate() + for dep in self.dependencies.values(): + if len(dep.cpp_info.libdirs) > 0: + copy(self, "*.dylib", dep.cpp_info.libdirs[0], self.build_folder) + copy(self, "*.dll", dep.cpp_info.libdirs[0], self.build_folder) + if len(dep.cpp_info.bindirs) > 0: + copy(self, "*.dll", dep.cpp_info.bindirs[0], self.build_folder) + if not self.conf.get("tools.build:skip_test", False, check_type=bool): + test_path = path.join(self.build_folder, "tests") + if not path.exists(test_path): + mkdir(self, test_path) + if len(dep.cpp_info.libdirs) > 0: + copy(self, "*.dylib", dep.cpp_info.libdirs[0], path.join(self.build_folder, "tests")) + copy(self, "*.dll", dep.cpp_info.libdirs[0], path.join(self.build_folder, "tests")) + if len(dep.cpp_info.bindirs) > 0: + copy(self, "*.dll", dep.cpp_info.bindirs[0], path.join(self.build_folder, "tests")) + + def layout(self): cmake_layout(self) - self.cpp.build.includedirs = ["."] # To package the generated headers self.cpp.package.libs = ["_CuraEngine"] diff --git a/include/Application.h b/include/Application.h index 3d781f702a..4a3f793daf 100644 --- a/include/Application.h +++ b/include/Application.h @@ -1,12 +1,15 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef APPLICATION_H #define APPLICATION_H #include "utils/NoCopy.h" -#include //For size_t. + #include +#include +#include + namespace cura { @@ -14,6 +17,8 @@ class Communication; class Slice; class ThreadPool; +struct PluginSetupConfiguration; + /*! * A singleton class that serves as the starting point for all slicing. * @@ -85,7 +90,9 @@ class Application : NoCopy * * \param nworkers The number of workers (including the main thread) that are ran. */ - void startThreadPool(int nworkers=0); + void startThreadPool(int nworkers = 0); + + std::string instance_uuid; protected: #ifdef ARCUS @@ -95,7 +102,7 @@ class Application : NoCopy * \param argv The arguments provided to the application. */ void connect(); -#endif //ARCUS +#endif // ARCUS /*! * \brief Print the header and license to the stderr channel. @@ -134,8 +141,10 @@ class Application : NoCopy * This destroys the Communication instance along with it. */ ~Application(); + + void registerPlugins(const PluginSetupConfiguration& plugins_config); }; -} //Cura namespace. +} // namespace cura -#endif //APPLICATION_H \ No newline at end of file +#endif // APPLICATION_H \ No newline at end of file diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h new file mode 100644 index 0000000000..6f3155148e --- /dev/null +++ b/include/ExtruderPlan.h @@ -0,0 +1,212 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef EXTRUDERPLAN_H +#define EXTRUDERPLAN_H + +#include "FanSpeedLayerTime.h" +#include "RetractionConfig.h" +#include "gcodeExport.h" +#include "pathPlanning/GCodePath.h" +#include "pathPlanning/NozzleTempInsert.h" +#include "pathPlanning/TimeMaterialEstimates.h" +#include "settings/types/LayerIndex.h" +#include "settings/types/Ratio.h" +#include "utils/IntPoint.h" + +#ifdef BUILD_TESTS +#include //Friend tests, so that they can inspect the privates. +#endif + +#include +#include +#include +#include + +namespace cura +{ +class LayerPlanBuffer; +class LayerPlan; +/*! + * An extruder plan contains all planned paths (GCodePath) pertaining to a single extruder train. + * + * It allows for temperature command inserts which can be inserted in between paths. + */ +class ExtruderPlan +{ + friend class LayerPlanBuffer; + friend class LayerPlan; +#ifdef BUILD_TESTS + friend class ExtruderPlanPathsParameterizedTest; + FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationZeroIsUncompensated); + FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull); + FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf); + FRIEND_TEST(ExtruderPlanTest, BackPressureCompensationEmptyPlan); +#endif +public: + size_t extruder_nr{ 0 }; //!< The extruder used for this paths in the current plan. + + ExtruderPlan() noexcept = default; + + /*! + * Simple contructor. + * + * \warning Doesn't set the required temperature yet. + * + * \param extruder The extruder number for which this object is a plan. + * \param layer_nr The layer index of the layer that this extruder plan is + * part of. + * \param is_raft_layer Whether this extruder plan is part of a raft layer. + */ + ExtruderPlan( + const size_t extruder, + const LayerIndex layer_nr, + const bool is_initial_layer, + const bool is_raft_layer, + const coord_t layer_thickness, + const FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, + const RetractionConfig& retraction_config); + + + void insertCommand(NozzleTempInsert&& insert); + + /*! + * Insert the inserts into gcode which should be inserted before \p path_idx + * + * \param path_idx The index into ExtruderPlan::paths which is currently being consider for temperature command insertion + * \param gcode The gcode exporter to which to write the temperature command. + * \param cumulative_path_time The time spend on this path up to this point. + */ + void handleInserts(const size_t path_idx, GCodeExport& gcode, const double cumulative_path_time = std::numeric_limits::infinity()); + + /*! + * Insert all remaining temp inserts into gcode, to be called at the end of an extruder plan + * + * Inserts temperature commands which should be inserted _after_ the last path. + * Also inserts all temperatures which should have been inserted earlier, + * but for which ExtruderPlan::handleInserts hasn't been called correctly. + * + * \param gcode The gcode exporter to which to write the temperature command. + */ + void handleAllRemainingInserts(GCodeExport& gcode); + + /*! + * Applying fan speed changes for minimal layer times. + * + * \param starting_position The position the head was before starting this extruder plan + * \param minTime Maximum minimum layer time for all extruders in this layer + * \param time_other_extr_plans The time spent on the other extruder plans in this layer + */ + void processFanSpeedForMinimalLayerTime(Point starting_position, Duration maximum_cool_min_layer_time, double time_other_extr_plans); + + /*! + * Applying fan speed changes for the first layers. + */ + void processFanSpeedForFirstLayers(); + + /*! + * Get the fan speed computed for this extruder plan + * + * \warning assumes ExtruderPlan::processFanSpeedForMinimalLayerTime has already been called + * + * \return The fan speed computed in processFanSpeedForMinimalLayerTime + */ + double getFanSpeed(); + + /*! + * Apply back-pressure compensation to this path. + * Since the total (filament) pressure in a feeder-system is not only dependent on the pressure that exists between the nozzle and the + * feed-mechanism (which should be near-constant on a bowden style setup), but _also_ between the nozzle and the last-printed layer. + * This last type is called 'back-pressure'. In this function, properties of the path-outflow are adjusted so that the back-pressure is + * compensated for. This is conjectured to be especially important if the printer has a Bowden-tube style setup. + * + * \param The amount of back-pressure compensation as a ratio. 'Applying' a value of 0 is a no-op. + */ + void applyBackPressureCompensation(const Ratio back_pressure_compensation); + +private: + LayerIndex layer_nr{ 0 }; //!< The layer number at which we are currently printing. + bool is_initial_layer{ false }; //!< Whether this extruder plan is printed on the very first layer (which might be raft) + bool is_raft_layer{ false }; //!< Whether this is a layer which is part of the raft + + coord_t layer_thickness{ 200 }; //!< The thickness of this layer in Z-direction + + FanSpeedLayerTimeSettings fan_speed_layer_time_settings{}; //!< The fan speed and layer time settings used to limit this extruder plan + + RetractionConfig retraction_config{}; //!< The retraction settings for the extruder of this plan + + + std::vector paths; //!< The paths planned for this extruder + std::list inserts; //!< The nozzle temperature command inserts, to be inserted in between segments + double heated_pre_travel_time{ 0.0 }; //!< The time at the start of this ExtruderPlan during which the head travels and has a temperature of initial_print_temperature + + /*! + * The required temperature at the start of this extruder plan + * or the temp to which to heat gradually over the layer change between this plan and the previous with the same extruder. + * + * In case this extruder plan uses a different extruder than the last extruder plan: + * this is the temperature to which to heat and wait before starting this extruder. + * + * In case this extruder plan uses the same extruder as the previous extruder plan (previous layer): + * this is the temperature used to heat to gradually when moving from the previous extruder layer to the next. + * In that case no temperature (and wait) command will be inserted from this value, but a NozzleTempInsert is used instead. + * In this case this member is only used as a way to convey information between different calls of \ref LayerPlanBuffer::processBuffer + */ + double required_start_temperature{ -1.0 }; + std::optional extrusion_temperature{ std::nullopt }; //!< The normal temperature for printing this extruder plan. That start and end of this extruder plan may deviate + //!< because of the initial and final print temp (none if extruder plan has no extrusion moves) + std::optional::iterator> extrusion_temperature_command{ + std::nullopt + }; //!< The command to heat from the printing temperature of this extruder plan to the printing + //!< temperature of the next extruder plan (if it has the same extruder). + std::optional prev_extruder_standby_temp{ + std::nullopt + }; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder. + + TimeMaterialEstimates estimates{}; //!< Accumulated time and material estimates for all planned paths within this extruder plan. + double slowest_path_speed{ 0.0 }; + + double extraTime{ 0.0 }; //!< Extra waiting time at the and of this extruder plan, so that the filament can cool + + double fan_speed{ 0.0 }; //!< The fan speed to be used during this extruder plan + + double temperatureFactor{ 0.0 }; //!< Temperature reduction factor for small layers + + /*! + * Set the fan speed to be used while printing this extruder plan + * + * \param fan_speed The speed for the fan + */ + void setFanSpeed(double fan_speed); + + /*! + * Force the minimal layer time to hold by slowing down and lifting the head if required. + * + * \param maximum_cool_min_layer_time Maximum minimum layer time for all extruders in this layer + * \param time_other_extr_plans Time spend on other extruders in this layer + */ + void forceMinimalLayerTime(double maximum_cool_min_layer_time, double time_other_extr_plans); + + /*! + * @return The time needed for (un)retract the path + */ + double getRetractTime(const GCodePath& path); + + /*! + * @return distance between p0 and p1 as well as the time spend on the segment + */ + std::pair getPointToPointTime(const Point& p0, const Point& p1, const GCodePath& path); + + /*! + * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. + * and store them in each ExtruderPlan and each GCodePath. + * + * \param starting_position The position the head was in before starting this layer + * \return the total estimates of this layer + */ + TimeMaterialEstimates computeNaiveTimeEstimates(Point starting_position); +}; + +} // namespace cura + +#endif // EXTRUDERPLAN_H diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 014e29f059..d63547692e 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -1,5 +1,5 @@ // Copyright (c) 2023 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher. +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef GCODE_WRITER_H #define GCODE_WRITER_H @@ -8,13 +8,15 @@ #include #include "FanSpeedLayerTime.h" -#include "gcodeExport.h" #include "LayerPlanBuffer.h" +#include "gcodeExport.h" +#include "settings/MeshPathConfigs.h" #include "settings/PathConfigStorage.h" //For the MeshPathConfigs subclass. #include "utils/ExtrusionLine.h" //Processing variable-width paths. #include "utils/NoCopy.h" +#include "utils/gettime.h" -namespace cura +namespace cura { class AngleDegrees; @@ -24,32 +26,31 @@ class SliceDataStorage; class SliceMeshStorage; class SliceLayer; class SliceLayerPart; -class TimeKeeper; /*! * Secondary stage in Fused Filament Fabrication processing: The generated polygons are used in the gcode generation. - * Some polygons in the SliceDataStorage signify areas which are to be filled with parallel lines, + * Some polygons in the SliceDataStorage signify areas which are to be filled with parallel lines, * while other polygons signify the contours which should be printed. - * + * * The main function of this class is FffGcodeWriter::writeGCode(). */ class FffGcodeWriter : public NoCopy { - friend class FffProcessor; //Because FffProcessor exposes finalize (TODO) + friend class FffProcessor; // Because FffProcessor exposes finalize (TODO) private: coord_t max_object_height; //!< The maximal height of all previously sliced meshgroups, used to avoid collision when moving to the next meshgroup to print. /* * Buffer for all layer plans (of type LayerPlan) - * + * * The layer plans are buffered so that we can start heating up a nozzle several layers before it needs to be used. * Another reason is to perform Auto Temperature. */ - LayerPlanBuffer layer_plan_buffer; + LayerPlanBuffer layer_plan_buffer; /*! * The class holding the current state of the gcode being written. - * + * * It holds information such as the last written position etc. */ GCodeExport gcode; @@ -104,18 +105,18 @@ class FffGcodeWriter : public NoCopy /*! * Set the target to write gcode to: an output stream. - * + * * Used when CuraEngine is NOT used as command line tool. - * + * * \param stream The stream to write gcode to. */ void setTargetStream(std::ostream* stream); /*! * Get the total extruded volume for a specific extruder in mm^3 - * + * * Retractions and unretractions don't contribute to this. - * + * * \param extruder_nr The extruder number for which to get the total netto extruded volume * \return total filament printed in mm^3 */ @@ -123,7 +124,7 @@ class FffGcodeWriter : public NoCopy /*! * Get the total estimated print time in seconds for each feature - * + * * \return total print time in seconds for each feature */ std::vector getTotalPrintTimePerFeature(); @@ -131,13 +132,20 @@ class FffGcodeWriter : public NoCopy /*! * Write all the gcode for the current meshgroup. * This is the primary function of this class. - * + * * \param[in] storage The data storage from which to get the polygons to print and the areas to fill. * \param timeKeeper The stop watch to see how long it takes for each of the stages in the slicing process. */ void writeGCode(SliceDataStorage& storage, TimeKeeper& timeKeeper); private: + struct ProcessLayerResult + { + LayerPlan* layer_plan; + double total_elapsed_time; + TimeKeeper::RegisteredTimes stages_times; + }; + /*! * \brief Set the FffGcodeWriter::fan_speed_layer_time_settings by * retrieving all settings from the global/per-meshgroup settings. @@ -146,28 +154,28 @@ class FffGcodeWriter : public NoCopy /*! * Set the retraction and wipe config globally, per extruder and per mesh. - * + * * \param[out] storage The data storage to which to save the configurations */ void setConfigRetractionAndWipe(SliceDataStorage& storage); /*! * Get the extruder with which to start the print. - * + * * Generally this is the extruder of the adhesion type in use, but in case * the platform adhesion type is none, the support extruder is used. If * support is also disabled, the extruder with lowest number which is used * on the first layer is used as initial extruder. - * + * * \param[in] storage where to get settings from. */ size_t getStartExtruder(const SliceDataStorage& storage); /*! * Set the infill angles and skin angles in the SliceDataStorage. - * + * * These lists of angles are cycled through to get the infill angle of a specific layer. - * + * * \param mesh The mesh for which to determine the infill and skin angles. */ void setInfillAndSkinAngles(SliceMeshStorage& mesh); @@ -186,30 +194,30 @@ class FffGcodeWriter : public NoCopy /*! * Move up and over the already printed meshgroups to print the next meshgroup. - * + * * \param[in] storage where the slice data is stored. */ void processNextMeshGroupCode(const SliceDataStorage& storage); - + /*! * Add raft layer plans onto the FffGcodeWriter::layer_plan_buffer - * + * * \param[in,out] storage where the slice data is stored. */ void processRaft(const SliceDataStorage& storage); /*! * Convert the polygon data of a layer into a layer plan on the FffGcodeWriter::layer_plan_buffer - * + * * In case of negative layer numbers, create layers only containing the data from * the helper parts (support etc) to fill up the gap between the raft and the model. - * + * * \param[in] storage where the slice data is stored. * \param layer_nr The index of the layer to write the gcode of. * \param total_layers The total number of layers. * \return The layer plans */ - LayerPlan& processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const; + ProcessLayerResult processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const; /*! * This function checks whether prime blob should happen for any extruder on the first layer. @@ -217,17 +225,17 @@ class FffGcodeWriter : public NoCopy * * Technically, this function checks whether any extruder needs to be primed (with a prime blob) * separately just before they are used. - * + * * \return whether any extruder need to be primed separately just before they are used */ bool getExtruderNeedPrimeBlobDuringFirstLayer(const SliceDataStorage& storage, const size_t extruder_nr) const; /*! * Add the skirt or the brim to the layer plan \p gcodeLayer if it hasn't already been added yet. - * + * * This function should be called for only one layer; * calling it for multiple layers results in the skirt/brim being printed on multiple layers. - * + * * \param storage where the slice data is stored. * \param gcodeLayer The initial planning of the g-code of the layer. * \param extruder_nr The extruder train for which to process the skirt or @@ -238,15 +246,15 @@ class FffGcodeWriter : public NoCopy /*! * Adds the ooze shield to the layer plan \p gcodeLayer. - * + * * \param[in] storage where the slice data is stored. * \param gcodeLayer The initial planning of the gcode of the layer. */ void processOozeShield(const SliceDataStorage& storage, LayerPlan& gcodeLayer) const; - + /*! * Adds the draft protection screen to the layer plan \p gcodeLayer. - * + * * \param[in] storage where the slice data is stored. * \param gcodeLayer The initial planning of the gcode of the layer. */ @@ -256,14 +264,14 @@ class FffGcodeWriter : public NoCopy * Calculate in which order to plan the extruders for each layer * Store the order of extruders for each layer in extruder_order_per_layer for normal layers * and the order of extruders for raft/filler layers in extruder_order_per_layer_negative_layers. - * + * * Only extruders which are (most probably) going to be used are planned - * + * * \note At the planning stage we only have information on areas, not how those are filled. * If an area is too small to be filled with anything it will still get specified as being used with the extruder for that area. - * + * * Computes \ref FffGcodeWriter::extruder_order_per_layer and \ref FffGcodeWriter::extruder_order_per_layer_negative_layers - * + * * \param[in] storage where the slice data is stored. */ void calculateExtruderOrderPerLayer(const SliceDataStorage& storage); @@ -280,10 +288,10 @@ class FffGcodeWriter : public NoCopy /*! * Gets a list of extruders that are used on the given layer, but excluding the given starting extruder. * When it's on the first layer, the prime blob will also be taken into account. - * + * * \note At the planning stage we only have information on areas, not how those are filled. * If an area is too small to be filled with anything it will still get specified as being used with the extruder for that area. - * + * * \param[in] storage where the slice data is stored. * \param current_extruder The current extruder with which we last printed * \return The order of extruders for a layer beginning with \p current_extruder @@ -294,7 +302,7 @@ class FffGcodeWriter : public NoCopy * Calculate in which order to plan the meshes of a specific extruder * Each mesh which has some feature printed with the extruder is included in this order. * One mesh can occur in the mesh order of multiple extruders. - * + * * \param[in] storage where the slice data is stored. * \param extruder_nr The extruder for which to determine the order * \return A vector of mesh indices ordered on print order for that extruder. @@ -303,41 +311,46 @@ class FffGcodeWriter : public NoCopy /*! * Add a single layer from a single mesh-volume to the layer plan \p gcodeLayer in mesh surface mode. - * + * * \param[in] storage where the slice data is stored. * \param mesh The mesh to add to the layer plan \p gcodeLayer. * \param mesh_config the line config with which to print a print feature * \param gcodeLayer The initial planning of the gcode of the layer. */ - void addMeshLayerToGCode_meshSurfaceMode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcodeLayer) const; - + void addMeshLayerToGCode_meshSurfaceMode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, LayerPlan& gcodeLayer) const; + /*! * Add the open polylines from a single layer from a single mesh-volume to the layer plan \p gcodeLayer for mesh the surface modes. - * + * * \param[in] storage where the slice data is stored. * \param mesh The mesh for which to add to the layer plan \p gcodeLayer. * \param mesh_config the line config with which to print a print feature * \param gcodeLayer The initial planning of the gcode of the layer. */ - void addMeshOpenPolyLinesToGCode(const SliceMeshStorage& mesh, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const; - + void addMeshOpenPolyLinesToGCode(const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const; + /*! * Add all features of a given extruder from a single layer from a single mesh-volume to the layer plan \p gcode_layer. - * + * * This adds all features (e.g. walls, skin etc.) of this \p mesh to the gcode which are printed using \p extruder_nr - * + * * \param[in] storage where the slice data is stored. - * \param mesh The mesh to add to the layer plan \p gcode_layer. + * \param mesh_ptr The mesh to add to the layer plan \p gcode_layer. * \param extruder_nr The extruder for which to print all features of the mesh which should be printed with this extruder * \param mesh_config the line config with which to print a print feature * \param gcode_layer The initial planning of the gcode of the layer. */ - void addMeshLayerToGCode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const; + void addMeshLayerToGCode( + const SliceDataStorage& storage, + const std::shared_ptr& mesh_ptr, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + LayerPlan& gcode_layer) const; /*! * Add all features of the given extruder from a single part from a given layer of a mesh-volume to the layer plan \p gcode_layer. * This only adds the features which are printed with \p extruder_nr. - * + * * \param[in] storage where the slice data is stored. * \param storage Storage to get global settings from. * \param mesh The mesh to add to the layer plan \p gcode_layer. @@ -346,7 +359,13 @@ class FffGcodeWriter : public NoCopy * \param part The part to add * \param gcode_layer The initial planning of the gcode of the layer. */ - void addMeshPartToGCode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part, LayerPlan& gcode_layer) const; + void addMeshPartToGCode( + const SliceDataStorage& storage, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part, + LayerPlan& gcode_layer) const; /*! * \brief Add infill for a given part in a layer plan. @@ -359,12 +378,18 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode. * \return Whether this function added anything to the layer plan. */ - bool processInfill(const SliceDataStorage& storage, LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; + bool processInfill( + const SliceDataStorage& storage, + LayerPlan& gcodeLayer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const; /*! * \brief Add thicker (multiple layers) sparse infill for a given part in a * layer plan. - * + * * \param gcodeLayer The initial planning of the gcode of the layer. * \param mesh The mesh for which to add to the layer plan \p gcodeLayer. * \param extruder_nr The extruder for which to print all features of the @@ -373,7 +398,13 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode. * \return Whether this function added anything to the layer plan. */ - bool processMultiLayerInfill(const SliceDataStorage& storage, LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; + bool processMultiLayerInfill( + const SliceDataStorage& storage, + LayerPlan& gcodeLayer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const; /*! * \brief Add normal sparse infill for a given part in a layer. @@ -385,7 +416,13 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode. * \return Whether this function added anything to the layer plan. */ - bool processSingleLayerInfill(const SliceDataStorage& storage, LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; + bool processSingleLayerInfill( + const SliceDataStorage& storage, + LayerPlan& gcodeLayer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const; /*! * Generate the insets for the walls of a given layer part. @@ -397,7 +434,13 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode * \return Whether this function added anything to the layer plan */ - bool processInsets(const SliceDataStorage& storage, LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; + bool processInsets( + const SliceDataStorage& storage, + LayerPlan& gcodeLayer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const; /*! * Generate the a spiralized wall for a given layer part. @@ -407,7 +450,9 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode * \param mesh The mesh for which to add to the layer plan \p gcodeLayer. */ - void processSpiralizedWall(const SliceDataStorage& storage, LayerPlan& gcode_layer, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part, const SliceMeshStorage& mesh) const; + void + processSpiralizedWall(const SliceDataStorage& storage, LayerPlan& gcode_layer, const MeshPathConfigs& mesh_config, const SliceLayerPart& part, const SliceMeshStorage& mesh) + const; /*! * Add the gcode of the top/bottom skin of the given part and of the perimeter gaps. @@ -420,21 +465,27 @@ class FffGcodeWriter : public NoCopy * \param part The part for which to create gcode * \return Whether this function added anything to the layer plan */ - bool processSkin(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; + bool processSkin( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const; /*! * Add the gcode of the top/bottom skin of the given skin part and of the perimeter gaps. - * + * * Perimeter gaps are handled for the current extruder for the following features if they are printed with this extruder. * - skin outlines * - roofing (if concentric) * - top/bottom (if concentric) * They are all printed at the end of printing the skin part features which are printed with this extruder. - * + * * Note that the normal perimeter gaps are printed with the outer wall extruder, * while newly generated perimeter gaps * are printed with the extruder with which the feature was printed which generated the gaps. - * + * * \param[in] storage where the slice data is stored. * \param gcode_layer The initial planning of the gcode of the layer. * \param mesh The mesh for which to add to the layer plan \p gcode_layer. @@ -443,7 +494,13 @@ class FffGcodeWriter : public NoCopy * \param skin_part The skin part for which to create gcode * \return Whether this function added anything to the layer plan */ - bool processSkinPart(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SkinPart& skin_part) const; + bool processSkinPart( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part) const; /*! * Add the roofing which is the area inside the innermost skin inset which has air 'directly' above @@ -456,7 +513,14 @@ class FffGcodeWriter : public NoCopy * \param skin_part The skin part for which to create gcode * \param[out] added_something Whether this function added anything to the layer plan */ - void processRoofing(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const; + void processRoofing( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part, + bool& added_something) const; /*! * Add the normal skinfill which is the area inside the innermost skin inset @@ -470,11 +534,18 @@ class FffGcodeWriter : public NoCopy * \param skin_part The skin part for which to create gcode * \param[out] added_something Whether this function added anything to the layer plan */ - void processTopBottom(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const; + void processTopBottom( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part, + bool& added_something) const; /*! * Process a dense skin feature like roofing or top/bottom - * + * * \param[in] storage where the slice data is stored. * \param gcode_layer The initial planning of the gcode of the layer. * \param mesh The mesh for which to add to the layer plan \p gcode_layer. @@ -491,7 +562,21 @@ class FffGcodeWriter : public NoCopy * \param[out] added_something Whether this function added anything to the layer plan * \param fan_speed fan speed override for this skin area */ - void processSkinPrintFeature(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const PathConfigStorage::MeshPathConfigs& mesh_config, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, const Ratio skin_density, const bool monotonic, bool& added_something, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; + void processSkinPrintFeature( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const MeshPathConfigs& mesh_config, + const size_t extruder_nr, + const Polygons& area, + const GCodePathConfig& config, + EFillMethod pattern, + const AngleDegrees skin_angle, + const coord_t skin_overlap, + const Ratio skin_density, + const bool monotonic, + bool& added_something, + double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; /*! * see if we can avoid printing a lines or zig zag style skin part in multiple segments by moving to @@ -510,7 +595,7 @@ class FffGcodeWriter : public NoCopy * +------+ +------+ * 1, 2 = start locations of skin segments * # = seam - * + * * \param filling_part The part which we are going to fill with a linear filling type * \param filling_angle The angle of the filling lines * \param last_position The position the print head is in before going to fill the part @@ -556,10 +641,12 @@ class FffGcodeWriter : public NoCopy * layer. * * \param[in] storage Where the slice data is stored. + * \param[in] support_roof_outlines which polygons to generate roofs for -- originally split-up because of fractional (layer-height) layers + * \param[in] current_roof_config config to be used -- most importantly, support has slightly different configs for fractional (layer-height) layers * \param gcodeLayer The initial planning of the g-code of the layer. * \return Whether any support skin was added to the layer plan. */ - bool addSupportRoofsToGCode(const SliceDataStorage& storage, LayerPlan& gcodeLayer) const; + bool addSupportRoofsToGCode(const SliceDataStorage& storage, const Polygons& support_roof_outlines, const GCodePathConfig& current_roof_config, LayerPlan& gcode_layer) const; /*! * Add the support bottoms to the layer plan \p gcodeLayer of the current @@ -573,9 +660,9 @@ class FffGcodeWriter : public NoCopy /*! * Change to a new extruder, and add the prime tower instructions if the new extruder is different from the last. - * + * * On layer 0 this function adds the skirt for the nozzle it switches to, instead of the prime tower. - * + * * \param[in] storage where the slice data is stored. * \param gcode_layer The initial planning of the gcode of the layer. * \param extruder_nr The extruder to switch to. @@ -589,7 +676,7 @@ class FffGcodeWriter : public NoCopy * \param prev_extruder The current extruder with which we last printed. */ void addPrimeTower(const SliceDataStorage& storage, LayerPlan& gcodeLayer, const size_t prev_extruder) const; - + /*! * Add the end gcode and set all temperatures to zero. */ @@ -628,9 +715,15 @@ class FffGcodeWriter : public NoCopy * \param infill_line_width line width of the infill * \return true if there needs to be a skin edge support wall in this layer, otherwise false */ - static bool partitionInfillBySkinAbove(Polygons& infill_below_skin, Polygons& infill_not_below_skin, const LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const SliceLayerPart& part, coord_t infill_line_width) ; + static bool partitionInfillBySkinAbove( + Polygons& infill_below_skin, + Polygons& infill_not_below_skin, + const LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const SliceLayerPart& part, + coord_t infill_line_width); }; -}//namespace cura +} // namespace cura #endif // GCODE_WRITER_H diff --git a/include/FffPolygonGenerator.h b/include/FffPolygonGenerator.h index 6f649d9c77..49342b57fe 100644 --- a/include/FffPolygonGenerator.h +++ b/include/FffPolygonGenerator.h @@ -1,15 +1,15 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef FFF_POLYGON_GENERATOR_H #define FFF_POLYGON_GENERATOR_H +#include "settings/types/LayerIndex.h" #include "utils/NoCopy.h" namespace cura { -struct LayerIndex; class MeshGroup; class ProgressStageEstimator; class SliceDataStorage; @@ -19,24 +19,23 @@ class TimeKeeper; /*! * Primary stage in Fused Filament Fabrication processing: Polygons are generated. * The model is sliced and each slice consists of polygons representing the outlines: the boundaries between inside and outside the object. - * After slicing, the layers are processed; for example the wall insets are generated, and the areas which are to be filled with support and infill, which are all represented by polygons. - * In this stage nothing other than areas and circular paths are generated, which are both represented by polygons. - * No infill lines or support pattern etc. is generated. - * + * After slicing, the layers are processed; for example the wall insets are generated, and the areas which are to be filled with support and infill, which are all represented by + * polygons. In this stage nothing other than areas and circular paths are generated, which are both represented by polygons. No infill lines or support pattern etc. is generated. + * * The main function of this class is FffPolygonGenerator::generateAreas(). */ class FffPolygonGenerator : public NoCopy { public: /*! - * Slice the \p object, process the outline information into inset perimeter polygons, support area polygons, etc. - * + * Slice the \p object, process the outline information into inset perimeter polygons, support area polygons, etc. + * * \param object The object to slice. * \param timeKeeper Object which keeps track of timings of each stage. * \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline. */ bool generateAreas(SliceDataStorage& storage, MeshGroup* object, TimeKeeper& timeKeeper); - + private: /*! * \brief Helper function to get the actual height of the draft shield. @@ -53,73 +52,78 @@ class FffPolygonGenerator : public NoCopy /*! * Slice the \p object and store the outlines in the \p storage. - * + * * \param object The object to slice. * \param timeKeeper Object which keeps track of timings of each stage. * \param storage Output parameter: where the outlines are stored. See SliceLayerPart::outline. - * + * * \return Whether the process succeeded (always true). */ bool sliceModel(MeshGroup* object, TimeKeeper& timeKeeper, SliceDataStorage& storage); /// slices the model /*! - * Processes the outline information as stored in the \p storage: generates inset perimeter polygons, support area polygons, etc. - * + * Processes the outline information as stored in the \p storage: generates inset perimeter polygons, support area polygons, etc. + * * \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage * \param timeKeeper Object which keeps track of timings of each stage. */ void slices2polygons(SliceDataStorage& storage, TimeKeeper& timeKeeper); - + /*! * Processes the outline information as stored in the \p storage: generates inset perimeter polygons, skin and infill - * + * * \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage * \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage * \param mesh_order The order in which the meshes are processed (used for infill meshes) * \param inset_skin_progress_estimate The progress stage estimate calculator */ - void processBasicWallsSkinInfill(SliceDataStorage& storage, const size_t mesh_order_idx, const std::vector& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate); + void processBasicWallsSkinInfill( + SliceDataStorage& storage, + const size_t mesh_order_idx, + const std::vector& mesh_order, + ProgressStageEstimator& inset_skin_progress_estimate); /*! * Process the mesh to be an infill mesh: limit all outlines to within the infill of normal meshes and subtract their volume from the infill of those meshes - * + * * \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage * \param mesh_order_idx The index of the mesh_idx in \p mesh_order to process in the vector of meshes in \p storage * \param mesh_order The order in which the meshes are processed */ void processInfillMesh(SliceDataStorage& storage, const size_t mesh_order_idx, const std::vector& mesh_order); - + /*! * Process features which are derived from the basic walls, skin, and infill: * fuzzy skin, infill combine - * + * * \param mesh Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage */ void processDerivedWallsSkinInfill(SliceMeshStorage& mesh); - + /*! * Checks whether a layer is empty or not - * + * * \param storage Input and Ouput parameter: stores all layers * \param layer_idx Index of the layer to check - * + * * \return Whether or not the layer is empty */ - bool isEmptyLayer(SliceDataStorage& storage, const unsigned int layer_idx); - + bool isEmptyLayer(SliceDataStorage& storage, const LayerIndex& layer_idx); + /*! * \brief Remove all bottom layers which are empty. - * + * * \warning Changes \p total_layers - * + * * \param[in, out] storage Stores all layers. * \param[in, out] total_layers The total number of layers. */ void removeEmptyFirstLayers(SliceDataStorage& storage, size_t& total_layers); /*! - * Set \ref SliceDataStorage::max_print_height_per_extruder and \ref SliceDataStorage::max_print_height_order and \ref SliceDataStorage::max_print_height_second_to_last_extruder - * + * Set \ref SliceDataStorage::max_print_height_per_extruder and \ref SliceDataStorage::max_print_height_order and \ref + * SliceDataStorage::max_print_height_second_to_last_extruder + * * \param[in,out] storage Where to retrieve mesh and support etc settings from and where the print height statistics are saved. */ void computePrintHeightStatistics(SliceDataStorage& storage); @@ -146,7 +150,7 @@ class FffPolygonGenerator : public NoCopy /*! * Generate the polygons where the draft screen should be. - * + * * \param storage Input and Output parameter: fetches the outline information (see SliceLayerPart::outline) and generates the other reachable field of the \p storage */ void processDraftShield(SliceDataStorage& storage); @@ -159,16 +163,16 @@ class FffPolygonGenerator : public NoCopy /*! * Make the outer wall 'fuzzy' - * + * * Introduce new vertices and move existing vertices in or out by a random distance, based on the fuzzy skin settings. - * + * * This only changes the outer wall. - * + * * \param[in,out] mesh where the outer wall is retrieved and stored in. */ void processFuzzyWalls(SliceMeshStorage& mesh); }; -}//namespace cura +} // namespace cura -#endif //FFF_POLYGON_GENERATOR_H +#endif // FFF_POLYGON_GENERATOR_H diff --git a/include/GCodePathConfig.h b/include/GCodePathConfig.h index 89e1ca8905..4531b650aa 100644 --- a/include/GCodePathConfig.h +++ b/include/GCodePathConfig.h @@ -1,105 +1,76 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef G_CODE_PATH_CONFIG_H #define G_CODE_PATH_CONFIG_H #include "PrintFeature.h" -#include "settings/types/Ratio.h" //For flow rate. +#include "pathPlanning/SpeedDerivatives.h" +#include "settings/types/Ratio.h" #include "settings/types/Velocity.h" #include "utils/Coord_t.h" -namespace cura +namespace cura { -struct LayerIndex; - /*! * The GCodePathConfig is the configuration for moves/extrusion actions. This defines at which width the line is printed and at which speed. */ -class GCodePathConfig +struct GCodePathConfig { -public: - /*! - * A simple wrapper class for all derivatives of position which are used when printing a line - */ - struct SpeedDerivatives - { - Velocity speed; //!< movement speed (mm/s) - Acceleration acceleration; //!< acceleration of head movement (mm/s^2) - Velocity jerk; //!< jerk of the head movement (around stand still) as instantaneous speed change (mm/s) - SpeedDerivatives(Velocity speed, Acceleration acceleration, Velocity jerk) : speed(speed), acceleration(acceleration), jerk(jerk) {} - }; - const PrintFeatureType type; //!< name of the feature type + coord_t z_offset{}; // @p max_speed_layer ! - * - * \warning Calling this function twice will smooth the speed more toward \p first_layer_config - * - * \param first_layer_config The speed settings at layer zero - * \param layer_nr The layer number - * \param max_speed_layer The layer number for which the speed_iconic should be used. - */ - void smoothSpeed(SpeedDerivatives first_layer_config, const LayerIndex& layer_nr, const LayerIndex& max_speed_layer); + [[nodiscard]] constexpr bool operator==(const GCodePathConfig& other) const noexcept = default; + [[nodiscard]] constexpr auto operator<=>(const GCodePathConfig& other) const = default; /*! * Can only be called after the layer height has been set (which is done while writing the gcode!) */ - double getExtrusionMM3perMM() const; + [[nodiscard]] double getExtrusionMM3perMM() const noexcept; /*! * Get the movement speed in mm/s */ - Velocity getSpeed() const; + [[nodiscard]] Velocity getSpeed() const noexcept; /*! * Get the current acceleration of this config */ - Acceleration getAcceleration() const; + [[nodiscard]] Acceleration getAcceleration() const noexcept; /*! * Get the current jerk of this config */ - Velocity getJerk() const; + [[nodiscard]] Velocity getJerk() const noexcept; - coord_t getLineWidth() const; + [[nodiscard]] coord_t getLineWidth() const noexcept; - bool isTravelPath() const; + [[nodiscard]] bool isTravelPath() const noexcept; - bool isBridgePath() const; + [[nodiscard]] bool isBridgePath() const noexcept; - double getFanSpeed() const; + [[nodiscard]] double getFanSpeed() const noexcept; - Ratio getFlowRatio() const; + [[nodiscard]] Ratio getFlowRatio() const noexcept; - coord_t getLayerThickness() const; + [[nodiscard]] coord_t getLayerThickness() const noexcept; - const PrintFeatureType& getPrintFeatureType() const; + [[nodiscard]] PrintFeatureType getPrintFeatureType() const noexcept; private: - double calculateExtrusion() const; + [[nodiscard]] double calculateExtrusion() const noexcept; }; -}//namespace cura +} // namespace cura #endif // G_CODE_PATH_CONFIG_H diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index 432ae2c29f..f5bdba8f57 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -4,11 +4,11 @@ #ifndef INSET_ORDER_OPTIMIZER_H #define INSET_ORDER_OPTIMIZER_H -#include - #include "settings/ZSeamConfig.h" #include "sliceDataStorage.h" +#include + namespace cura { @@ -37,22 +37,23 @@ class InsetOrderOptimizer * \param part The part from which to read the previously generated insets. * \param layer_nr The current layer number. */ - InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, - const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const Settings& settings, - const int extruder_nr, - const GCodePathConfig& inset_0_non_bridge_config, - const GCodePathConfig& inset_X_non_bridge_config, - const GCodePathConfig& inset_0_bridge_config, - const GCodePathConfig& inset_X_bridge_config, - const bool retract_before_outer_wall, - const coord_t wall_0_wipe_dist, - const coord_t wall_x_wipe_dist, - const size_t wall_0_extruder_nr, - const size_t wall_x_extruder_nr, - const ZSeamConfig& z_seam_config, - const std::vector& paths); + InsetOrderOptimizer( + const FffGcodeWriter& gcode_writer, + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const Settings& settings, + const int extruder_nr, + const GCodePathConfig& inset_0_non_bridge_config, + const GCodePathConfig& inset_X_non_bridge_config, + const GCodePathConfig& inset_0_bridge_config, + const GCodePathConfig& inset_X_bridge_config, + const bool retract_before_outer_wall, + const coord_t wall_0_wipe_dist, + const coord_t wall_x_wipe_dist, + const size_t wall_0_extruder_nr, + const size_t wall_x_extruder_nr, + const ZSeamConfig& z_seam_config, + const std::vector& paths); /*! * Adds the insets to the given layer plan. @@ -66,9 +67,9 @@ class InsetOrderOptimizer /*! * Get the order constraints of the insets when printing walls per region / hole. * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * + * * Odd walls should always go after their enclosing wall polygons. - * + * * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. */ static value_type getRegionOrder(const auto& input, const bool outer_to_inner); @@ -76,12 +77,13 @@ class InsetOrderOptimizer /*! * Get the order constraints of the insets when printing walls per inset. * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * + * * Odd walls should always go after their enclosing wall polygons. - * + * * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. */ static value_type getInsetOrder(const auto& input, const bool outer_to_inner); + private: const FffGcodeWriter& gcode_writer; const SliceDataStorage& storage; @@ -99,10 +101,11 @@ class InsetOrderOptimizer const size_t wall_x_extruder_nr; const ZSeamConfig& z_seam_config; const std::vector& paths; - const unsigned int layer_nr; + const LayerIndex layer_nr; std::vector> inset_polys; // vector of vectors holding the inset polygons - Polygons retraction_region; //After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see retraction_region_calculated). + Polygons retraction_region; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see + // retraction_region_calculated). /*! * Determine if the paths should be reversed @@ -130,8 +133,7 @@ class InsetOrderOptimizer * \return A vector of ExtrusionLines with walls that should be printed */ std::vector getWallsToBeAdded(const bool reverse, const bool use_one_extruder); - }; -} //namespace cura +} // namespace cura #endif // INSET_ORDER_OPTIMIZER_H diff --git a/include/LayerPlan.h b/include/LayerPlan.h index a4679761e1..972af3e6d3 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -4,221 +4,54 @@ #ifndef LAYER_PLAN_H #define LAYER_PLAN_H -#include -#include -#include -#include -#ifdef BUILD_TESTS - #include //Friend tests, so that they can inspect the privates. -#endif - +#include "ExtruderPlan.h" #include "FanSpeedLayerTime.h" -#include "gcodeExport.h" +#include "InsetOrderOptimizer.h" #include "PathOrderOptimizer.h" #include "SpaceFillType.h" +#include "gcodeExport.h" #include "pathPlanning/GCodePath.h" #include "pathPlanning/NozzleTempInsert.h" #include "pathPlanning/TimeMaterialEstimates.h" #include "settings/PathConfigStorage.h" #include "settings/types/LayerIndex.h" -#include "utils/polygon.h" - -#include "InsetOrderOptimizer.h" #include "utils/ExtrusionJunction.h" +#include "utils/polygon.h" -namespace cura -{ - -class Comb; -class LayerPlan; // forward declaration so that ExtruderPlan can be a friend -class LayerPlanBuffer; // forward declaration so that ExtruderPlan can be a friend -class SliceDataStorage; - -/*! - * An extruder plan contains all planned paths (GCodePath) pertaining to a single extruder train. - * - * It allows for temperature command inserts which can be inserted in between paths. - */ -class ExtruderPlan -{ - friend class LayerPlan; // TODO: LayerPlan still does a lot which should actually be handled in this class. - friend class LayerPlanBuffer; // TODO: LayerPlanBuffer handles paths directly #ifdef BUILD_TESTS - friend class ExtruderPlanPathsParameterizedTest; - FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationZeroIsUncompensated); - FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull); - FRIEND_TEST(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf); - FRIEND_TEST(ExtruderPlanTest, BackPressureCompensationEmptyPlan); +#include //Friend tests, so that they can inspect the privates. #endif -protected: - std::vector paths; //!< The paths planned for this extruder - std::list inserts; //!< The nozzle temperature command inserts, to be inserted in between segments - - double heated_pre_travel_time; //!< The time at the start of this ExtruderPlan during which the head travels and has a temperature of initial_print_temperature - - /*! - * The required temperature at the start of this extruder plan - * or the temp to which to heat gradually over the layer change between this plan and the previous with the same extruder. - * - * In case this extruder plan uses a different extruder than the last extruder plan: - * this is the temperature to which to heat and wait before starting this extruder. - * - * In case this extruder plan uses the same extruder as the previous extruder plan (previous layer): - * this is the temperature used to heat to gradually when moving from the previous extruder layer to the next. - * In that case no temperature (and wait) command will be inserted from this value, but a NozzleTempInsert is used instead. - * In this case this member is only used as a way to convey information between different calls of \ref LayerPlanBuffer::processBuffer - */ - double required_start_temperature; - std::optional extrusion_temperature; //!< The normal temperature for printing this extruder plan. That start and end of this extruder plan may deviate because of the initial and final print temp (none if extruder plan has no extrusion moves) - std::optional::iterator> extrusion_temperature_command; //!< The command to heat from the printing temperature of this extruder plan to the printing temperature of the next extruder plan (if it has the same extruder). - std::optional prev_extruder_standby_temp; //!< The temperature to which to set the previous extruder. Not used if the previous extruder plan was the same extruder. - - TimeMaterialEstimates estimates; //!< Accumulated time and material estimates for all planned paths within this extruder plan. - double slowest_path_speed; - -public: - size_t extruder_nr; //!< The extruder used for this paths in the current plan. - - /*! - * Simple contructor. - * - * \warning Doesn't set the required temperature yet. - * - * \param extruder The extruder number for which this object is a plan. - * \param layer_nr The layer index of the layer that this extruder plan is - * part of. - * \param is_raft_layer Whether this extruder plan is part of a raft layer. - */ - ExtruderPlan(const size_t extruder, const LayerIndex layer_nr, const bool is_initial_layer, const bool is_raft_layer, const coord_t layer_thickness, const FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, const RetractionConfig& retraction_config); - - - void insertCommand(auto&& insert) - { - inserts.emplace_back(std::forward(insert)); - } - - /*! - * Insert the inserts into gcode which should be inserted before \p path_idx - * - * \param path_idx The index into ExtruderPlan::paths which is currently being consider for temperature command insertion - * \param gcode The gcode exporter to which to write the temperature command. - * \param cumulative_path_time The time spend on this path up to this point. - */ - void handleInserts(const size_t path_idx, GCodeExport& gcode, const double& cumulative_path_time = std::numeric_limits::infinity()); - - /*! - * Insert all remaining temp inserts into gcode, to be called at the end of an extruder plan - * - * Inserts temperature commands which should be inserted _after_ the last path. - * Also inserts all temperatures which should have been inserted earlier, - * but for which ExtruderPlan::handleInserts hasn't been called correctly. - * - * \param gcode The gcode exporter to which to write the temperature command. - */ - void handleAllRemainingInserts(GCodeExport& gcode); - - /*! - * Applying fan speed changes for minimal layer times. - * - * \param starting_position The position the head was before starting this extruder plan - * \param minTime Maximum minimum layer time for all extruders in this layer - * \param time_other_extr_plans The time spent on the other extruder plans in this layer - */ - void processFanSpeedForMinimalLayerTime(Point starting_position, Duration maximum_cool_min_layer_time, double time_other_extr_plans); - - /*! - * Applying fan speed changes for the first layers. - */ - void processFanSpeedForFirstLayers(); - - /*! - * Get the fan speed computed for this extruder plan - * - * \warning assumes ExtruderPlan::processFanSpeedForMinimalLayerTime has already been called - * - * \return The fan speed computed in processFanSpeedForMinimalLayerTime - */ - double getFanSpeed(); - - /*! - * Apply back-pressure compensation to this path. - * Since the total (filament) pressure in a feeder-system is not only dependent on the pressure that exists between the nozzle and the - * feed-mechanism (which should be near-constant on a bowden style setup), but _also_ between the nozzle and the last-printed layer. - * This last type is called 'back-pressure'. In this function, properties of the path-outflow are adjusted so that the back-pressure is - * compensated for. This is conjectured to be especially important if the printer has a Bowden-tube style setup. - * - * \param The amount of back-pressure compensation as a ratio. 'Applying' a value of 0 is a no-op. - */ - void applyBackPressureCompensation(const Ratio back_pressure_compensation); - -protected: - LayerIndex layer_nr; //!< The layer number at which we are currently printing. - bool is_initial_layer; //!< Whether this extruder plan is printed on the very first layer (which might be raft) - const bool is_raft_layer; //!< Whether this is a layer which is part of the raft - - coord_t layer_thickness; //!< The thickness of this layer in Z-direction - - const FanSpeedLayerTimeSettings& fan_speed_layer_time_settings; //!< The fan speed and layer time settings used to limit this extruder plan - - const RetractionConfig& retraction_config; //!< The retraction settings for the extruder of this plan - - double extraTime; //!< Extra waiting time at the and of this extruder plan, so that the filament can cool - - double fan_speed; //!< The fan speed to be used during this extruder plan - - double temperatureFactor; //!< Temperature reduction factor for small layers - - /*! - * Set the fan speed to be used while printing this extruder plan - * - * \param fan_speed The speed for the fan - */ - void setFanSpeed(double fan_speed); - - /*! - * Force the minimal layer time to hold by slowing down and lifting the head if required. - * - * \param maximum_cool_min_layer_time Maximum minimum layer time for all extruders in this layer - * \param time_other_extr_plans Time spend on other extruders in this layer - */ - void forceMinimalLayerTime(double maximum_cool_min_layer_time, double time_other_extr_plans); - /*! - * @return The time needed for (un)retract the path - */ - double getRetractTime(const GCodePath& path); +#include +#include +#include +#include +#include - /*! - * @return distance between p0 and p1 as well as the time spend on the segment - */ - std::pair getPointToPointTime(const Point& p0, const Point& p1, const GCodePath& path); +namespace cura +{ - /*! - * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. - * and store them in each ExtruderPlan and each GCodePath. - * - * \param starting_position The position the head was in before starting this layer - * \return the total estimates of this layer - */ - TimeMaterialEstimates computeNaiveTimeEstimates(Point starting_position); -}; +class Comb; +class SliceDataStorage; +class LayerPlanBuffer; -class LayerPlanBuffer; // forward declaration to prevent circular dependency -/*! +/*! * The LayerPlan class stores multiple moves that are planned. - * - * + * + * * It facilitates the combing to keep the head inside the print. * It also keeps track of the print time estimate for this planning so speed adjustments can be made for the minimal-layer-time. - * + * * A LayerPlan is also knows as a 'layer plan'. - * + * */ class LayerPlan : public NoCopy { friend class LayerPlanBuffer; +#ifdef BUILD_TESTS friend class AddTravelTest; +#endif public: const PathConfigStorage configs_storage; //!< The line configs for this layer for each feature type @@ -227,7 +60,6 @@ class LayerPlan : public NoCopy bool mode_skip_agressive_merge; //!< Whether to give every new path the 'skip_agressive_merge_hint' property (see GCodePath); default is false. private: - const SliceDataStorage& storage; //!< The polygon data obtained from FffPolygonProcessor const LayerIndex layer_nr; //!< The layer number of this layer plan const bool is_initial_layer; //!< Whether this is the first layer (which might be raft) @@ -238,7 +70,7 @@ class LayerPlan : public NoCopy std::vector has_prime_tower_planned_per_extruder; //!< For each extruder, whether the prime tower is planned yet or not. std::optional last_planned_position; //!< The last planned XY position of the print head (if known) - const SliceMeshStorage* current_mesh; //!< The mesh of the last planned move. + std::shared_ptr current_mesh; //!< The mesh of the last planned move. /*! * Whether the skirt or brim polygons have been processed into planned paths @@ -260,7 +92,7 @@ class LayerPlan : public NoCopy Polygons comb_boundary_minimum; //!< The minimum boundary within which to comb, or to move into when performing a retraction. Polygons comb_boundary_preferred; //!< The boundary preferably within which to comb, or to move into when performing a retraction. Comb* comb; - coord_t comb_move_inside_distance; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing. + coord_t comb_move_inside_distance; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing. Polygons bridge_wall_mask; //!< The regions of a layer part that are not supported, used for bridging Polygons overhang_mask; //!< The regions of a layer part where the walls overhang @@ -275,25 +107,33 @@ class LayerPlan : public NoCopy /*! * Either create a new path with the given config or return the last path if it already had that config. * If LayerPlan::forceNewPathStart has been called a new path will always be returned. - * + * * \param config The config used for the path returned * \param space_fill_type The type of space filling which this path employs + * \param z_offset (optional) Vertical offset w.r.t current layer height, defaults to 0 * \param flow (optional) A ratio for the extrusion speed * \param spiralize Whether to gradually increase the z while printing. (Note that this path may be part of a sequence of spiralized paths, forming one polygon) * \param speed_factor (optional) a factor which the speed will be multiplied by. * \return A path with the given config which is now the last path in LayerPlan::paths */ - GCodePath* getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow = 1.0_r, const Ratio width_factor = 1.0_r, bool spiralize = false, const Ratio speed_factor = 1.0_r); + GCodePath* getLatestPathWithConfig( + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const coord_t z_offset = 0, + const Ratio flow = 1.0_r, + const Ratio width_factor = 1.0_r, + const bool spiralize = false, + const Ratio speed_factor = 1.0_r); public: /*! * Force LayerPlan::getLatestPathWithConfig to return a new path. - * - * This function is introduced because in some cases - * LayerPlan::getLatestPathWithConfig is called consecutively with the same config pointer, + * + * This function is introduced because in some cases + * LayerPlan::getLatestPathWithConfig is called consecutively with the same config pointer, * though the content of the config has changed. - * - * Example cases: + * + * Example cases: * - when changing extruder, the same travel config is used, but its extruder field is changed. */ void forceNewPathStart(); @@ -313,7 +153,16 @@ class LayerPlan : public NoCopy * \param travel_avoid_distance The distance by which to avoid other layer * parts when travelling through air. */ - LayerPlan(const SliceDataStorage& storage, LayerIndex layer_nr, coord_t z, coord_t layer_height, size_t start_extruder, const std::vector& fan_speed_layer_time_settings_per_extruder, coord_t comb_boundary_offset, coord_t comb_move_inside_distance, coord_t travel_avoid_distance); + LayerPlan( + const SliceDataStorage& storage, + LayerIndex layer_nr, + coord_t z, + coord_t layer_height, + size_t start_extruder, + const std::vector& fan_speed_layer_time_settings_per_extruder, + coord_t comb_boundary_offset, + coord_t comb_move_inside_distance, + coord_t travel_avoid_distance); ~LayerPlan(); @@ -327,11 +176,11 @@ class LayerPlan : public NoCopy const Polygons* getCombBoundaryInside() const; - int getLayerNr() const; + LayerIndex getLayerNr() const; /*! * Get the last planned position, or if no position has been planned yet, the user specified layer start position. - * + * * \warning The layer start position might be outside of the build plate! */ Point getLastPlannedPositionOrStartingPosition() const; @@ -361,17 +210,17 @@ class LayerPlan : public NoCopy /*! * Get the destination state of the first travel move. * This consists of the location and whether the destination was inside the model, or e.g. to support - * + * * Returns nothing if the layer is empty and no travel move was ever made. */ std::optional> getFirstTravelDestinationState() const; /*! - * Set whether the next destination is inside a layer part or not. - * - * Features like infill, walls, skin etc. are considered inside. - * Features like prime tower and support are considered outside. - */ + * Set whether the next destination is inside a layer part or not. + * + * Features like infill, walls, skin etc. are considered inside. + * Features like prime tower and support are considered outside. + */ void setIsInside(bool is_inside); /*! @@ -391,7 +240,7 @@ class LayerPlan : public NoCopy * Track the currently printing mesh. * \param mesh_id A unique ID indicating the current mesh. */ - void setMesh(const SliceMeshStorage* mesh_id); + void setMesh(const std::shared_ptr& mesh); /*! * Set bridge_wall_mask. @@ -434,17 +283,17 @@ class LayerPlan : public NoCopy * \param p The point to travel to. * \param force_retract Whether to force a retraction to occur. */ - GCodePath& addTravel(const Point p, const bool force_retract = false); + GCodePath& addTravel(const Point& p, const bool force_retract = false, const coord_t z_offset = 0); /*! * Add a travel path to a certain point and retract if needed. - * + * * No combing is performed. - * + * * \param p The point to travel to * \param path (optional) The travel path to which to add the point \p p */ - GCodePath& addTravel_simple(Point p, GCodePath* path = nullptr); + GCodePath& addTravel_simple(const Point& p, GCodePath* path = nullptr); /*! * Plan a prime blob at the current location. @@ -453,7 +302,7 @@ class LayerPlan : public NoCopy /*! * Add an extrusion move to a certain point, optionally with a different flow than the one in the \p config. - * + * * \param p The point to extrude to * \param config The config with which to extrude * \param space_fill_type Of what space filling type this extrusion move is @@ -469,7 +318,15 @@ class LayerPlan : public NoCopy * forming one polygon.) * \param fan_speed Fan speed override for this path. */ - void addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, const Ratio width_factor = 1.0_r, bool spiralize = false, Ratio speed_factor = 1.0_r, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); + void addExtrusionMove( + const Point p, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const Ratio& flow = 1.0_r, + const Ratio width_factor = 1.0_r, + const bool spiralize = false, + const Ratio speed_factor = 1.0_r, + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); /*! * Add polygon to the gcode starting at vertex \p startIdx @@ -482,11 +339,19 @@ class LayerPlan : public NoCopy * \param flow_ratio The ratio with which to multiply the extrusion amount * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) */ - void addPolygon(ConstPolygonRef polygon, int startIdx, const bool reverse, const GCodePathConfig& config, coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio& flow_ratio = 1.0_r, bool always_retract = false); + void addPolygon( + ConstPolygonRef polygon, + int startIdx, + const bool reverse, + const GCodePathConfig& config, + coord_t wall_0_wipe_dist = 0, + bool spiralize = false, + const Ratio& flow_ratio = 1.0_r, + bool always_retract = false); /*! * Add polygons to the gcode with optimized order. - * + * * When \p spiralize is true, each polygon will gradually increase from a z * corresponding to this layer to the z corresponding to the next layer. * Doing this for each polygon means there is a chance for the print head to @@ -494,7 +359,7 @@ class LayerPlan : public NoCopy * would mean you are printing half of the layer in non-spiralize mode, * while each layer starts with a different part. Two towers would result in * alternating spiralize and non-spiralize layers. - * + * * \param polygons The polygons. * \param config The config with which to print the polygon lines. * for each given segment (optionally nullptr). @@ -512,7 +377,16 @@ class LayerPlan : public NoCopy * \param start_near_location Start optimising the path near this location. * If unset, this causes it to start near the last planned location. */ - void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); + void addPolygonsByOptimizer( + const Polygons& polygons, + const GCodePathConfig& config, + const ZSeamConfig& z_seam_config = ZSeamConfig(), + coord_t wall_0_wipe_dist = 0, + bool spiralize = false, + const Ratio flow_ratio = 1.0_r, + bool always_retract = false, + bool reverse_order = false, + const std::optional start_near_location = std::optional()); /*! * Add a single line that is part of a wall to the gcode. @@ -534,7 +408,17 @@ class LayerPlan : public NoCopy * \param distance_to_bridge_start The distance along the wall from p0 to * the first bridge segment. */ - void addWallLine(const Point& p0, const Point& p1, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, float flow, const Ratio width_factor, float& non_bridge_line_volume, Ratio speed_factor, double distance_to_bridge_start); + void addWallLine( + const Point& p0, + const Point& p1, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + float flow, + const Ratio width_factor, + float& non_bridge_line_volume, + Ratio speed_factor, + double distance_to_bridge_start); /*! * Add a wall to the g-code starting at vertex \p start_idx @@ -551,7 +435,15 @@ class LayerPlan : public NoCopy * \param always_retract Whether to force a retraction when moving to the * start of the wall (used for outer walls). */ - void addWall(ConstPolygonRef wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract); + void addWall( + ConstPolygonRef wall, + int start_idx, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + float flow_ratio, + bool always_retract); /*! * Add a wall to the g-code starting at vertex \p start_idx @@ -572,7 +464,18 @@ class LayerPlan : public NoCopy * \param is_reversed Whether to print this wall in reverse direction. * \param is_linked_path Whether the path is a continuation off the previous path */ - void addWall(const ExtrusionLine& wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, const bool is_linked_path); + void addWall( + const ExtrusionLine& wall, + int start_idx, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + float flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path); /*! * Add an infill wall to the g-code @@ -594,8 +497,7 @@ class LayerPlan : public NoCopy * \param always_retract Whether to force a retraction when moving to the start of a wall (used for outer walls) * \param alternate_inset_direction_modifier Whether to alternate the direction of the walls for each inset. */ - void addWalls - ( + void addWalls( const Polygons& walls, const Settings& settings, const GCodePathConfig& non_bridge_config, @@ -603,8 +505,7 @@ class LayerPlan : public NoCopy const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, float flow_ratio = 1.0, - bool always_retract = false - ); + bool always_retract = false); /*! * Add lines to the gcode with optimized order. @@ -619,7 +520,17 @@ class LayerPlan : public NoCopy * \param reverse_print_direction Whether to reverse the optimized order and their printing direction. * \param order_requirements Pairs where first needs to be printed before second. Pointers are pointing to elements of \p polygons */ - void addLinesByOptimizer(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const bool enable_travel_optimization = false, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0, const std::optional near_start_location = std::optional(), const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, const bool reverse_print_direction = false, const std::unordered_multimap& order_requirements = PathOrderOptimizer::no_order_requirements); + void addLinesByOptimizer( + const Polygons& polygons, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const bool enable_travel_optimization = false, + const coord_t wipe_dist = 0, + const Ratio flow_ratio = 1.0, + const std::optional near_start_location = std::optional(), + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, + const bool reverse_print_direction = false, + const std::unordered_multimap& order_requirements = PathOrderOptimizer::no_order_requirements); /*! * Add polygons to the g-code with monotonic order. @@ -642,8 +553,7 @@ class LayerPlan : public NoCopy * \param flow_ratio The ratio with which to multiply the extrusion amount. * \param fan_speed Fan speed override for this path. */ - void addLinesMonotonic - ( + void addLinesMonotonic( const Polygons& area, const Polygons& polygons, const GCodePathConfig& config, @@ -653,8 +563,7 @@ class LayerPlan : public NoCopy const coord_t exclude_distance = 0, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0_r, - const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT - ); + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); protected: /*! @@ -672,8 +581,7 @@ class LayerPlan : public NoCopy const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, - const double fan_speed - ); + const double fan_speed); public: /*! @@ -689,7 +597,14 @@ class LayerPlan : public NoCopy * \param is_top_layer true when the top layer of the spiral is being printed * \param is_bottom_layer true when the bottom layer of the spiral is being printed */ - void spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRef wall, ConstPolygonRef last_wall, int seam_vertex_idx, int last_seam_vertex_idx, const bool is_top_layer, const bool is_bottom_layer); + void spiralizeWallSlice( + const GCodePathConfig& config, + ConstPolygonRef wall, + ConstPolygonRef last_wall, + int seam_vertex_idx, + int last_seam_vertex_idx, + const bool is_top_layer, + const bool is_bottom_layer); /*! @@ -716,7 +631,7 @@ class LayerPlan : public NoCopy while (true) { const Point& vertex = cura::make_point(wall[curr_idx]); - if (!air_below.inside(vertex, true)) + if (! air_below.inside(vertex, true)) { // vertex isn't above air so it's OK to use return curr_idx; @@ -737,7 +652,7 @@ class LayerPlan : public NoCopy /*! * Write the planned paths to gcode - * + * * \param gcode The gcode to write the planned paths to */ void writeGCode(GCodeExport& gcode); @@ -745,18 +660,18 @@ class LayerPlan : public NoCopy /*! * Whether the current retracted path is to be an extruder switch retraction. * This function is used to avoid a G10 S1 after a G10. - * + * * \param extruder_plan_idx The index of the current extruder plan - * \param path_idx The index of the current retracted path + * \param path_idx The index of the current retracted path * \return Whether the path should be an extruder switch retracted path */ bool makeRetractSwitchRetract(unsigned int extruder_plan_idx, unsigned int path_idx); - + /*! * Writes a path to GCode and performs coasting, or returns false if it did nothing. - * + * * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. - * + * * \param gcode The gcode to write the planned paths to. * \param extruder_plan_idx The index of the current extruder plan. * \param path_idx The index into LayerPlan::paths for the next path to be @@ -764,15 +679,20 @@ class LayerPlan : public NoCopy * \param layer_thickness The height of the current layer. * \return Whether any GCode has been written for the path. */ - bool writePathWithCoasting(GCodeExport& gcode, const size_t extruder_plan_idx, const size_t path_idx, const coord_t layer_thickness, const std::function insertTempOnTime); + bool writePathWithCoasting( + GCodeExport& gcode, + const size_t extruder_plan_idx, + const size_t path_idx, + const coord_t layer_thickness, + const std::function insertTempOnTime); /*! - * Applying speed corrections for minimal layer times and determine the fanSpeed. - * + * Applying speed corrections for minimal layer times and determine the fanSpeed. + * * \param starting_position The position of the print head when the first extruder plan of this layer starts */ void processFanSpeedAndMinimalLayerTime(Point starting_position); - + /*! * Add a travel move to the layer plan to move inside the current layer part * by a given distance away from the outline. @@ -786,6 +706,11 @@ class LayerPlan : public NoCopy */ void moveInsideCombBoundary(const coord_t distance, const std::optional& part = std::nullopt); + /*! + * If enabled, apply the modify plugin to the layer-plan. + */ + void applyModifyPlugin(); + /*! * Apply back-pressure compensation to this layer-plan. * Since the total (filament) pressure in a feeder-system is not only dependent on the pressure that exists between the nozzle and the @@ -796,7 +721,6 @@ class LayerPlan : public NoCopy void applyBackPressureCompensation(); private: - /*! * \brief Compute the preferred or minimum combing boundary * @@ -816,6 +740,6 @@ class LayerPlan : public NoCopy Polygons computeCombBoundary(const CombBoundary boundary_type); }; -}//namespace cura +} // namespace cura #endif // LAYER_PLAN_H diff --git a/include/LayerPlanBuffer.h b/include/LayerPlanBuffer.h index 4e05ffcade..2b1ce8cefc 100644 --- a/include/LayerPlanBuffer.h +++ b/include/LayerPlanBuffer.h @@ -1,58 +1,69 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef LAYER_PLAN_BUFFER_H #define LAYER_PLAN_BUFFER_H #include +#include +#include "ExtruderPlan.h" +#include "LayerPlan.h" #include "Preheat.h" +#include "gcodeExport.h" +#include "settings/Settings.h" #include "settings/types/Duration.h" -namespace cura +namespace cura { -class ExtruderPlan; -class LayerPlan; + +class GCodeExport; /*! * Class for buffering multiple layer plans (\ref LayerPlan) / extruder plans within those layer plans, so that temperature commands can be inserted in earlier layer plans. - * + * * This class handles where to insert temperature commands for: * - initial layer temperature * - flow dependent temperature * - starting to heat up from the standby temperature * - initial printing temperature | printing temperature | final printing temperature - * + * * \image html assets/precool.png "Temperature Regulation" width=10cm * \image latex assets/precool.png "Temperature Regulation" width=10cm - * + * */ class LayerPlanBuffer { + friend class LayerPlan; GCodeExport& gcode; Preheat preheat_config; //!< the nozzle and material temperature settings for each extruder train. - static constexpr size_t buffer_size = 5; // should be as low as possible while still allowing enough time in the buffer to heat up from standby temp to printing temp // TODO: hardcoded value + static constexpr size_t buffer_size + = 5; // should be as low as possible while still allowing enough time in the buffer to heat up from standby temp to printing temp // TODO: hardcoded value // this value should be higher than 1, cause otherwise each layer is viewed as the first layer and no temp commands are inserted. - static constexpr Duration extra_preheat_time = 1.0_s; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones. + static constexpr Duration extra_preheat_time + = 1.0_s; //!< Time to start heating earlier than computed to avoid accummulative discrepancy between actual heating times and computed ones. - std::vector extruder_used_in_meshgroup; //!< For each extruder whether it has already been planned once in this meshgroup. This is used to see whether we should heat to the initial_print_temp or to the extrusion_temperature + std::vector extruder_used_in_meshgroup; //!< For each extruder whether it has already been planned once in this meshgroup. This is used to see whether we should heat to + //!< the initial_print_temp or to the extrusion_temperature /*! * The buffer containing several layer plans (LayerPlan) before writing them to gcode. - * + * * The front is the lowest/oldest layer. * The back is the highest/newest layer. */ std::list buffer; + public: LayerPlanBuffer(GCodeExport& gcode) - : gcode(gcode) - , extruder_used_in_meshgroup(MAX_EXTRUDERS, false) - { } + : gcode(gcode) + , extruder_used_in_meshgroup(MAX_EXTRUDERS, false) + { + } void setPreheatConfig(); @@ -64,7 +75,7 @@ class LayerPlanBuffer /*! * Push a new layer onto the buffer and handle the buffer. * Write a layer to gcode if it is popped out of the buffer. - * + * * \param layer_plan The layer to handle * \param gcode The exporter with which to write a layer to gcode if the buffer is too large after pushing the new layer. */ @@ -81,7 +92,7 @@ class LayerPlanBuffer * This inserts the temperature commands to start warming for a given layer in earlier layers; * the fan speeds and layer time settings of the most recently pushed layer are processed; * the correctly combing travel move between the last added layer and the layer before is added. - * + * * Pop out the earliest layer in the buffer if the buffer size is exceeded * \return A nullptr or the popped gcode_layer */ @@ -89,7 +100,7 @@ class LayerPlanBuffer /*! * Add the travel move to properly travel from the end location of the previous layer to the starting location of the next - * + * * \param prev_layer The layer before the just added layer, to which to add the combing travel move. * \param newest_layer The newly added layer, with a non-combing travel move as first path. */ @@ -102,7 +113,7 @@ class LayerPlanBuffer /*! * Insert a preheat command for @p extruder into @p extruder_plan_before - * + * * \param extruder_plan_before An extruder plan before the extruder plan for which the temperature is computed, in which to insert the preheat command * \param time_before_extruder_plan_end The time before the end of the extruder plan, before which to insert the preheat command * \param extruder_nr The extruder for which to set the temperature @@ -113,9 +124,9 @@ class LayerPlanBuffer /*! * Compute the time needed to preheat from standby to required (initial) printing temperature at the start of an extruder plan, * based on the time the extruder has been on standby. - * + * * Also computes the temperature to which we cool before starting to heat agian. - * + * * \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers) * \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed * \return the time needed to preheat and the temperature from which heating starts @@ -123,11 +134,11 @@ class LayerPlanBuffer Preheat::WarmUpResult computeStandbyTempPlan(std::vector& extruder_plans, unsigned int extruder_plan_idx); /*! - * For two consecutive extruder plans of the same extruder (so on different layers), + * For two consecutive extruder plans of the same extruder (so on different layers), * preheat the extruder to the temperature corresponding to the average flow of the second extruder plan. - * + * * The preheat commands are inserted such that the middle of the temperature change coincides with the start of the next layer. - * + * * \param prev_extruder_plan The former extruder plan (of the former layer) * \param extruder_nr The extruder for which too set the temperature * \param required_temp The required temperature for the second extruder plan @@ -139,7 +150,7 @@ class LayerPlanBuffer * Find the time window in which this extruder hasn't been used * and compute at what time the preheat command needs to be inserted. * Then insert the preheat command in the right extruder plan. - * + * * \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers) * \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to find the preheat time needed */ @@ -148,15 +159,15 @@ class LayerPlanBuffer /*! * Insert temperature commands related to the extruder plan corersponding to @p extruder_plan_idx * and the extruder plan before: - * + * * In case the extruder plan before has the same extruder: * - gradually change printing temperature around the layer change (\ref LayerPlanBuffer::insertPreheatCommand_singleExtrusion) - * + * * In case the previous extruder plan is a different extruder * - insert preheat command from standby to initial temp in the extruder plan(s) before (\ref LayerPlanBuffer::insertPreheatCommand_multiExtrusion) * - insert the final print temp command of the previous extruder plan (\ref LayerPlanBuffer::insertFinalPrintTempCommand) * - insert the normal extrusion temp command for the current extruder plan (\ref LayerPlanBuffer::insertPrintTempCommand) - * + * * \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers) * \param extruder_plan_idx The index of the extruder plan in \p extruder_plans for which to generate the preheat command */ @@ -164,20 +175,20 @@ class LayerPlanBuffer /*! * Insert the temperature command to heat from the initial print temperature to the printing temperature - * + * * The temperature command is insert at the start of the very first extrusion move - * + * * \param extruder_plan The extruder plan in which to insert the heat up command */ void insertPrintTempCommand(ExtruderPlan& extruder_plan); /*! * Insert the temp command to start cooling from the printing temperature to the final print temp - * + * * The print temp is inserted before the last extrusion move of the extruder plan corresponding to \p last_extruder_plan_idx - * + * * The command is inserted at a timed offset before the end of the last extrusion move - * + * * \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers) * \param last_extruder_plan_idx The index of the last extruder plan in \p extruder_plans with the same extruder as previous extruder plans */ @@ -192,7 +203,7 @@ class LayerPlanBuffer * Reconfigure the standby temperature during which we didn't print with this extruder. * Find the previous extruder plan with the same extruder as layers[layer_plan_idx].extruder_plans[extruder_plan_idx] * Set the prev_extruder_standby_temp in the next extruder plan - * + * * \param extruder_plans The extruder plans in the buffer, moved to a temporary vector (from lower to upper layers) * \param extruder_plan_idx The index of the extruder plan in \p extruder_plans before which to reconfigure the standby temperature * \param standby_temp The temperature to which to cool down when the extruder is in standby mode. @@ -201,7 +212,6 @@ class LayerPlanBuffer }; - } // namespace cura -#endif // LAYER_PLAN_BUFFER_H \ No newline at end of file +#endif // LAYER_PLAN_BUFFER_H diff --git a/include/Mold.h b/include/Mold.h index 744fb3dd3e..03d5ecc536 100644 --- a/include/Mold.h +++ b/include/Mold.h @@ -1,9 +1,11 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef MOLD_H #define MOLD_H +#include + namespace cura { @@ -33,9 +35,10 @@ class Mold * offset from the mold angle). */ static void process(std::vector& slicer_list); + private: }; -}//namespace cura +} // namespace cura -#endif//MOLD_H +#endif // MOLD_H diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 93198fcd19..ae7591db45 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -4,9 +4,6 @@ #ifndef PATHORDEROPTIMIZER_H #define PATHORDEROPTIMIZER_H -#include -#include - #include "InsetOrderOptimizer.h" // for makeOrderIncludeTransitive #include "PathOrdering.h" #include "pathPlanning/CombPath.h" //To calculate the combing distance if we want to use combing. @@ -16,12 +13,17 @@ #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. #include "utils/polygonUtils.h" #include "utils/views/dfs.h" -#include -#include + #include #include -#include #include +#include +#include +#include +#include +#include + +#include namespace cura { @@ -99,10 +101,17 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, const std::unordered_multimap& order_requirements = no_order_requirements, const bool group_outer_walls = false) + PathOrderOptimizer( + const Point start_point, + const ZSeamConfig seam_config = ZSeamConfig(), + const bool detect_loops = false, + const Polygons* combing_boundary = nullptr, + const bool reverse_direction = false, + const std::unordered_multimap& order_requirements = no_order_requirements, + const bool group_outer_walls = false) : start_point(start_point) , seam_config(seam_config) - , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) + , combing_boundary((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) , order_requirements(&order_requirements) @@ -136,72 +145,72 @@ class PathOrderOptimizer * This reorders the \ref paths field and fills their starting vertices and * directions. */ - void optimize() + void optimize(bool precompute_start = true) { - if(paths.empty()) + if (paths.empty()) { return; } - //Get the vertex data and store it in the paths. - for(auto& path : paths) + // Get the vertex data and store it in the paths. + for (auto& path : paths) { path.converted = path.getVertexData(); vertices_to_paths.emplace(path.vertices, &path); } - //If necessary, check polylines to see if they are actually polygons. - if(detect_loops) + // If necessary, check polylines to see if they are actually polygons. + if (detect_loops) { - for(auto& path : paths) + for (auto& path : paths) { - if(!path.is_closed) + if (! path.is_closed) { - //If we want to detect chains, first check if some of the polylines are secretly polygons. - path.is_closed = isLoopingPolyline(path); //If it is, we'll set the seam position correctly later. + // If we want to detect chains, first check if some of the polylines are secretly polygons. + path.is_closed = isLoopingPolyline(path); // If it is, we'll set the seam position correctly later. } } } - - //Add all vertices to a bucket grid so that we can find nearby endpoints quickly. + + // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); - for(const auto& [i, path]: paths | ranges::views::enumerate) + for (const auto& [i, path] : paths | ranges::views::enumerate) { if (path.converted->empty()) { continue; } - if(path.is_closed) + if (path.is_closed) { - for(const Point& point : *path.converted) + for (const Point& point : *path.converted) { - line_bucket_grid.insert(point, i); //Store by index so that we can also mark them down in the `picked` vector. + line_bucket_grid.insert(point, i); // Store by index so that we can also mark them down in the `picked` vector. } } - else //For polylines, only insert the endpoints. Those are the only places we can start from so the only relevant vertices to be near to. + else // For polylines, only insert the endpoints. Those are the only places we can start from so the only relevant vertices to be near to. { line_bucket_grid.insert(path.converted->front(), i); line_bucket_grid.insert(path.converted->back(), i); } } - //For some Z seam types the start position can be pre-computed. - //This is faster since we don't need to re-compute the start position at each step then. - const bool precompute_start = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; - if(precompute_start) + // For some Z seam types the start position can be pre-computed. + // This is faster since we don't need to re-compute the start position at each step then. + precompute_start &= seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; + if (precompute_start) { - for(auto& path : paths) + for (auto& path : paths) { - if(!path.is_closed || path.converted->empty()) + if (! path.is_closed || path.converted->empty()) { - continue; //Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. } path.start_vertex = findStartLocation(path, seam_config.pos); } } - std::vector optimized_order; //To store our result in. At the end we'll std::swap. + std::vector optimized_order; // To store our result in. At the end we'll std::swap. if (order_requirements->empty()) { @@ -213,9 +222,9 @@ class PathOrderOptimizer } - if(reverse_direction && order_requirements->empty()) + if (reverse_direction && order_requirements->empty()) { - std::vector reversed = reverseOrderPaths(optimized_order); //Reverse-insert the optimized order, to invert the ordering. + std::vector reversed = reverseOrderPaths(optimized_order); // Reverse-insert the optimized order, to invert the ordering. std::swap(reversed, paths); } else @@ -225,6 +234,7 @@ class PathOrderOptimizer combing_grid.reset(); } + protected: /*! * If \ref detect_loops is enabled, endpoints of polylines that are closer @@ -276,18 +286,24 @@ class PathOrderOptimizer std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { - std::vector optimized_order; //To store our result in. + std::vector optimized_order; // To store our result in. Point current_position = start_point; - std::unordered_map picked(paths.size()); //Fixed size boolean flag for whether each path is already in the optimized vector. + std::unordered_map picked(paths.size()); // Fixed size boolean flag for whether each path is already in the optimized vector. - auto isPicked = [&picked](OrderablePath* c) { return picked[c]; }; - auto notPicked = [&picked](OrderablePath* c) { return !picked[c]; }; + auto isPicked = [&picked](OrderablePath* c) + { + return picked[c]; + }; + auto notPicked = [&picked](OrderablePath* c) + { + return ! picked[c]; + }; - while(optimized_order.size() < paths.size()) + while (optimized_order.size() < paths.size()) { - //Use bucket grid to find paths within snap_radius + // Use bucket grid to find paths within snap_radius std::vector nearby_candidates; for (const auto i : line_bucket_grid.getNearbyVals(current_position, snap_radius)) { @@ -296,14 +312,14 @@ class PathOrderOptimizer std::vector available_candidates; available_candidates.reserve(nearby_candidates.size()); - for(auto candidate : nearby_candidates | ranges::views::filter(notPicked)) + for (auto candidate : nearby_candidates | ranges::views::filter(notPicked)) { available_candidates.push_back(candidate); } - if(available_candidates.empty()) // We need to broaden our search through all candidates + if (available_candidates.empty()) // We need to broaden our search through all candidates { - for(auto path : paths | ranges::views::addressof | ranges::views::filter(notPicked)) + for (auto path : paths | ranges::views::addressof | ranges::views::filter(notPicked)) { available_candidates.push_back(path); } @@ -315,15 +331,15 @@ class PathOrderOptimizer optimized_order.push_back(*best_path); picked[best_path] = true; - if(!best_path->converted->empty()) //If all paths were empty, the best path is still empty. We don't upate the current position then. + if (! best_path->converted->empty()) // If all paths were empty, the best path is still empty. We don't upate the current position then. { - if(best_path->is_closed) + if (best_path->is_closed) { - current_position = (*best_path->converted)[best_path->start_vertex]; //We end where we started. + current_position = (*best_path->converted)[best_path->start_vertex]; // We end where we started. } else { - //Pick the other end from where we started. + // Pick the other end from where we started. current_position = best_path->start_vertex == 0 ? best_path->converted->back() : best_path->converted->front(); } } @@ -332,9 +348,10 @@ class PathOrderOptimizer return optimized_order; } - std::vector getOptimizerOrderWithConstraints(SparsePointGridInclusive line_bucket_grid, size_t snap_radius, const std::unordered_multimap& order_requirements) + std::vector + getOptimizerOrderWithConstraints(SparsePointGridInclusive line_bucket_grid, size_t snap_radius, const std::unordered_multimap& order_requirements) { - std::vector optimized_order; //To store our result in. + std::vector optimized_order; // To store our result in. // initialize the roots set with all possible nodes std::unordered_set roots; @@ -358,8 +375,8 @@ class PathOrderOptimizer std::unordered_set visited; Point current_position = start_point; - std::function(const Path, const std::unordered_multimap&)> get_neighbours = - [current_position, this](const Path current_node, const std::unordered_multimap& graph) + std::function(const Path, const std::unordered_multimap&)> get_neighbours + = [current_position, this](const Path current_node, const std::unordered_multimap& graph) { std::vector order; // Output order to traverse neighbors @@ -382,13 +399,13 @@ class PathOrderOptimizer // update local_current_position auto path = vertices_to_paths[best_candidate]; - if(path->is_closed) + if (path->is_closed) { - local_current_position = (*path->converted)[path->start_vertex]; //We end where we started. + local_current_position = (*path->converted)[path->start_vertex]; // We end where we started. } else { - //Pick the other end from where we started. + // Pick the other end from where we started. local_current_position = path->start_vertex == 0 ? path->converted->back() : path->converted->front(); } } @@ -396,34 +413,33 @@ class PathOrderOptimizer return order; }; - const std::function handle_node = - [¤t_position, &optimized_order, this] - (const Path current_node, const std::nullptr_t _state) + const std::function handle_node + = [¤t_position, &optimized_order, this](const Path current_node, const std::nullptr_t _state) + { + // We should make map from node <-> path for this stuff + for (auto& path : paths) { - // We should make map from node <-> path for this stuff - for (auto& path : paths) + if (path.vertices == current_node) { - if (path.vertices == current_node) + if (path.is_closed) + { + current_position = (*path.converted)[path.start_vertex]; // We end where we started. + } + else { - if(path.is_closed) - { - current_position = (*path.converted)[path.start_vertex]; //We end where we started. - } - else - { - //Pick the other end from where we started. - current_position = path.start_vertex == 0 ? path.converted->back() : path.converted->front(); - } - - // Add to optimized order - optimized_order.push_back(path); - - break; + // Pick the other end from where we started. + current_position = path.start_vertex == 0 ? path.converted->back() : path.converted->front(); } + + // Add to optimized order + optimized_order.push_back(path); + + break; } + } - return nullptr; - }; + return nullptr; + }; if (group_outer_walls) { @@ -482,7 +498,7 @@ class PathOrderOptimizer } else { - while (!roots.empty()) + while (! roots.empty()) { Path root = findClosestPathVertices(current_position, roots); roots.erase(root); @@ -497,13 +513,13 @@ class PathOrderOptimizer std::vector reverseOrderPaths(std::vector pathsOrderPaths) { std::vector reversed; - //Don't replace with swap, assign or insert. They require functions that we can't implement for all template arguments for Path. + // Don't replace with swap, assign or insert. They require functions that we can't implement for all template arguments for Path. reversed.reserve(pathsOrderPaths.size()); - for(auto& path: pathsOrderPaths | ranges::views::reverse) + for (auto& path : pathsOrderPaths | ranges::views::reverse) { reversed.push_back(path); - reversed.back().backwards = !reversed.back().backwards; - if(!reversed.back().is_closed) + reversed.back().backwards = ! reversed.back().backwards; + if (! reversed.back().is_closed) { reversed.back().start_vertex = reversed.back().converted->size() - 1 - reversed.back().start_vertex; } @@ -530,33 +546,35 @@ class PathOrderOptimizer coord_t best_distance2 = std::numeric_limits::max(); OrderablePath* best_candidate = 0; - for(OrderablePath* path : candidate_paths) + for (OrderablePath* path : candidate_paths) { - if(path->converted->empty()) //No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (path->converted->empty()) // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. { - if(best_distance2 == std::numeric_limits::max()) + if (best_distance2 == std::numeric_limits::max()) { best_candidate = path; } continue; } - const bool precompute_start = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; - if(!path->is_closed || !precompute_start) //Find the start location unless we've already precomputed it. + const bool precompute_start + = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; + if (! path->is_closed || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex = findStartLocation(*path, start_position); - if(!path->is_closed) //Open polylines start at vertex 0 or vertex N-1. Indicate that they should be reversed if they start at N-1. + if (! path->is_closed) // Open polylines start at vertex 0 or vertex N-1. Indicate that they should be reversed if they start at N-1. { path->backwards = path->start_vertex > 0; } } const Point candidate_position = (*path->converted)[path->start_vertex]; coord_t distance2 = getDirectDistance(start_position, candidate_position); - if(distance2 < best_distance2 && combing_boundary) //If direct distance is longer than best combing distance, the combing distance can never be better, so only compute combing if necessary. + if (distance2 < best_distance2 + && combing_boundary) // If direct distance is longer than best combing distance, the combing distance can never be better, so only compute combing if necessary. { distance2 = getCombingDistance(start_position, candidate_position); } - if(distance2 < best_distance2) //Closer than the best candidate so far. + if (distance2 < best_distance2) // Closer than the best candidate so far. { best_candidate = path; best_distance2 = distance2; @@ -589,47 +607,59 @@ class PathOrderOptimizer * applicable. * \param is_closed Whether the polygon is closed (a polygon) or not * (a polyline). If the path is not closed, it will choose between the two - * endpoints rather than + * endpoints rather than * \return An index to a vertex in that path where printing must start. */ size_t findStartLocation(const OrderablePath& path, const Point& target_pos) { - if(!path.is_closed) + if (! path.is_closed) { - //For polylines, the seam settings are not applicable. Simply choose the position closest to target_pos then. - const coord_t back_distance = (combing_boundary == nullptr) - ? getDirectDistance(path.converted->back(), target_pos) - : getCombingDistance(path.converted->back(), target_pos); - if(back_distance < getDirectDistance(path.converted->front(), target_pos) || (combing_boundary && back_distance < getCombingDistance(path.converted->front(), target_pos))) //Lazy or: Only compute combing distance if direct distance is closer. + // For polylines, the seam settings are not applicable. Simply choose the position closest to target_pos then. + const coord_t back_distance + = (combing_boundary == nullptr) ? getDirectDistance(path.converted->back(), target_pos) : getCombingDistance(path.converted->back(), target_pos); + if (back_distance < getDirectDistance(path.converted->front(), target_pos) + || (combing_boundary + && back_distance < getCombingDistance(path.converted->front(), target_pos))) // Lazy or: Only compute combing distance if direct distance is closer. { - return path.converted->size() - 1; //Back end is closer. + return path.converted->size() - 1; // Back end is closer. } else { - return 0; //Front end is closer. + return 0; // Front end is closer. } } - //Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. + // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. - if(seam_config.type == EZSeamType::RANDOM) + if (seam_config.type == EZSeamType::RANDOM) { size_t vert = getRandomPointInPolygon(*path.converted); return vert; } + // Precompute segments lengths because we are going to need them multiple times + std::vector segments_sizes(path.converted->size()); + coord_t total_length = 0; + for (const auto& [i, here] : **path.converted | ranges::views::enumerate) + { + const Point& next = (*path.converted)[(i + 1) % path.converted->size()]; + const coord_t segment_size = vSize(next - here); + segments_sizes[i] = segment_size; + total_length += segment_size; + } + size_t best_i; float best_score = std::numeric_limits::infinity(); - for(const auto& [i, here]: **path.converted | ranges::views::enumerate) + for (const auto& [i, here] : **path.converted | ranges::views::drop_last(1) | ranges::views::enumerate) { - //For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - //For SHARPEST_CORNER, use a fixed starting score of 0. - const coord_t distance = (combing_boundary == nullptr) - ? getDirectDistance(here, target_pos) - : getCombingDistance(here, target_pos); - const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) ? MM2INT(10) : vSize2(here - target_pos); - - float corner_angle = cornerAngle(path, i); + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + // For SHARPEST_CORNER, use a fixed starting score of 0. + const coord_t distance = (combing_boundary == nullptr) ? getDirectDistance(here, target_pos) : getCombingDistance(here, target_pos); + const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + ? MM2INT(10) + : vSize2(here - target_pos); + + float corner_angle = cornerAngle(path, i, segments_sizes, total_length); // angles < 0 are concave (left turning) // angles > 0 are convex (right turning) @@ -646,34 +676,30 @@ class PathOrderOptimizer // so the user has some control over where the seam will lie. // the divisor here may need adjusting to obtain the best results (TBD) - corner_shift = score_distance / 10; + corner_shift = score_distance / 50; } float score = score_distance; - switch(seam_config.corner_pref) + switch (seam_config.corner_pref) { default: case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - if(corner_angle < 0) // Indeed a concave corner? Give it some advantage over other corners. More advantage for sharper corners. - { - score -= (-corner_angle + 1.0) * corner_shift; - } + // Give advantage to concave corners. More advantage for sharper corners. + score += corner_angle * corner_shift; break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - if(corner_angle > 0) // Indeed a convex corner? - { - score -= (corner_angle + 1.0) * corner_shift; - } + // Give advantage to convex corners. More advantage for sharper corners. + score -= corner_angle * corner_shift; break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - score -= std::abs(corner_angle) * corner_shift; //Still give sharper corners more advantage. + score -= std::abs(corner_angle) * corner_shift; // Still give sharper corners more advantage. break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: //Give sharper corners some advantage, but sharper concave corners even more. + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: // Give sharper corners some advantage, but sharper concave corners even more. { float score_corner = std::abs(corner_angle) * corner_shift; - if(corner_angle < 0) //Concave corner. + if (corner_angle < 0) // Concave corner. { score_corner *= 2; } @@ -682,8 +708,8 @@ class PathOrderOptimizer } } - constexpr float EPSILON = 25.0; - if(std::abs(best_score - score) <= EPSILON) + constexpr float EPSILON = 5.0; + if (std::abs(best_score - score) <= EPSILON) { // add breaker for two candidate starting location with similar score // if we don't do this then we (can) get an un-even seam @@ -691,13 +717,13 @@ class PathOrderOptimizer // if x-coord for both points are equal then break ties by // favouring points with lower y-coord const Point& best_point = (*path.converted)[best_i]; - if(std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) + if (std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) { best_score = std::min(best_score, score); best_i = i; } } - else if(score < best_score) + else if (score < best_score) { best_i = i; best_score = score; @@ -708,82 +734,81 @@ class PathOrderOptimizer } /*! - * Some models have very sharp corners, but also have a high resolution. If a sharp corner - * consists of many points each point individual might have a shallow corner, but the - * collective angle of all nearby points is greater. To counter this the cornerAngle is - * calculated from all points within angle_query_distance of the query point. Angles closer - * to the current point are weighted more towards the total angle then points further away. - * The formula for the angle weight is: 1 - (distance_to_query / angle_query_distance)^fall_off_strength - * \param path The vertex data of a path - * \param i index of the query point - * \param angle_query_distance query range (default to 0.1mm) - * \param fall_off_strength fall of strength of the angle weight - * \return sum of angles of all points p in range i - angle_query_distance < p < i + angle_query_distance - */ - float cornerAngle(const OrderablePath& path, int i, const coord_t angle_query_distance = 100, const float fall_off_strength = 0.5) + * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point + * is computed by travelling on the path and stopping when the distance has been reached, For example: + * |------|---------|------|--------------*---| + * H A B C N D + * In this case, H is the start point of the path and ABCD are the actual following points of the path. + * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. + * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path + * at a given physical distance. + * \param path The vertex data of a path + * \param here The starting point index + * \param distance The distance we want to travel on the path, which may be positive to go forward + * or negative to go backward + * \param segments_sizes The pre-computed sizes of the segments + * \return The position of the path a the given distance from the reference point + */ + static Point findNeighbourPoint(const OrderablePath& path, int here, coord_t distance, const std::vector& segments_sizes) { - // If the edge length becomes too small we cannot accurately calculate the angle - // define a minimum edge length, so we don't get deviant values in the angle calculations - constexpr coord_t min_edge_length = 10; - constexpr coord_t min_edge_length2 = min_edge_length * min_edge_length; - - const int offset_index = i % path.converted->size(); - Point here = (*path.converted)[offset_index]; - - const std::function find_neighbour_point = [&offset_index, &path](const int direction, const Point& here) + const int direction = distance > 0 ? 1 : -1; + const int size_delta = distance > 0 ? -1 : 0; + distance = std::abs(distance); + + // Travel on the path until we reach the distance + int actual_delta = 0; + coord_t travelled_distance = 0; + coord_t segment_size = 0; + while (travelled_distance < distance) { - int offset_index_ = offset_index; - Point neighbour; - do - { - offset_index_ = (offset_index_ + path.converted->size() + direction) % path.converted->size(); - neighbour = (*path.converted)[offset_index_]; - } - while (vSize2(here - neighbour) < min_edge_length2 && offset_index_ != offset_index); // find previous point that is at least min_edge_length units away from here - return neighbour; - }; + actual_delta += direction; + segment_size = segments_sizes[(here + actual_delta + size_delta + path.converted->size()) % path.converted->size()]; + travelled_distance += segment_size; + } - const std::function iterate_to_previous_point = [&find_neighbour_point](Point& previous_, Point& here_, Point& next_) - { - const auto dist = vSize(here_ - next_); - next_ = here_; - here_ = previous_; - previous_ = find_neighbour_point(-1, here_); - return dist; - }; - Point previous = find_neighbour_point(-1, here); + const Point& next_pos = (*path.converted)[(here + actual_delta + path.converted->size()) % path.converted->size()]; - const std::function iterate_to_next_point = [&find_neighbour_point](Point& previous_, Point& here_, Point& next_) + if (travelled_distance > distance) [[likely]] { - const auto dist = vSize(here_ - previous_); - previous_ = here_; - here_ = next_; - next_ = find_neighbour_point(1, here_); - return dist; - }; - Point next = find_neighbour_point(1, here); - - float corner_angle = LinearAlg2D::getAngleLeft(previous, here, next) - M_PI; - - for (const auto& iterate_func : {iterate_to_previous_point, iterate_to_next_point}) + // We have overtaken the required distance, go backward on the last segment + int prev = (here + actual_delta - direction + path.converted->size()) % path.converted->size(); + const Point& prev_pos = (*path.converted)[prev]; + + const Point vector = next_pos - prev_pos; + const Point unit_vector = (vector * 1000) / segment_size; + const Point vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); + return prev_pos + vector_delta / 1000; + } + else { - Point next_ = next; - Point here_ = here; - Point previous_ = previous; - for - ( - coord_t distance_to_query = iterate_func(previous_, here_, next_); - distance_to_query < angle_query_distance && here_ != here; - distance_to_query += iterate_func(previous_, here_, next_) - ) - { - // angles further away from the query point are weighted less - const float angle_weight = 1.0 - pow(distance_to_query / angle_query_distance, fall_off_strength); - corner_angle += (LinearAlg2D::getAngleLeft(previous_, here_, next_) - M_PI) * angle_weight; - } + // Luckily, the required distance stops exactly on an existing point + return next_pos; } + } + + /*! + * Some models have very sharp corners, but also have a high resolution. If a sharp corner + * consists of many points each point individual might have a shallow corner, but the + * collective angle of all nearby points is greater. To counter this the cornerAngle is + * calculated from two points within angle_query_distance of the query point, no matter + * what segment this leads us to + * \param path The vertex data of a path + * \param i index of the query point + * \param segments_sizes The pre-computed sizes of the segments + * \param total_length The path total length + * \param angle_query_distance query range (default to 1mm) + * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] + */ + static float cornerAngle(const OrderablePath& path, int i, const std::vector& segments_sizes, coord_t total_length, const coord_t angle_query_distance = 1000) + { + const coord_t bounded_distance = std::min(angle_query_distance, total_length / 2); + const Point& here = (*path.converted)[i]; + const Point next = findNeighbourPoint(path, i, bounded_distance, segments_sizes); + const Point previous = findNeighbourPoint(path, i, -bounded_distance, segments_sizes); + + float angle = LinearAlg2D::getAngleLeft(previous, here, next) - M_PI; - return corner_angle / M_PI; // Limit angle between -1 and 1. + return angle / M_PI; } /*! @@ -810,11 +835,11 @@ class PathOrderOptimizer */ coord_t getCombingDistance(const Point& a, const Point& b) { - if(!PolygonUtils::polygonCollidesWithLineSegment(*combing_boundary, a, b)) + if (! PolygonUtils::polygonCollidesWithLineSegment(*combing_boundary, a, b)) { - return getDirectDistance(a, b); //No collision with any line. Just compute the direct distance then. + return getDirectDistance(a, b); // No collision with any line. Just compute the direct distance then. } - if(paths.size() > 100) + if (paths.size() > 100) { /* If we have many paths to optimize the order for, this combing calculation can become very expensive. Instead, penalize travels @@ -822,13 +847,13 @@ class PathOrderOptimizer return getDirectDistance(a, b) * 5; } - if(combing_grid == nullptr) + if (combing_grid == nullptr) { - constexpr coord_t grid_size = 2000; //2mm grid cells. Smaller will use more memory, but reduce chance of unnecessary collision checks. + constexpr coord_t grid_size = 2000; // 2mm grid cells. Smaller will use more memory, but reduce chance of unnecessary collision checks. combing_grid = PolygonUtils::createLocToLineGrid(*combing_boundary, grid_size); } - CombPath comb_path; //Output variable. + CombPath comb_path; // Output variable. constexpr coord_t rounding_error = -25; constexpr coord_t tiny_travel_threshold = 0; constexpr bool fail_on_unavoidable_obstacles = false; @@ -836,12 +861,12 @@ class PathOrderOptimizer coord_t sum = 0; Point last_point = a; - for(const Point& point : comb_path) + for (const Point& point : comb_path) { sum += vSize(point - last_point); last_point = point; } - return sum * sum; //Squared distance, for fair comparison with direct distance. + return sum * sum; // Squared distance, for fair comparison with direct distance. } /*! @@ -856,7 +881,7 @@ class PathOrderOptimizer bool isLoopingPolyline(const OrderablePath& path) { - if(path.converted->empty()) + if (path.converted->empty()) { return false; } @@ -867,6 +892,6 @@ class PathOrderOptimizer template const std::unordered_multimap PathOrderOptimizer::no_order_requirements; -} //namespace cura +} // namespace cura -#endif //PATHORDEROPTIMIZER_H +#endif // PATHORDEROPTIMIZER_H diff --git a/include/Preheat.h b/include/Preheat.h index 800ddc4413..dba5d72c08 100644 --- a/include/Preheat.h +++ b/include/Preheat.h @@ -1,24 +1,23 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef PREHEAT_H #define PREHEAT_H -#include -#include // max - #include "settings/types/Duration.h" +#include "settings/types/Ratio.h" #include "settings/types/Temperature.h" -namespace cura -{ +#include // max +#include -class Ratio; +namespace cura +{ /*! * Class for computing heatup and cooldown times used for computing the time the printer needs to heat up to a printing temperature. */ -class Preheat +class Preheat { public: /*! @@ -44,7 +43,7 @@ class Preheat /*! * Get the optimal temperature corresponding to a given average flow, * or the initial layer temperature. - * + * * \param extruder The extruder train * \param flow The flow for which to get the optimal temperature * \param is_initial_layer Whether the initial layer temperature should be returned instead of flow-based temperature @@ -54,12 +53,12 @@ class Preheat /*! * Decide when to start warming up again after starting to cool down towards \p temp_mid. - * Two cases are considered: + * Two cases are considered: * the case where the standby temperature is reached \__/ . * and the case where it isn't \/ . - * + * * \warning it is assumed that \p temp_mid is lower than both \p temp_start and \p temp_end. If not somewhat weird results may follow. - * + * // ,temp_end // / . // ,temp_start / . @@ -78,12 +77,12 @@ class Preheat /*! * Decide when to start cooling down again after starting to warm up towards the \p temp_mid - * Two cases are considered: + * Two cases are considered: * the case where the temperature is reached /"""\ . * and the case where it isn't /\ . - * + * * \warning it is assumed that \p temp_mid is higher than both \p temp_start and \p temp_end. If not somewhat weird results may follow. - * + * // _> temp_mid // /""""""""\ . // / \ . @@ -111,6 +110,6 @@ class Preheat Duration getTimeToGoFromTempToTemp(const size_t extruder, const Temperature& temp_before, const Temperature& temp_after, const bool during_printing); }; -} // namespace cura +} // namespace cura #endif // PREHEAT_H \ No newline at end of file diff --git a/include/PrimeTower.h b/include/PrimeTower.h index c0e50685bf..a493cfb3be 100644 --- a/include/PrimeTower.h +++ b/include/PrimeTower.h @@ -1,15 +1,17 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef PRIME_TOWER_H #define PRIME_TOWER_H #include +#include "settings/types/LayerIndex.h" #include "utils/polygon.h" // Polygons #include "utils/polygonUtils.h" -namespace cura + +namespace cura { class SliceDataStorage; @@ -24,12 +26,10 @@ class LayerPlan; class PrimeTower { private: - struct ExtrusionMoves - { - Polygons polygons; - Polygons lines; - }; - unsigned int extruder_count; //!< Number of extruders + using MovesByExtruder = std::vector; + using MovesByLayer = std::vector; + + size_t extruder_count; //!< Number of extruders bool wipe_from_middle; //!< Whether to wipe on the inside of the hollow prime tower Point middle; //!< The middle of the prime tower @@ -39,14 +39,16 @@ class PrimeTower std::vector prime_tower_start_locations; //!< The differernt locations where to pre-wipe the active nozzle const unsigned int number_of_prime_tower_start_locations = 21; //!< The required size of \ref PrimeTower::wipe_locations - std::vector pattern_per_extruder; //!< For each extruder the pattern to print on all layers of the prime tower. - std::vector pattern_per_extruder_layer0; //!< For each extruder the pattern to print on the first layer + MovesByExtruder prime_moves; //!< For each extruder, the moves to be processed for actual priming. + MovesByLayer base_extra_moves; //!< For each layer and each extruder, the extra moves to be processed for better adhesion/strength + + Polygons outer_poly; //!< The outline of the outermost prime tower. + std::vector outer_poly_base; //!< The outline of the layers having extra width for the base public: bool enabled; //!< Whether the prime tower is enabled. bool would_have_actual_tower; //!< Whether there is an actual tower. bool multiple_extruders_on_first_layer; //!< Whether multiple extruders are allowed on the first layer of the prime tower (e.g. when a raft is there) - Polygons outer_poly; //!< The outline of the outermost prime tower. /* * In which order, from outside to inside, will we be printing the prime @@ -55,7 +57,7 @@ class PrimeTower * This is the spatial order from outside to inside. This is NOT the actual * order in time in which they are printed. */ - std::vector extruder_order; + std::vector extruder_order; /*! * \brief Creates a prime tower instance that will determine where and how @@ -72,7 +74,7 @@ class PrimeTower /*! * Generate the prime tower area to be used on each layer - * + * * Fills \ref PrimeTower::inner_poly and sets \ref PrimeTower::middle */ void generateGroundpoly(); @@ -84,7 +86,7 @@ class PrimeTower /*! * Add path plans for the prime tower to the \p gcode_layer - * + * * \param storage where to get settings from; where to get the maximum height of the prime tower from * \param[in,out] gcode_layer Where to get the current extruder from; where to store the generated layer paths * \param prev_extruder The previous extruder with which paths were planned; from which extruder a switch was made @@ -100,11 +102,23 @@ class PrimeTower */ void subtractFromSupport(SliceDataStorage& storage); -private: + /*! + * Get the outer polygon for the given layer, which may be the priming polygon only, or a larger polygon for layers with a base + * + * \param[in] layer_nr The index of the layer + * \return The outer polygon for the prime tower at the given layer + */ + const Polygons& getOuterPoly(const LayerIndex& layer_nr) const; /*! - * \see WipeTower::generatePaths - * + * Get the outer polygon for the very first layer, which may be the priming polygon only, or a larger polygon if there is a base + */ + const Polygons& getGroundPoly() const; + +private: + /*! + * \see PrimeTower::generatePaths + * * Generate the extrude paths for each extruder on even and odd layers * Fill the ground poly with dense infill. */ @@ -138,8 +152,6 @@ class PrimeTower }; - - -}//namespace cura +} // namespace cura #endif // PRIME_TOWER_H diff --git a/include/SkeletalTrapezoidationEdge.h b/include/SkeletalTrapezoidationEdge.h index aa897d830f..60f464deb5 100644 --- a/include/SkeletalTrapezoidationEdge.h +++ b/include/SkeletalTrapezoidationEdge.h @@ -4,19 +4,24 @@ #ifndef SKELETAL_TRAPEZOIDATION_EDGE_H #define SKELETAL_TRAPEZOIDATION_EDGE_H -#include // smart pointers +#include "utils/ExtrusionJunction.h" + #include +#include // smart pointers #include -#include "utils/ExtrusionJunction.h" - namespace cura { class SkeletalTrapezoidationEdge { private: - enum class Central : int { UNKNOWN = -1, NO = 0, YES = 1}; + enum class Central : int + { + UNKNOWN = -1, + NO = 0, + YES = 1 + }; public: /*! @@ -28,9 +33,11 @@ class SkeletalTrapezoidationEdge int lower_bead_count; coord_t feature_radius; // The feature radius at which this transition is placed TransitionMiddle(coord_t pos, int lower_bead_count, coord_t feature_radius) - : pos(pos), lower_bead_count(lower_bead_count) + : pos(pos) + , lower_bead_count(lower_bead_count) , feature_radius(feature_radius) - {} + { + } }; /*! @@ -42,8 +49,11 @@ class SkeletalTrapezoidationEdge int lower_bead_count; bool is_lower_end; // Whether this is the ed of the transition with lower bead count TransitionEnd(coord_t pos, int lower_bead_count, bool is_lower_end) - : pos(pos), lower_bead_count(lower_bead_count), is_lower_end(is_lower_end) - {} + : pos(pos) + , lower_bead_count(lower_bead_count) + , is_lower_end(is_lower_end) + { + } }; enum class EdgeType : int @@ -55,12 +65,14 @@ class SkeletalTrapezoidationEdge EdgeType type; SkeletalTrapezoidationEdge() - : SkeletalTrapezoidationEdge(EdgeType::NORMAL) - {} + : SkeletalTrapezoidationEdge(EdgeType::NORMAL) + { + } SkeletalTrapezoidationEdge(const EdgeType& type) - : type(type) - , is_central(Central::UNKNOWN) - {} + : type(type) + , is_central(Central::UNKNOWN) + { + } bool isCentral() const { @@ -80,7 +92,7 @@ class SkeletalTrapezoidationEdge { return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty()); } - void setTransitions(std::shared_ptr> storage) + void setTransitions(std::shared_ptr>& storage) { transitions = storage; } @@ -93,7 +105,7 @@ class SkeletalTrapezoidationEdge { return transition_ends.use_count() > 0 && (ignore_empty || ! transition_ends.lock()->empty()); } - void setTransitionEnds(std::shared_ptr> storage) + void setTransitionEnds(std::shared_ptr>& storage) { transition_ends = storage; } @@ -106,7 +118,7 @@ class SkeletalTrapezoidationEdge { return extrusion_junctions.use_count() > 0 && (ignore_empty || ! extrusion_junctions.lock()->empty()); } - void setExtrusionJunctions(std::shared_ptr storage) + void setExtrusionJunctions(std::shared_ptr& storage) { extrusion_junctions = storage; } diff --git a/include/SkeletalTrapezoidationJoint.h b/include/SkeletalTrapezoidationJoint.h index 603b9b452f..e8c5ad42a7 100644 --- a/include/SkeletalTrapezoidationJoint.h +++ b/include/SkeletalTrapezoidationJoint.h @@ -1,20 +1,21 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef SKELETAL_TRAPEZOIDATION_JOINT_H #define SKELETAL_TRAPEZOIDATION_JOINT_H -#include // smart pointers - #include "BeadingStrategy/BeadingStrategy.h" #include "utils/IntPoint.h" +#include // smart pointers + namespace cura { class SkeletalTrapezoidationJoint { using Beading = BeadingStrategy::Beading; + public: struct BeadingPropagation { @@ -27,23 +28,26 @@ class SkeletalTrapezoidationJoint , dist_to_bottom_source(0) , dist_from_top_source(0) , is_upward_propagated_only(false) - {} + { + } }; coord_t distance_to_boundary; coord_t bead_count; - float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead at the higher transition. + float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead + //! at the higher transition. SkeletalTrapezoidationJoint() - : distance_to_boundary(-1) - , bead_count(-1) - , transition_ratio(0) - {} + : distance_to_boundary(-1) + , bead_count(-1) + , transition_ratio(0) + { + } bool hasBeading() const { return beading.use_count() > 0; } - void setBeading(std::shared_ptr storage) + void setBeading(std::shared_ptr& storage) { beading = storage; } @@ -53,7 +57,6 @@ class SkeletalTrapezoidationJoint } private: - std::weak_ptr beading; }; diff --git a/include/SkirtBrim.h b/include/SkirtBrim.h index acce6bb0e6..896e4aac45 100644 --- a/include/SkirtBrim.h +++ b/include/SkirtBrim.h @@ -1,17 +1,17 @@ -//Copyright (c) 2023 UltiMaker -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef SKIRT_BRIM_H #define SKIRT_BRIM_H -#include "utils/Coord_t.h" +#include + #include "ExtruderTrain.h" #include "settings/EnumSettings.h" #include "sliceDataStorage.h" +#include "utils/Coord_t.h" -#include - -namespace cura +namespace cura { class Polygons; @@ -27,26 +27,25 @@ class SkirtBrim */ struct Offset { - Offset - ( + Offset( const std::variant& reference_outline_or_index, const bool external_only, const coord_t offset_value, const coord_t total_offset, const size_t inset_idx, const int extruder_nr, - const bool is_last - ) : - reference_outline_or_index(reference_outline_or_index), - external_only(external_only), - offset_value(offset_value), - total_offset(total_offset), - inset_idx(inset_idx), - extruder_nr(extruder_nr), - is_last(is_last) - {} - - std::variant reference_outline_or_index; + const bool is_last) + : reference_outline_or_index(reference_outline_or_index) + , external_only(external_only) + , offset_value(offset_value) + , total_offset(total_offset) + , inset_idx(inset_idx) + , extruder_nr(extruder_nr) + , is_last(is_last) + { + } + + std::variant reference_outline_or_index; bool external_only; //!< Wether to only offset outward from the reference polygons coord_t offset_value; //!< Distance by which to offset from the reference coord_t total_offset; //!< Total distance from the model @@ -58,15 +57,12 @@ class SkirtBrim /*! * Defines an order on offsets (potentially from different extruders) based on how far the offset is from the original outline. */ - static inline const auto OffsetSorter - { - [](const Offset& a, const Offset& b) - { - // Use extruder_nr in case both extruders have the same offset settings. - return a.total_offset != b.total_offset ? a.total_offset < b.total_offset : a.extruder_nr < b.extruder_nr; - } - }; - + static inline const auto OffsetSorter{ [](const Offset& a, const Offset& b) + { + // Use extruder_nr in case both extruders have the same offset settings. + return a.total_offset != b.total_offset ? a.total_offset < b.total_offset : a.extruder_nr < b.extruder_nr; + } }; + SliceDataStorage& storage; //!< Where to retrieve settings and store brim lines. const EPlatformAdhesion adhesion_type; //!< Whether we are generating brim, skirt, or raft const bool has_ooze_shield; //!< Whether the meshgroup has an ooze shield @@ -85,17 +81,17 @@ class SkirtBrim public: /*! * Precomputes some values used in several functions when calling \ref generate - * + * * \param storage Storage containing the parts at the first layer. */ SkirtBrim(SliceDataStorage& storage); /*! * Generate skirt or brim (depending on parameters). - * + * * When \p distance > 0 and \p count == 1 a skirt is generated, which has * slightly different configuration. Otherwise, a brim is generated. - * + * * \param storage Storage containing the parts at the first layer. * \param first_layer_outline The outline to generate skirt or brim around. * \param distance The distance of the first outset from the parts at the first @@ -108,7 +104,7 @@ class SkirtBrim private: /*! * Plan the offsets which we will be going to perform and put them in the right order. - * + * * In order for brims of different materials to grow toward the middle, * we need to perform the offsets alternatingly. * We therefore first create all planned Offset objects, @@ -118,18 +114,9 @@ class SkirtBrim */ std::vector generateBrimOffsetPlan(std::vector& starting_outlines); - /*! - * In case that the models have skirt 'adhesion', but the prime tower has a brim, the covered areas are different. - * - * Since the output of this function will need to be handled differently than the rest of the adhesion lines, have a separate function. - * Specifically, for skirt an additional 'approximate convex hull' is applied to the initial 'covered area', which is detrimental to brim. - * \return An ordered list of offsets of the prime-tower to perform in the order in which they are to be performed. - */ - std::vector generatePrimeTowerBrimForSkirtAdhesionOffsetPlan(); - /*! * Generate the primary skirt/brim of the one skirt_brim_extruder or of all extruders simultaneously. - * + * * \param[in,out] all_brim_offsets The offsets to perform. Adjusted when the minimal length constraint isn't met yet. * \param[in,out] covered_area The area of the first layer covered by model or generated brim lines. * \param[in,out] allowed_areas_per_extruder The difference between the machine bed area (offsetted by the nozzle offset) and the covered_area. @@ -139,9 +126,9 @@ class SkirtBrim /*! * Generate the brim inside the ooze shield and draft shield - * + * * \warning Adjusts brim_covered_area - * + * * \param storage Storage containing the parts at the first layer. * \param[in,out] brim_covered_area The area that was covered with brim before (in) and after (out) adding the shield brims * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area @@ -163,10 +150,10 @@ class SkirtBrim /*! * The disallowed area around the internal holes of parts with other parts inside which would get an external brim. - * + * * In order to prevent the external_only brim of a part inside another part to overlap with the internal holes of the outer part, * we generate a disallowed area around those internal hole polygons. - * + * * \param outline The full layer outlines * \param extruder_nr The extruder for which to compute disallowed areas * \return The disallowed areas @@ -175,9 +162,9 @@ class SkirtBrim /*! * Generate a brim line with offset parameters given by \p offset from the \p starting_outlines and store it in the \ref storage. - * + * * \warning Has side effects on \p covered_area, \p allowed_areas_per_extruder and \p total_length - * + * * \param offset The parameters with which to perform the offset * \param[in,out] covered_area The total area covered by the brims (and models) on the first layer. * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area @@ -188,11 +175,11 @@ class SkirtBrim /*! * Generate a skirt of extruders which don't yet comply with the minimum length requirement. - * + * * This skirt goes directly adjacent to all primary brims. - * + * * The skirt is stored in storage.skirt_brim. - * + * * \param[in,out] covered_area The total area covered by the brims (and models) on the first layer. * \param[in,out] allowed_areas_per_extruder The difference between the machine areas and the \p covered_area * \param[in,out] total_length The total length of the brim lines for each extruder. @@ -204,26 +191,7 @@ class SkirtBrim * Generate the brim which is printed from the outlines of the support inward. */ void generateSupportBrim(); - -private: - /*! - * \brief Generate the skirt/brim lines around the model. - * - * \param start_distance The distance of the first outset from the parts at - * the first line. - * \param primary_line_count Number of offsets / brim lines of the primary - * extruder. - * \param primary_extruder_minimal_length The minimal total length of the - * skirt/brim lines of the primary extruder. - * \param first_layer_outline The reference polygons from which to offset - * outward to generate skirt/brim lines. - * \param[out] skirt_brim_primary_extruder Where to store the resulting - * brim/skirt lines. - * \return The offset of the last brim/skirt line from the reference polygon - * \p first_layer_outline. - */ - static coord_t generatePrimarySkirtBrimLines(const coord_t start_distance, size_t& primary_line_count, const coord_t primary_extruder_minimal_length, const Polygons& first_layer_outline, Polygons& skirt_brim_primary_extruder); }; -}//namespace cura +} // namespace cura -#endif //SKIRT_BRIM_H +#endif // SKIRT_BRIM_H diff --git a/include/SupportInfillPart.h b/include/SupportInfillPart.h index 2a1849ab55..1767847cb7 100644 --- a/include/SupportInfillPart.h +++ b/include/SupportInfillPart.h @@ -25,17 +25,18 @@ namespace cura class SupportInfillPart { public: - PolygonsPart outline; //!< The outline of the support infill area - AABB outline_boundary_box; //!< The boundary box for the infill area - coord_t support_line_width; //!< The support line width - int inset_count_to_generate; //!< The number of insets need to be generated from the outline. This is not the actual insets that will be generated. - std::vector> infill_area_per_combine_per_density; //!< a list of separated sub-areas which requires different infill densities and combined thicknesses - // for infill_areas[x][n], x means the density level and n means the thickness + PolygonsPart outline; //!< The outline of the support infill area + AABB outline_boundary_box; //!< The boundary box for the infill area + coord_t support_line_width; //!< The support line width + int inset_count_to_generate; //!< The number of insets need to be generated from the outline. This is not the actual insets that will be generated. + std::vector> infill_area_per_combine_per_density; //!< a list of separated sub-areas which requires different infill densities and combined thicknesses + // for infill_areas[x][n], x means the density level and n means the thickness std::vector wall_toolpaths; //!< Any walls go here, not in the areas, where they could be combined vertically (don't combine walls). Binned by inset_idx. coord_t custom_line_distance; + bool use_fractional_config; //!< Request to use the configuration used to fill a partial layer height here, instead of the normal full layer height configuration. - SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate = 0, coord_t custom_line_distance = 0 ); + SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate = 0, coord_t custom_line_distance = 0); const Polygons& getInfillArea() const; }; diff --git a/include/TopSurface.h b/include/TopSurface.h index dcc5e5b9c2..77505bfa94 100644 --- a/include/TopSurface.h +++ b/include/TopSurface.h @@ -1,18 +1,20 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef TOPSURFACE_H #define TOPSURFACE_H +#include "GCodePathConfig.h" #include "utils/polygon.h" //For the polygon areas. namespace cura { -class GCodePathConfig; class FffGcodeWriter; class LayerPlan; +class SliceDataStorage; class SliceMeshStorage; +class SliceDataStorage; class TopSurface { @@ -59,6 +61,6 @@ class TopSurface Polygons areas; }; -} +} // namespace cura #endif /* TOPSURFACE_H */ diff --git a/include/TreeSupport.h b/include/TreeSupport.h index 32f67cd074..6ad2a5fac4 100644 --- a/include/TreeSupport.h +++ b/include/TreeSupport.h @@ -1,5 +1,5 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef TREESUPPORT_H #define TREESUPPORT_H @@ -32,7 +32,7 @@ constexpr auto TREE_PROGRESS_GENERATE_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / constexpr auto TREE_PROGRESS_SMOOTH_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / 3; constexpr auto TREE_PROGRESS_FINALIZE_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / 3; -constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA = 100; +constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA = 100.0; constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS = 1; constexpr auto SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT = false; constexpr auto SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL = false; @@ -69,8 +69,6 @@ class TreeSupport private: - - /*! * \brief Precalculates all avoidances, that could be required. * @@ -101,17 +99,14 @@ class TreeSupport * \param reduced_aabb[in,out] The already processed elements. * \param input_aabb[in] Not yet processed elements * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The influence areas without avoidance removed. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is + * not forced to. Value is the influence area where the center of a circle of support may be placed. \param influence_areas[in] The influence areas without avoidance removed. * \param insert_bp_areas[out] Elements to be inserted into the main dictionary after the Helper terminates. * \param insert_model_areas[out] Elements to be inserted into the secondary dictionary after the Helper terminates. - * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because of avoidance) - * \param erase[out] Elements that should be deleted from the above dictionaries. - * \param layer_idx[in] The Index of the current Layer. + * \param insert_influence[out] Elements to be inserted into the dictionary containing the largest possibly valid influence area (ignoring if the area may not be there because + * of avoidance) \param erase[out] Elements that should be deleted from the above dictionaries. \param layer_idx[in] The Index of the current Layer. */ - void mergeHelper - ( + void mergeHelper( std::map& reduced_aabb, std::map& input_aabb, const PropertyAreasUnordered& to_bp_areas, @@ -121,8 +116,7 @@ class TreeSupport PropertyAreasUnordered& insert_model_areas, PropertyAreasUnordered& insert_influence, std::vector& erase, - const LayerIndex layer_idx - ); + const LayerIndex layer_idx); /*! * \brief Merges Influence Areas if possible. @@ -132,28 +126,23 @@ class TreeSupport * * \param to_bp_areas[in] The Elements of the current Layer that will reach the buildplate. * Value is the influence area where the center of a circle of support may be placed. - * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is not forced to. - * Value is the influence area where the center of a circle of support may be placed. - * \param influence_areas[in] The Elements of the current Layer without avoidances removed. This is the largest possible influence area for this layer. - * Value is the influence area where the center of a circle of support may be placed. - * \param layer_idx[in] The current layer. + * \param to_model_areas[in] The Elements of the current Layer that do not have to reach the buildplate. Also contains main as every element that can reach the buildplate is + * not forced to. Value is the influence area where the center of a circle of support may be placed. \param influence_areas[in] The Elements of the current Layer without + * avoidances removed. This is the largest possible influence area for this layer. Value is the influence area where the center of a circle of support may be placed. \param + * layer_idx[in] The current layer. */ - void mergeInfluenceAreas - ( - PropertyAreasUnordered& to_bp_areas, - PropertyAreas& to_model_areas, - PropertyAreas& influence_areas, - LayerIndex layer_idx - ); + void mergeInfluenceAreas(PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, LayerIndex layer_idx); /*! * \brief Checks if an influence area contains a valid subsection and returns the corresponding metadata and the new Influence area. * * Calculates an influence areas of the layer below, based on the influence area of one element on the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are instead increased by maximum_move_distance_slow. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in we would change our gracious or to_buildplate status the influence areas are + * instead increased by maximum_move_distance_slow. Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this + * were the case, the radius is not increased instead. * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was + * done to avoid not needed heap allocations. * * \param settings[in] Which settings have to be used to check validity. * \param layer_idx[in] Number of the current layer. @@ -161,13 +150,12 @@ class TreeSupport * \param relevant_offset[in] The maximal possible influence area. No guarantee regarding validity with current layer collision required, as it is ensured in-function! * \param to_bp_data[out] The part of the Influence area that can reach the buildplate. * \param to_model_data[out] The part of the Influence area that do not have to reach the buildplate. This has overlap with new_layer_data. - * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. - * \return A valid support element for the next layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. + * \param increased[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the + * user-supplied settings. \param overspeed[in] How much should the already offset area be offset again. Usually this is 0. \param mergelayer[in] Will the merge method be + * called on this layer. This information is required as some calculation can be avoided if they are not required for merging. \return A valid support element for the next + * layer regarding the calculated influence areas. Empty if no influence are can be created using the supplied influence area and settings. */ - std::optional increaseSingleArea - ( + std::optional increaseSingleArea( AreaIncreaseSettings settings, LayerIndex layer_idx, TreeSupportElement* parent, @@ -176,37 +164,35 @@ class TreeSupport Polygons& to_model_data, Polygons& increased, const coord_t overspeed, - const bool mergelayer - ); + const bool mergelayer); /*! * \brief Increases influence areas as far as required. * * Calculates influence areas of the layer below, based on the influence areas of the current layer. - * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are instead increased by maximum_move_distance. - * Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were the case, the radius is not increased instead. + * Increases every influence area by maximum_move_distance_slow. If this is not enough, as in it would change the gracious or to_buildplate status, the influence areas are + * instead increased by maximum_move_distance. Also ensures that increasing the radius of a branch, does not cause it to change its status (like to_buildplate ). If this were + * the case, the radius is not increased instead. * - * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was done to avoid not needed heap allocations. + * Warning: The used format inside this is different as the SupportElement does not have a valid area member. Instead this area is saved as value of the dictionary. This was + * done to avoid not needed heap allocations. * * \param to_bp_areas[out] Influence areas that can reach the buildplate - * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also considered valid areas to the model. - * This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. - * \param influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the user-supplied settings. - * \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. - * \param last_layer[in] Influence areas of the current layer. - * \param layer_idx[in] Number of the current layer. - * \param mergelayer[in] Will the merge method be called on this layer. This information is required as some calculation can be avoided if they are not required for merging. + * \param to_model_areas[out] Influence areas that do not have to reach the buildplate. This has overlap with new_layer_data, as areas that can reach the buildplate are also + * considered valid areas to the model. This redundancy is required if a to_buildplate influence area is allowed to merge with a to model influence area. \param + * influence_areas[out] Area than can reach all further up support points. No assurance is made that the buildplate or the model can be reached in accordance to the + * user-supplied settings. \param bypass_merge_areas[out] Influence areas ready to be added to the layer below that do not need merging. \param last_layer[in] Influence areas + * of the current layer. \param layer_idx[in] Number of the current layer. \param mergelayer[in] Will the merge method be called on this layer. This information is required as + * some calculation can be avoided if they are not required for merging. */ - void increaseAreas - ( + void increaseAreas( PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, const LayerIndex layer_idx, - const bool mergelayer - ); + const bool mergelayer); /*! * \brief Propagates influence downwards, and merges overlapping ones. @@ -245,15 +231,13 @@ class TreeSupport * * \param linear_data[in] All currently existing influence areas with the layer they are on * \param layer_tree_polygons[out] Resulting branch areas with the layerindex they appear on. - * layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) corresponding branch area in layer_tree_polygons. - * \param inverse_tree_order[in] A mapping that returns the child of every influence area. + * layer_tree_polygons.size() has to be at least linear_data.size() as each Influence area in linear_data will save have at least one (that's why it's a vector) + * corresponding branch area in layer_tree_polygons. \param inverse_tree_order[in] A mapping that returns the child of every influence area. */ - void generateBranchAreas - ( + void generateBranchAreas( std::vector>& linear_data, std::vector>& layer_tree_polygons, - const std::map& inverse_tree_order - ); + const std::map& inverse_tree_order); /*! * \brief Applies some smoothing to the outer wall, intended to smooth out sudden jumps as they can happen when a branch moves though a hole. @@ -270,13 +254,11 @@ class TreeSupport * \param dropped_down_areas[out] Areas that have to be added to support all non-graceful areas. * \param inverse_tree_order[in] A mapping that returns the child of every influence area. */ - void dropNonGraciousAreas - ( + void dropNonGraciousAreas( std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, - const std::map& inverse_tree_order - ); + const std::map& inverse_tree_order); void filterFloatingLines(std::vector& support_layer_storage); @@ -335,7 +317,6 @@ class TreeSupport * Required for the progress bar the behave as expected when areas have to be calculated multiple times */ double progress_offset = 0; - }; diff --git a/include/TreeSupportTipGenerator.h b/include/TreeSupportTipGenerator.h index 16985e90dc..13c9dc6a80 100644 --- a/include/TreeSupportTipGenerator.h +++ b/include/TreeSupportTipGenerator.h @@ -1,7 +1,11 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + #ifndef TREESUPPORTTIPGENERATOR_H #define TREESUPPORTTIPGENERATOR_H #include "TreeModelVolumes.h" +#include "TreeSupport.h" #include "TreeSupportBaseCircle.h" #include "TreeSupportElement.h" #include "TreeSupportEnums.h" @@ -12,7 +16,6 @@ #include "sliceDataStorage.h" #include "utils/Coord_t.h" #include "utils/polygon.h" -#include "TreeSupport.h" namespace cura { @@ -20,19 +23,9 @@ namespace cura class TreeSupportTipGenerator { - public: - TreeSupportTipGenerator(const SliceDataStorage& storage, const SliceMeshStorage& mesh, TreeModelVolumes& volumes_); - ~ TreeSupportTipGenerator() - { - if (cross_fill_provider) - { - delete cross_fill_provider; - } - } - /*! * \brief Generate tips, that will later form branches * @@ -43,10 +36,14 @@ class TreeSupportTipGenerator * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ - void generateTips(SliceDataStorage& storage,const SliceMeshStorage& mesh ,std::vector>& move_bounds, std::vector& additional_support_areas, std::vector& placed_support_lines_support_areas); + void generateTips( + SliceDataStorage& storage, + const SliceMeshStorage& mesh, + std::vector>& move_bounds, + std::vector& additional_support_areas, + std::vector& placed_support_lines_support_areas); private: - enum class LineStatus { INVALID, @@ -85,17 +82,16 @@ class TreeSupportTipGenerator std::function)> getEvaluatePointForNextLayerFunction(size_t current_layer); /*! - * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using supplied lambda. + * \brief Evaluates which points of some lines are not valid one layer below and which are. Assumes all points are valid on the current layer. Validity is evaluated using + * supplied lambda. * * \param lines[in] The lines that have to be evaluated. * \param evaluatePoint[in] The function used to evaluate the points. * \return A pair with which points are still valid in the first slot and which are not in the second slot. */ - std::pair, std::vector> splitLines - ( - std::vector lines, - std::function)> evaluatePoint - ); // assumes all Points on the current line are valid + std::pair, std::vector> splitLines( + std::vector lines, + std::function)> evaluatePoint); // assumes all Points on the current line are valid /*! * \brief Ensures that every line segment is about distance in length. The resulting lines may differ from the original but all points are on the original @@ -116,7 +112,7 @@ class TreeSupportTipGenerator * \param line_width[in] What is the width of a line used in the infill. * \return A valid CrossInfillProvider. Has to be freed manually to avoid a memory leak. */ - SierpinskiFillProvider* generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const; + std::shared_ptr generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const; /*! @@ -125,7 +121,7 @@ class TreeSupportTipGenerator * \param result[out] The dropped overhang ares * \param roof[in] Whether the result is for roof generation. */ - void dropOverhangAreas(const SliceMeshStorage& mesh, std::vector& result, bool roof ); + void dropOverhangAreas(const SliceMeshStorage& mesh, std::vector& result, bool roof); /*! * \brief Calculates which areas should be supported with roof, and saves these in roof support_roof_drawn @@ -143,7 +139,15 @@ class TreeSupportTipGenerator * \param roof[in] Whether the tip supports a roof. * \param skip_ovalisation[in] Whether the tip may be ovalized when drawn later. */ - void addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation, std::vector additional_ovalization_targets = std::vector()); + void addPointAsInfluenceArea( + std::vector>& move_bounds, + std::pair p, + size_t dtt, + LayerIndex insert_layer, + size_t dont_move_until, + bool roof, + bool skip_ovalisation, + std::vector additional_ovalization_targets = std::vector()); /*! @@ -155,7 +159,14 @@ class TreeSupportTipGenerator * \param supports_roof[in] Whether the tip supports a roof. * \param dont_move_until[in] Until which dtt the branch should not move if possible. */ - void addLinesAsInfluenceAreas(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until, bool connect_points); + void addLinesAsInfluenceAreas( + std::vector>& move_bounds, + std::vector lines, + size_t roof_tip_layers, + LayerIndex insert_layer_idx, + bool supports_roof, + size_t dont_move_until, + bool connect_points); /*! * \brief Remove tips that should not have been added in the first place. @@ -163,7 +174,7 @@ class TreeSupportTipGenerator * \param storage[in] Background storage, required for adding roofs. * \param additional_support_areas[in] Areas that should have been roofs, but are now support, as they would not generate any lines as roof. */ - void removeUselessAddedPoints(std::vector>& move_bounds,SliceDataStorage& storage, std::vector& additional_support_areas); + void removeUselessAddedPoints(std::vector>& move_bounds, SliceDataStorage& storage, std::vector& additional_support_areas); /*! @@ -183,14 +194,14 @@ class TreeSupportTipGenerator /*! - * \brief Minimum area an overhang has to have to become a roof. + * \brief Minimum area an overhang has to have to be supported. */ - const double minimum_roof_area; + const double minimum_support_area; /*! - * \brief Minimum area an overhang has to have to be supported. + * \brief Minimum area an overhang has to have to become a roof. */ - const double minimum_support_area; + const double minimum_roof_area; /*! * \brief Amount of layers of roof. Zero if roof is disabled @@ -264,7 +275,8 @@ class TreeSupportTipGenerator const bool only_gracious = SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL; /*! - * \brief Whether minimum_roof_area is a hard limit. If false the roof will be combined with roof above and below, to see if a part of this roof may be part of a valid roof further up/down. + * \brief Whether minimum_roof_area is a hard limit. If false the roof will be combined with roof above and below, to see if a part of this roof may be part of a valid roof + * further up/down. */ const bool force_minimum_roof_area = SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT; @@ -276,7 +288,7 @@ class TreeSupportTipGenerator /*! * \brief Required to generate cross infill patterns */ - SierpinskiFillProvider* cross_fill_provider; + std::shared_ptr cross_fill_provider; /*! * \brief Map that saves locations of already inserted tips. Used to prevent tips far to close together from being added. @@ -294,15 +306,10 @@ class TreeSupportTipGenerator std::vector roof_tips_drawn; - - std::mutex critical_move_bounds; std::mutex critical_roof_tips; - - - }; -} +} // namespace cura #endif /* TREESUPPORT_H */ \ No newline at end of file diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index 2f6378dc20..fd6ab60526 100644 --- a/include/TreeSupportUtils.h +++ b/include/TreeSupportUtils.h @@ -1,3 +1,6 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + #ifndef TREESUPPORTTUTILS_H #define TREESUPPORTTUTILS_H @@ -7,21 +10,20 @@ #include "TreeSupportEnums.h" #include "TreeSupportSettings.h" #include "boost/functional/hash.hpp" // For combining hashes +#include "infill.h" #include "polyclipping/clipper.hpp" #include "settings/EnumSettings.h" #include "sliceDataStorage.h" #include "utils/Coord_t.h" #include "utils/polygon.h" + #include -#include "infill.h" namespace cura { class TreeSupportUtils { - - public: /*! * \brief Adds the implicit line from the last vertex of a Polygon to the first one. @@ -81,7 +83,6 @@ class TreeSupportUtils } - /*! * \brief Returns Polylines representing the (infill) lines that will result in slicing the given area * @@ -95,7 +96,15 @@ class TreeSupportUtils * todo doku * \return A Polygons object that represents the resulting infill lines. */ - [[nodiscard]] static Polygons generateSupportInfillLines(const Polygons& area,const TreeSupportSettings& config, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls, bool generate_support_supporting = false) + [[nodiscard]] static Polygons generateSupportInfillLines( + const Polygons& area, + const TreeSupportSettings& config, + bool roof, + LayerIndex layer_idx, + coord_t support_infill_distance, + std::shared_ptr cross_fill_provider, + bool include_walls, + bool generate_support_supporting = false) { Polygons gaps; // As we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it, it works perfectly. @@ -107,7 +116,7 @@ class TreeSupportUtils constexpr coord_t support_roof_overlap = 0; constexpr size_t infill_multiplier = 1; const int support_shift = roof ? 0 : support_infill_distance / 2; - const size_t wall_line_count = include_walls ? (!roof ? config.support_wall_count : config.support_roof_wall_count):0; + const size_t wall_line_count = include_walls ? (! roof ? config.support_wall_count : config.support_roof_wall_count) : 0; constexpr coord_t narrow_area_width = 0; const Point infill_origin; constexpr bool skip_stitching = false; @@ -124,32 +133,30 @@ class TreeSupportUtils const size_t divisor = angles.size(); const size_t index = ((layer_idx % divisor) + divisor) % divisor; const AngleDegrees fill_angle = angles[index]; - Infill roof_computation - ( - pattern, - zig_zaggify_infill, - connect_polygons, - area, - roof ? config.support_roof_line_width : config.support_line_width, - support_infill_distance, - support_roof_overlap, - infill_multiplier, - fill_angle, - z, - support_shift, - config.maximum_resolution, - config.maximum_deviation, - wall_line_count, - narrow_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size - ); + Infill roof_computation( + pattern, + zig_zaggify_infill, + connect_polygons, + area, + roof ? config.support_roof_line_width : config.support_line_width, + support_infill_distance, + support_roof_overlap, + infill_multiplier, + fill_angle, + z, + support_shift, + config.maximum_resolution, + config.maximum_deviation, + wall_line_count, + narrow_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); Polygons areas; Polygons lines; @@ -168,8 +175,8 @@ class TreeSupportUtils [[nodiscard]] static Polygons safeUnion(const Polygons& first, const Polygons& second = Polygons()) { // The unionPolygons function can slowly remove Polygons under certain circumstances, because of rounding issues (Polygons that have a thin area). - // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly workaround is needed - // Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: + // This does not cause a problem when actually using it on large areas, but as influence areas (representing centerpoints) can be very thin, this does occur so this ugly + // workaround is needed Here is an example of a Polygons object that will loose vertices when unioning, and will be gone after a few times unionPolygons was called: /* 120410,83599 120384,83643 @@ -198,14 +205,12 @@ class TreeSupportUtils * \param distance[in] The distance by which me should be offset. Expects values >=0. * \param collision[in] The area representing obstacles. * \param last_step_offset_without_check[in] The most it is allowed to offset in one step. - * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get very small. - * Required as arcTolerance is not exposed in offset, which should result with a similar result, benefit may be eliminated by simplifying. - * \param min_offset_per_step Don't get below this amount of offset per step taken. Fine-tune tradeoff between speed and accuracy. - * \param simplifier[in] Pointer to Simplify object if the offset operation also simplify the Polygon. Improves performance. - * \return The resulting Polygons object. + * \param min_amount_offset[in] How many steps have to be done at least. As this uses round offset this increases the amount of vertices, which may be required if Polygons get + * very small. Required as arcTolerance is not exposed in offset, which should result with a similar result, benefit may be eliminated by simplifying. \param + * min_offset_per_step Don't get below this amount of offset per step taken. Fine-tune tradeoff between speed and accuracy. \param simplifier[in] Pointer to Simplify object if + * the offset operation also simplify the Polygon. Improves performance. \return The resulting Polygons object. */ - [[nodiscard]] static Polygons safeOffsetInc - ( + [[nodiscard]] static Polygons safeOffsetInc( const Polygons& me, coord_t distance, const Polygons& collision, @@ -213,8 +218,7 @@ class TreeSupportUtils coord_t last_step_offset_without_check, size_t min_amount_offset, coord_t min_offset_per_step, - Simplify* simplifier - ) + Simplify* simplifier) { bool do_final_difference = last_step_offset_without_check == 0; Polygons ret = safeUnion(me); // Ensure sane input. @@ -267,7 +271,7 @@ class TreeSupportUtils } } ret = ret.offset(distance - steps * step_size, ClipperLib::jtRound); // Offset the remainder. - if(simplifier) + if (simplifier) { ret = simplifier->polygon(ret); } @@ -284,31 +288,30 @@ class TreeSupportUtils * * \param polylines[in] The polyline object from which the lines are moved. * \param area[in] The area the points are moved out of. - * \param max_allowed_distance[in] The maximum distance a point may be moved. If not possible the point will be moved as far as possible in the direction of the outside of the provided area. - * \return A Polyline object containing the moved points. + * \param max_allowed_distance[in] The maximum distance a point may be moved. If not possible the point will be moved as far as possible in the direction of the outside of the + * provided area. \return A Polyline object containing the moved points. */ - [[nodiscard]]static Polygons movePointsOutside(const Polygons& polylines, const Polygons& area, coord_t max_allowed_distance) + [[nodiscard]] static Polygons movePointsOutside(const Polygons& polylines, const Polygons& area, coord_t max_allowed_distance) { Polygons result; - for (auto line:polylines) + for (auto line : polylines) { Polygon next_line; - for (Point p:line) + for (Point p : line) { - if (area.inside(p)) { Point next_outside = p; - PolygonUtils::moveOutside(area,next_outside); - if (vSize2(p-next_outside)0) + if (next_line.size() > 0) { result.add(next_line); } @@ -326,25 +329,23 @@ class TreeSupportUtils return result; } - [[nodiscard]]static VariableWidthLines polyLineToVWL(const Polygons& polylines, coord_t line_width) + [[nodiscard]] static VariableWidthLines polyLineToVWL(const Polygons& polylines, coord_t line_width) { VariableWidthLines result; - for (auto path: polylines) + for (auto path : polylines) { - ExtrusionLine vwl_line(1,true); + ExtrusionLine vwl_line(1, true); - for(Point p: path) + for (Point p : path) { - vwl_line.emplace_back(p,line_width,1); + vwl_line.emplace_back(p, line_width, 1); } result.emplace_back(vwl_line); } return result; } - - }; -} //namespace cura +} // namespace cura #endif // TREESUPPORTTUTILS_H diff --git a/include/communication/ArcusCommunication.h b/include/communication/ArcusCommunication.h index 133c68268f..7cc7679535 100644 --- a/include/communication/ArcusCommunication.h +++ b/include/communication/ArcusCommunication.h @@ -6,17 +6,17 @@ #ifdef ARCUS #ifdef BUILD_TESTS - #include +#include #endif -#include //For unique_ptr and shared_ptr. - #include "Communication.h" //The class we're implementing. #include "Cura.pb.h" //To create Protobuf messages for Cura's front-end. -//Forward declarations to speed up compilation. +#include //For unique_ptr and shared_ptr. + +// Forward declarations to speed up compilation. namespace Arcus { - class Socket; +class Socket; } namespace cura @@ -117,7 +117,7 @@ class ArcusCommunication : public Communication * \param z The z-coordinate of the top side of the layer. * \param thickness The thickness of the layer. */ - void sendLayerComplete(const LayerIndex& layer_nr, const coord_t& z, const coord_t& thickness) override; + void sendLayerComplete(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness) override; /* * \brief Send a line to the front-end to display in layer view. @@ -192,7 +192,7 @@ class ArcusCommunication : public Communication * \param layer_nr The index of the layer to send data for. This is zero- * indexed but may be negative for raft layers. */ - void setLayerForSend(const LayerIndex& layer_nr) override; + void setLayerForSend(const LayerIndex::value_type& layer_nr) override; /* * \brief Slice the next scene that the front-end wants us to slice. @@ -227,7 +227,7 @@ class ArcusCommunication : public Communication const std::unique_ptr path_compiler; }; -} //namespace cura +} // namespace cura -#endif //ARCUS -#endif //ARCUSCOMMUNICATION_H +#endif // ARCUS +#endif // ARCUSCOMMUNICATION_H diff --git a/include/communication/ArcusCommunicationPrivate.h b/include/communication/ArcusCommunicationPrivate.h index bcd304ef42..5d2cebfed2 100644 --- a/include/communication/ArcusCommunicationPrivate.h +++ b/include/communication/ArcusCommunicationPrivate.h @@ -1,23 +1,24 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef ARCUSCOMMUNICATIONPRIVATE_H #define ARCUSCOMMUNICATIONPRIVATE_H #ifdef ARCUS -#include //For ostringstream. - #include "ArcusCommunication.h" //We're adding a subclass to this. #include "SliceDataStruct.h" +#include "settings/types/LayerIndex.h" + +#include //For ostringstream. namespace cura { -struct LayerIndex; class ArcusCommunication::Private { friend class ArcusCommunicationPrivateTest; + public: Private(); @@ -26,7 +27,7 @@ class ArcusCommunication::Private * \param layer_nr The layer number to get the optimised layer data for. * \return The optimised layer data for that layer. */ - std::shared_ptr getOptimizedLayerById(LayerIndex layer_nr); + std::shared_ptr getOptimizedLayerById(LayerIndex::value_type layer_nr); /* * Reads the global settings from a Protobuf message. @@ -73,7 +74,7 @@ class ArcusCommunication::Private const size_t millisecUntilNextTry; // How long we wait until we try to connect again. }; -} //namespace cura +} // namespace cura -#endif //ARCUS -#endif //ARCUSCOMMUNICATIONPRIVATE_H \ No newline at end of file +#endif // ARCUS +#endif // ARCUSCOMMUNICATIONPRIVATE_H \ No newline at end of file diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 321671fd1c..2948241c51 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -4,13 +4,13 @@ #ifndef COMMANDLINE_H #define COMMANDLINE_H +#include "Communication.h" //The class we're implementing. + #include //Loading JSON documents to get settings from them. #include //To store the command line arguments. #include #include //To store the command line arguments. -#include "Communication.h" //The class we're implementing. - namespace cura { class Settings; @@ -86,7 +86,7 @@ class CommandLine : public Communication * The command line doesn't do anything with that information so this is * ignored. */ - void sendLayerComplete(const LayerIndex&, const coord_t&, const coord_t&) override; + void sendLayerComplete(const LayerIndex::value_type&, const coord_t&, const coord_t&) override; /* * \brief Send a line for display. @@ -143,7 +143,7 @@ class CommandLine : public Communication * This has no effect though because we don't shwo these three functions * because the command line doesn't show layer view. */ - void setLayerForSend(const LayerIndex&) override; + void setLayerForSend(const LayerIndex::value_type&) override; /* * \brief Slice the next scene that the command line commands us to slice. @@ -188,14 +188,12 @@ class CommandLine : public Communication * \return Error code. If it's 0, the document was successfully loaded. If * it's 1, some inheriting file could not be opened. */ - int loadJSON - ( + int loadJSON( const rapidjson::Document& document, const std::unordered_set& search_directories, Settings& settings, bool force_read_parent = false, - bool force_read_nondefault = false - ); + bool force_read_nondefault = false); /* * \brief Load an element containing a list of settings. @@ -216,6 +214,6 @@ class CommandLine : public Communication const std::string findDefinitionFile(const std::string& definition_id, const std::unordered_set& search_directories); }; -} //namespace cura +} // namespace cura -#endif //COMMANDLINE_H \ No newline at end of file +#endif // COMMANDLINE_H \ No newline at end of file diff --git a/include/communication/Communication.h b/include/communication/Communication.h index 1853f7c8e0..bdd68b9630 100644 --- a/include/communication/Communication.h +++ b/include/communication/Communication.h @@ -4,13 +4,13 @@ #ifndef COMMUNICATION_H #define COMMUNICATION_H -#include "../utils/IntPoint.h" //For coord_t and Point. +#include "settings/types/LayerIndex.h" +#include "settings/types/Velocity.h" +#include "utils/IntPoint.h" namespace cura { -//Some forward declarations to increase compilation speed. -struct LayerIndex; -struct Velocity; +// Some forward declarations to increase compilation speed. enum class PrintFeatureType : unsigned char; class Polygons; class ConstPolygonRef; @@ -26,7 +26,7 @@ class Communication /* * \brief Close the communication channel. */ - virtual ~Communication() {} + virtual ~Communication() = default; /* * \brief Test if there are more slices to be queued. @@ -62,7 +62,7 @@ class Communication * \param z The z-coordinate of the top side of the layer. * \param thickness The thickness of the layer. */ - virtual void sendLayerComplete(const LayerIndex& layer_nr, const coord_t& z, const coord_t& thickness) = 0; + virtual void sendLayerComplete(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness) = 0; /* * \brief Send polygons to the user to visualise. @@ -126,7 +126,7 @@ class Communication * \param layer_nr The index of the layer to send data for. This is zero- * indexed but may be negative for raft layers. */ - virtual void setLayerForSend(const LayerIndex& layer_nr) = 0; + virtual void setLayerForSend(const LayerIndex::value_type& layer_nr) = 0; /* * \brief Send the sliced layer data through this communication after the @@ -180,7 +180,6 @@ class Communication virtual void sliceNext() = 0; }; -} //namespace cura - -#endif //COMMUNICATION_H +} // namespace cura +#endif // COMMUNICATION_H diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 207f9df46c..c332ef0f52 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -1,35 +1,35 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef GCODEEXPORT_H #define GCODEEXPORT_H #include // for extrusionAmountAtPreviousRetractions #ifdef BUILD_TESTS - #include //To allow tests to use protected members. +#include //To allow tests to use protected members. #endif -#include // for stream.str() -#include - -#include "utils/AABB3D.h" //To track the used build volume for the Griffin header. -#include "timeEstimate.h" #include "settings/EnumSettings.h" #include "settings/Settings.h" //For MAX_EXTRUDERS. +#include "settings/types/LayerIndex.h" #include "settings/types/Temperature.h" //Bed temperature. #include "settings/types/Velocity.h" +#include "sliceDataStorage.h" +#include "timeEstimate.h" +#include "utils/AABB3D.h" //To track the used build volume for the Griffin header. #include "utils/IntPoint.h" #include "utils/NoCopy.h" -#include "sliceDataStorage.h" + +#include // for stream.str() +#include namespace cura { -struct LayerIndex; class RetractionConfig; struct WipeScriptConfig; -//The GCodeExport class writes the actual GCode. This is the only class that knows how GCode looks and feels. -// Any customizations on GCodes flavors are done in this class. +// The GCodeExport class writes the actual GCode. This is the only class that knows how GCode looks and feels. +// Any customizations on GCodes flavors are done in this class. class GCodeExport : public NoCopy { #ifdef BUILD_TESTS @@ -79,8 +79,10 @@ class GCodeExport : public NoCopy bool waited_for_temperature; //!< Whether the most recent temperature command has been a heat-and-wait command (M109) or not (M104). Temperature initial_temp; //!< Temperature this nozzle needs to be at the start of the print. - double retraction_e_amount_current; //!< The current retracted amount (in mm or mm^3), or zero(i.e. false) if it is not currently retracted (positive values mean retracted amount, so negative impact on E values) - double retraction_e_amount_at_e_start; //!< The ExtruderTrainAttributes::retraction_amount_current value at E0, i.e. the offset (in mm or mm^3) from E0 to the situation where the filament is at the tip of the nozzle. + double retraction_e_amount_current; //!< The current retracted amount (in mm or mm^3), or zero(i.e. false) if it is not currently retracted (positive values mean retracted + //!< amount, so negative impact on E values) + double retraction_e_amount_at_e_start; //!< The ExtruderTrainAttributes::retraction_amount_current value at E0, i.e. the offset (in mm or mm^3) from E0 to the situation + //!< where the filament is at the tip of the nozzle. double prime_volume; //!< Amount of material (in mm^3) to be primed after an unretration (due to oozing and/or coasting) Velocity last_retraction_prime_speed; //!< The last prime speed (in mm/s) of the to-be-primed amount @@ -88,20 +90,25 @@ class GCodeExport : public NoCopy double last_e_value_after_wipe; //!< The current material amount extruded since last wipe unsigned fan_number; // nozzle print cooling fan number + Point nozzle_offset; //!< Cache of setting machine_nozzle_offset_[xy] + bool machine_firmware_retract; //!< Cache of setting machine_firmware_retract std::deque extruded_volume_at_previous_n_retractions; // in mm^3 ExtruderTrainAttributes() - : is_primed(false) - , is_used(false) - , extruderCharacter(0) - , filament_area(0) - , totalFilament(0) - , currentTemperature(0) - , waited_for_temperature(false) - , initial_temp(0) - , retraction_e_amount_current(0.0), retraction_e_amount_at_e_start(0.0), - prime_volume(0.0), last_retraction_prime_speed(0.0), fan_number(0) + : is_primed(false) + , is_used(false) + , extruderCharacter(0) + , filament_area(0) + , totalFilament(0) + , currentTemperature(0) + , waited_for_temperature(false) + , initial_temp(0) + , retraction_e_amount_current(0.0) + , retraction_e_amount_at_e_start(0.0) + , prime_volume(0.0) + , last_retraction_prime_speed(0.0) + , fan_number(0) { } }; @@ -120,23 +127,27 @@ class GCodeExport : public NoCopy double max_extrusion_offset; //!< 0 to turn it off, normally 4 double extrusion_offset_factor; //!< default 1 - Point3 currentPosition; //!< The last build plate coordinates written to gcode (which might be different from actually written gcode coordinates when the extruder offset is encoded in the gcode) + Point3 currentPosition; //!< The last build plate coordinates written to gcode (which might be different from actually written gcode coordinates when the extruder offset is + //!< encoded in the gcode) Velocity currentSpeed; //!< The current speed (F values / 60) in mm/s - Acceleration current_print_acceleration; //!< The current acceleration (in mm/s^2) used for print moves (and also for travel moves if the gcode flavor doesn't have separate travel acceleration) - Acceleration current_travel_acceleration; //!< The current acceleration (in mm/s^2) used for travel moves for those gcode flavors that have separate print and travel accelerations + Acceleration current_print_acceleration; //!< The current acceleration (in mm/s^2) used for print moves (and also for travel moves if the gcode flavor doesn't have separate + //!< travel acceleration) + Acceleration + current_travel_acceleration; //!< The current acceleration (in mm/s^2) used for travel moves for those gcode flavors that have separate print and travel accelerations Velocity current_jerk; //!< The current jerk in the XY direction (in mm/s^3) AABB3D total_bounding_box; //!< The bounding box of all g-code. /*! * The z position to be used on the next xy move, if the head wasn't in the correct z position yet. - * + * * \see GCodeExport::writeExtrusion(Point, double, double) - * + * * \note After GCodeExport::writeExtrusion(Point, double, double) has been called currentPosition.z coincides with this value */ coord_t current_layer_z; - coord_t is_z_hopped; //!< The amount by which the print head is currently z hopped, or zero if it is not z hopped. (A z hop is used during travel moves to avoid collision with other layer parts) + coord_t is_z_hopped; //!< The amount by which the print head is currently z hopped, or zero if it is not z hopped. (A z hop is used during travel moves to avoid collision with + //!< other layer parts) size_t current_extruder; double current_fan_speed; @@ -146,7 +157,7 @@ class GCodeExport : public NoCopy std::vector total_print_times; //!< The total estimated print time in seconds for each feature TimeEstimateCalculator estimateCalculator; - unsigned int layer_nr; //!< for sending travel data + LayerIndex layer_nr; //!< for sending travel data bool is_volumetric; bool relative_extrusion; //!< whether to use relative extrusion distances rather than absolute @@ -154,15 +165,15 @@ class GCodeExport : public NoCopy Temperature initial_bed_temp; //!< bed temperature at the beginning of the print. Temperature bed_temperature; //!< Current build plate temperature. - Temperature build_volume_temperature; //!< build volume temperature - bool machine_heated_build_volume; //!< does the machine have the ability to control/stabilize build-volume-temperature + Temperature build_volume_temperature; //!< build volume temperature + bool machine_heated_build_volume; //!< does the machine have the ability to control/stabilize build-volume-temperature protected: /*! * Convert an E value to a value in mm (if it wasn't already in mm) for the current extruder. - * + * * E values are either in mm or in mm^3 * The current extruder is used to determine the filament area to make the conversion. - * + * * \param e the value to convert * \return the value converted to mm */ @@ -170,10 +181,10 @@ class GCodeExport : public NoCopy /*! * Convert a volume value to an E value (which might be volumetric as well) for the current extruder. - * + * * E values are either in mm or in mm^3 * The current extruder is used to determine the filament area to make the conversion. - * + * * \param mm3 the value to convert * \return the value converted to mm or mm3 depending on whether the E axis is volumetric */ @@ -181,10 +192,10 @@ class GCodeExport : public NoCopy /*! * Convert a distance value to an E value (which might be linear/distance based as well) for the current extruder. - * + * * E values are either in mm or in mm^3 * The current extruder is used to determine the filament area to make the conversion. - * + * * \param mm the value to convert * \return the value converted to mm or mm3 depending on whether the E axis is volumetric */ @@ -203,7 +214,6 @@ class GCodeExport : public NoCopy double eToMm3(double e, size_t extruder); public: - GCodeExport(); ~GCodeExport(); @@ -224,14 +234,15 @@ class GCodeExport : public NoCopy * \param mat_ids The material GUIDs for each material. * \return The string representing the file header */ - std::string getFileHeader(const std::vector& extruder_is_used, - const Duration* print_time = nullptr, - const std::vector& filament_used = std::vector(), - const std::vector& mat_ids = std::vector()); + std::string getFileHeader( + const std::vector& extruder_is_used, + const Duration* print_time = nullptr, + const std::vector& filament_used = std::vector(), + const std::vector& mat_ids = std::vector()); void setSliceUUID(const std::string& slice_uuid); - void setLayerNr(unsigned int layer_nr); + void setLayerNr(const LayerIndex& layer_nr); void setOutputStream(std::ostream* stream); @@ -241,7 +252,7 @@ class GCodeExport : public NoCopy void setFlavor(EGCodeFlavor flavor); EGCodeFlavor getFlavor() const; - + void setZ(int z); void setFlowRateExtrusionSettings(double max_extrusion_offset, double extrusion_offset_factor); @@ -252,24 +263,24 @@ class GCodeExport : public NoCopy * \param extra_prime_distance Amount of material in mm. */ void addExtraPrimeAmount(double extra_prime_volume); - + Point3 getPosition() const; - + Point getPositionXY() const; int getPositionZ() const; int getExtruderNr() const; - + void setFilamentDiameter(size_t extruder, const coord_t diameter); - + double getCurrentExtrudedVolume() const; /*! * Get the total extruded volume for a specific extruder in mm^3 - * + * * Retractions and unretractions don't contribute to this. - * + * * \param extruder_nr The extruder number for which to get the total netto extruded volume * \return total filament printed in mm^3 */ @@ -277,19 +288,19 @@ class GCodeExport : public NoCopy /*! * Get the total estimated print time in seconds for each feature - * + * * \return total print time in seconds for each feature */ std::vector getTotalPrintTimePerFeature(); /*! * Get the total print time in seconds for the complete print - * + * * \return total print time in seconds for the complete print */ double getSumTotalPrintTimes(); void updateTotalPrintTime(); void resetTotalPrintTimeAndFilament(); - + void writeComment(const std::string& comment); void writeTypeComment(const PrintFeatureType& type); @@ -304,7 +315,7 @@ class GCodeExport : public NoCopy /*! * Write a comment saying what (estimated) time has passed up to this point - * + * * \param time The time passed up till this point */ void writeTimeComment(const Duration time); @@ -318,21 +329,21 @@ class GCodeExport : public NoCopy * Write a comment saying that the print has a certain number of layers. */ void writeLayerCountComment(const size_t layer_count); - + void writeLine(const char* line); - + /*! * Reset the current_e_value to prevent too high E values. - * + * * The current extruded volume is added to the current extruder_attr. */ void resetExtrusionValue(); - + void writeDelay(const Duration& time_amount); /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param p location to go to * \param speed movement speed */ @@ -340,7 +351,7 @@ class GCodeExport : public NoCopy /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param p location to go to * \param speed movement speed * \param feature the feature that's currently printing @@ -351,7 +362,7 @@ class GCodeExport : public NoCopy /*! * Go to a X/Y location with the z-hopped Z value * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param p location to go to * \param speed movement speed */ @@ -361,9 +372,9 @@ class GCodeExport : public NoCopy * Go to a X/Y location with the extrusion Z * Perform un-z-hop * Perform unretraction - * + * * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param p location to go to * \param speed movement speed * \param feature the feature that's currently printing @@ -380,10 +391,10 @@ class GCodeExport : public NoCopy bool initializeExtruderTrains(const SliceDataStorage& storage, const size_t start_extruder_nr); /*! - * Set temperatures for the initial layer. Called by 'processStartingCode' and whenever a new object is started at layer 0. - * - * \param[in] storage where the slice data is stored. - * \param[in] start_extruder_nr The extruder with which to start the print. + * Set temperatures for the initial layer. Called by 'processStartingCode' and whenever a new object is started at layer 0. + * + * \param[in] storage where the slice data is stored. + * \param[in] start_extruder_nr The extruder with which to start the print. */ void processInitialLayerTemperature(const SliceDataStorage& storage, const size_t start_extruder_nr); @@ -398,7 +409,7 @@ class GCodeExport : public NoCopy private: /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param x build plate x * \param y build plate y * \param z build plate z @@ -411,7 +422,7 @@ class GCodeExport : public NoCopy * Perform unretract * Write extrusion move * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. - * + * * \param x build plate x * \param y build plate y * \param z build plate z @@ -420,13 +431,20 @@ class GCodeExport : public NoCopy * \param feature the print feature that's currently printing * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate */ - void writeExtrusion(const coord_t x, const coord_t y, const coord_t z, const Velocity& speed, const double extrusion_mm3_per_mm, const PrintFeatureType& feature, const bool update_extrusion_offset = false); + void writeExtrusion( + const coord_t x, + const coord_t y, + const coord_t z, + const Velocity& speed, + const double extrusion_mm3_per_mm, + const PrintFeatureType& feature, + const bool update_extrusion_offset = false); /*! * Write the F, X, Y, Z and E value (if they are not different from the last) - * + * * convenience function called from writeExtrusion and writeTravel - * + * * This function also applies the gcode offset by calling \ref GCodeExport::getGcodePos * This function updates the \ref GCodeExport::total_bounding_box * It estimates the time in \ref GCodeExport::estimateCalculator for the correct feature @@ -444,12 +462,18 @@ class GCodeExport : public NoCopy * \param feature print feature to track print time for */ void writeMoveBFB(const int x, const int y, const int z, const Velocity& speed, double extrusion_mm3_per_mm, PrintFeatureType feature); + + /*! + * Set bed temperature for the initial layer. Called by 'processInitialLayerTemperatures'. + */ + void processInitialLayerBedTemperature(); + public: /*! * Get ready for extrusion moves: * - unretract (G11 or G1 E.) * - prime blob (G1 E) - * + * * It estimates the time in \ref GCodeExport::estimateCalculator * It updates \ref GCodeExport::current_e_value and \ref GCodeExport::currentSpeed */ @@ -458,37 +482,37 @@ class GCodeExport : public NoCopy /*! * Start a z hop with the given \p hop_height. - * + * * \param hop_height The height to move above the current layer. - * \param speed The speed used for moving. + * \param speed The speed used for moving. */ - void writeZhopStart(const coord_t hop_height, Velocity speed = 0); + void writeZhopStart(const coord_t hop_height, Velocity speed = 0.0); /*! * End a z hop: go back to the layer height * * \param speed The speed used for moving. */ - void writeZhopEnd(Velocity speed = 0); + void writeZhopEnd(Velocity speed = 0.0); /*! - * Start the new_extruder: + * Start the new_extruder: * - set new extruder * - zero E value * - write extruder start gcode - * + * * \param new_extruder The extruder to start with */ void startExtruder(const size_t new_extruder); /*! - * Switch to the new_extruder: + * Switch to the new_extruder: * - perform neccessary retractions * - fiddle with E-values * - write extruder end gcode * - set new extruder * - write extruder start gcode - * + * * \param new_extruder The extruder to switch to * \param retraction_config_old_extruder The extruder switch retraction config of the old extruder, to perform the extruder switch retraction with. * \param perform_z_hop The amount by which the print head should be z hopped during extruder switch, or zero if it should not z hop. @@ -501,7 +525,7 @@ class GCodeExport : public NoCopy /*! * Write the gcode for priming the current extruder train so that it can be used. - * + * * \param travel_speed The travel speed when priming involves a movement */ void writePrimeTrain(const Velocity& travel_speed); @@ -512,9 +536,9 @@ class GCodeExport : public NoCopy * \param extruder The current extruder */ void setExtruderFanNumber(int extruder); - + void writeFanCommand(double speed); - + void writeTemperatureCommand(const size_t extruder, const Temperature& temperature, const bool wait = false); void writeBedTemperatureCommand(const Temperature& temperature, const bool wait = false); void writeBuildVolumeTemperatureCommand(const Temperature& temperature, const bool wait = false); @@ -542,7 +566,7 @@ class GCodeExport : public NoCopy /*! * Handle the initial (bed/nozzle) temperatures before any gcode is processed. * These temperatures are set in the pre-print setup in the firmware. - * + * * See FffGcodeWriter::processStartingCode * \param start_extruder_nr The extruder with which to start this print */ @@ -551,10 +575,10 @@ class GCodeExport : public NoCopy /*! * Override or set an initial nozzle temperature as written by GCodeExport::setInitialTemps * This is used primarily during better specification of temperatures in LayerPlanBuffer::insertPreheatCommand - * + * * \warning This function must be called before any of the layers in the meshgroup are written to file! * That's because it sets the current temperature in the gcode! - * + * * \param extruder_nr The extruder number for which to better specify the temp * \param temp The temp at which the nozzle should be at startup */ @@ -562,7 +586,7 @@ class GCodeExport : public NoCopy /*! * Finish the gcode: turn fans off, write end gcode and flush all gcode left in the buffer. - * + * * \param endCode The end gcode to be appended at the very end. */ void finalize(const char* endCode); @@ -579,7 +603,7 @@ class GCodeExport : public NoCopy * * \param extruder Extruder number which last_e_value_after_wipe value to reset. */ - void ResetLastEValueAfterWipe(size_t extruder); + void ResetLastEValueAfterWipe(size_t extruder); /*! * Generate g-code for wiping current nozzle using provided config. @@ -589,6 +613,6 @@ class GCodeExport : public NoCopy void insertWipeScript(const WipeScriptConfig& wipe_config); }; -} +} // namespace cura -#endif//GCODEEXPORT_H +#endif // GCODEEXPORT_H diff --git a/include/infill.h b/include/infill.h index 2f3e4fb287..289a0850bb 100644 --- a/include/infill.h +++ b/include/infill.h @@ -7,110 +7,187 @@ #include "infill/LightningGenerator.h" #include "infill/ZigzagConnectorProcessor.h" #include "settings/EnumSettings.h" //For infill types. -#include "settings/types/Angle.h" #include "settings/Settings.h" +#include "settings/types/Angle.h" +#include "utils/AABB.h" #include "utils/ExtrusionLine.h" #include "utils/IntPoint.h" #include "utils/section_type.h" +#include + +#include + namespace cura { -class AABB; class SierpinskiFillProvider; class SliceMeshStorage; -class Infill +class Infill { friend class InfillTest; - EFillMethod pattern; //!< the space filling pattern of the infill to generate - bool zig_zaggify; //!< Whether to connect the end pieces of the support lines via the wall - bool connect_lines; //!< Whether the lines and zig_zaggification are generated by the connectLines algorithm - bool connect_polygons; //!< Whether to connect as much polygons together into a single path - Polygons outer_contour; //!< The area that originally needs to be filled with infill. The input of the algorithm. - Polygons inner_contour; //!< The part of the contour that will get filled with an infill pattern. Equals outer_contour minus the extra infill walls. - coord_t infill_line_width; //!< The line width of the infill lines to generate - coord_t line_distance; //!< The distance between two infill lines / polygons - coord_t infill_overlap; //!< the distance by which to overlap with the actual area within which to generate infill - size_t infill_multiplier; //!< the number of infill lines next to each other - AngleDegrees fill_angle; //!< for linear infill types: the angle of the infill lines (or the angle of the grid) - coord_t z; //!< height of the layer for which we generate infill - coord_t shift; //!< shift of the scanlines in the direction perpendicular to the fill_angle - coord_t max_resolution; //!< Min feature size of the output - coord_t max_deviation; //!< Max deviation fro the original poly when enforcing max_resolution - size_t wall_line_count; //!< Number of walls to generate at the boundary of the infill region, spaced \ref infill_line_width apart - coord_t small_area_width; //!< Maximum width of a small infill region to be filled with walls - const Point infill_origin; //!< origin of the infill pattern - bool skip_line_stitching; //!< Whether to bypass the line stitching normally performed for polyline type infills - bool fill_gaps; //!< Whether to fill gaps in strips of infill that would be too thin to fit the infill lines. If disabled, those areas are left empty. - bool connected_zigzags; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector - bool use_endpieces; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself - bool skip_some_zags; //!< (ZigZag) Whether to skip some zags - size_t zag_skip_count; //!< (ZigZag) To skip one zag in every N if skip some zags is enabled - coord_t pocket_size; //!< The size of the pockets at the intersections of the fractal in the cross 3d pattern - bool mirror_offset; //!< Indication in which offset direction the extra infill lines are made - - static constexpr double one_over_sqrt_2 = 0.7071067811865475244008443621048490392848359376884740; //!< 1.0 / sqrt(2.0) + EFillMethod pattern{}; //!< the space filling pattern of the infill to generate + bool zig_zaggify{}; //!< Whether to connect the end pieces of the support lines via the wall + bool connect_lines{ calcConnectLines(pattern, zig_zaggify) }; //!< Whether the lines and zig_zaggification are generated by the connectLines algorithm + // TODO: The connected lines algorithm is only available for linear-based infill, for now. + // We skip ZigZag, Cross and Cross3D because they have their own algorithms. Eventually we want to replace all that with the new algorithm. + // Cubic Subdivision ends lines in the center of the infill so it won't be effective. + bool connect_polygons{}; //!< Whether to connect as much polygons together into a single path + Polygons outer_contour{}; //!< The area that originally needs to be filled with infill. The input of the algorithm. + Polygons inner_contour{}; //!< The part of the contour that will get filled with an infill pattern. Equals outer_contour minus the extra infill walls. + coord_t infill_line_width{}; //!< The line width of the infill lines to generate + coord_t line_distance{}; //!< The distance between two infill lines / polygons + coord_t infill_overlap{}; //!< the distance by which to overlap with the actual area within which to generate infill + size_t infill_multiplier{}; //!< the number of infill lines next to each other + AngleDegrees fill_angle{}; //!< for linear infill types: the angle of the infill lines (or the angle of the grid) + coord_t z{}; //!< height of the layer for which we generate infill + coord_t shift{}; //!< shift of the scanlines in the direction perpendicular to the fill_angle + coord_t max_resolution{}; //!< Min feature size of the output + coord_t max_deviation{}; //!< Max deviation fro the original poly when enforcing max_resolution + size_t wall_line_count{}; //!< Number of walls to generate at the boundary of the infill region, spaced \ref infill_line_width apart + coord_t small_area_width{}; //!< Maximum width of a small infill region to be filled with walls + Point infill_origin{}; //!< origin of the infill pattern + bool skip_line_stitching{ false }; //!< Whether to bypass the line stitching normally performed for polyline type infills + bool fill_gaps{ true }; //!< Whether to fill gaps in strips of infill that would be too thin to fit the infill lines. If disabled, those areas are left empty. + bool connected_zigzags{ false }; //!< (ZigZag) Whether endpieces of zigzag infill should be connected to the nearest infill line on both sides of the zigzag connector + bool use_endpieces{ false }; //!< (ZigZag) Whether to include endpieces: zigzag connector segments from one infill line to itself + bool skip_some_zags{ false }; //!< (ZigZag) Whether to skip some zags + size_t zag_skip_count{}; //!< (ZigZag) To skip one zag in every N if skip some zags is enabled + coord_t pocket_size{}; //!< The size of the pockets at the intersections of the fractal in the cross 3d pattern + bool mirror_offset{}; //!< Indication in which offset direction the extra infill lines are made + + static constexpr auto one_over_sqrt_2 = 1.0 / std::numbers::sqrt2; + + constexpr bool calcConnectLines(const EFillMethod pattern, const bool zig_zaggify) + { + return zig_zaggify + && (pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::GRID || pattern == EFillMethod::CUBIC + || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::TRIHEXAGON); + } + public: - Infill(EFillMethod pattern - , bool zig_zaggify - , bool connect_polygons - , const Polygons& in_outline - , coord_t infill_line_width - , coord_t line_distance - , coord_t infill_overlap - , size_t infill_multiplier - , AngleDegrees fill_angle - , coord_t z - , coord_t shift - , coord_t max_resolution - , coord_t max_deviation - , size_t wall_line_count = 0 - , coord_t small_area_width = 0 - , const Point& infill_origin = Point() - , bool skip_line_stitching = false - , bool fill_gaps = true - , bool connected_zigzags = false - , bool use_endpieces = false - , bool skip_some_zags = false - , size_t zag_skip_count = 0 - , coord_t pocket_size = 0 - ) - : pattern(pattern) - , zig_zaggify(zig_zaggify) - , connect_polygons(connect_polygons) - , outer_contour(in_outline) - , infill_line_width(infill_line_width) - , line_distance(line_distance) - , infill_overlap(infill_overlap) - , infill_multiplier(infill_multiplier) - , fill_angle(fill_angle) - , z(z) - , shift(shift) - , max_resolution(max_resolution) - , max_deviation(max_deviation) - , wall_line_count(wall_line_count) - , small_area_width(small_area_width) - , infill_origin(infill_origin) - , skip_line_stitching(skip_line_stitching) - , fill_gaps(fill_gaps) - , connected_zigzags(connected_zigzags) - , use_endpieces(use_endpieces) - , skip_some_zags(skip_some_zags) - , zag_skip_count(zag_skip_count) - , pocket_size(pocket_size) - , mirror_offset(zig_zaggify) + Infill() noexcept = default; + + Infill( + EFillMethod pattern, + bool zig_zaggify, + bool connect_polygons, + Polygons in_outline, + coord_t infill_line_width, + coord_t line_distance, + coord_t infill_overlap, + size_t infill_multiplier, + AngleDegrees fill_angle, + coord_t z, + coord_t shift, + coord_t max_resolution, + coord_t max_deviation) noexcept + : pattern{ pattern } + , zig_zaggify{ zig_zaggify } + , connect_polygons{ connect_polygons } + , outer_contour{ in_outline } + , infill_line_width{ infill_line_width } + , line_distance{ line_distance } + , infill_overlap{ infill_overlap } + , infill_multiplier{ infill_multiplier } + , fill_angle{ fill_angle } + , z{ z } + , shift{ shift } + , max_resolution{ max_resolution } + , max_deviation{ max_deviation } {}; + + Infill( + EFillMethod pattern, + bool zig_zaggify, + bool connect_polygons, + Polygons in_outline, + coord_t infill_line_width, + coord_t line_distance, + coord_t infill_overlap, + size_t infill_multiplier, + AngleDegrees fill_angle, + coord_t z, + coord_t shift, + coord_t max_resolution, + coord_t max_deviation, + size_t wall_line_count, + coord_t small_area_width, + Point infill_origin, + bool skip_line_stitching) noexcept + : pattern{ pattern } + , zig_zaggify{ zig_zaggify } + , connect_polygons{ connect_polygons } + , outer_contour{ in_outline } + , infill_line_width{ infill_line_width } + , line_distance{ line_distance } + , infill_overlap{ infill_overlap } + , infill_multiplier{ infill_multiplier } + , fill_angle{ fill_angle } + , z{ z } + , shift{ shift } + , max_resolution{ max_resolution } + , max_deviation{ max_deviation } + , wall_line_count{ wall_line_count } + , small_area_width{ small_area_width } + , infill_origin{ infill_origin } + , skip_line_stitching{ skip_line_stitching } {}; + + Infill( + EFillMethod pattern, + bool zig_zaggify, + bool connect_polygons, + Polygons in_outline, + coord_t infill_line_width, + coord_t line_distance, + coord_t infill_overlap, + size_t infill_multiplier, + AngleDegrees fill_angle, + coord_t z, + coord_t shift, + coord_t max_resolution, + coord_t max_deviation, + size_t wall_line_count, + coord_t small_area_width, + Point infill_origin, + bool skip_line_stitching, + bool fill_gaps, + bool connected_zigzags, + bool use_endpieces, + bool skip_some_zags, + size_t zag_skip_count, + coord_t pocket_size) noexcept + : pattern{ pattern } + , zig_zaggify{ zig_zaggify } + , connect_polygons{ connect_polygons } + , outer_contour{ in_outline } + , infill_line_width{ infill_line_width } + , line_distance{ line_distance } + , infill_overlap{ infill_overlap } + , infill_multiplier{ infill_multiplier } + , fill_angle{ fill_angle } + , z{ z } + , shift{ shift } + , max_resolution{ max_resolution } + , max_deviation{ max_deviation } + , wall_line_count{ wall_line_count } + , small_area_width{ small_area_width } + , infill_origin{ infill_origin } + , skip_line_stitching{ skip_line_stitching } + , fill_gaps{ fill_gaps } + , connected_zigzags{ connected_zigzags } + , use_endpieces{ use_endpieces } + , skip_some_zags{ skip_some_zags } + , zag_skip_count{ zag_skip_count } + , pocket_size{ pocket_size } + , mirror_offset{ zig_zaggify } { - //TODO: The connected lines algorithm is only available for linear-based infill, for now. - //We skip ZigZag, Cross and Cross3D because they have their own algorithms. Eventually we want to replace all that with the new algorithm. - //Cubic Subdivision ends lines in the center of the infill so it won't be effective. - connect_lines = zig_zaggify && (pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::GRID || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::TRIHEXAGON); } /*! * Generate the infill. - * + * * \param toolpaths (output) The resulting variable-width paths (from the extra walls around the pattern). Binned by inset_idx. * \param result_polygons (output) The resulting polygons (from concentric infill) * \param result_lines (output) The resulting line segments (from linear infill types) @@ -119,7 +196,17 @@ class Infill * \param mesh A mesh for which to generate infill (should only be used for non-helper-mesh objects). * \param[in] cross_fill_provider The cross fractal subdivision decision functor */ - void generate(std::vector& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, int layer_idx, SectionType section_type, const SierpinskiFillProvider* cross_fill_provider = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr); + void generate( + std::vector& toolpaths, + Polygons& result_polygons, + Polygons& result_lines, + const Settings& settings, + int layer_idx, + SectionType section_type, + const std::shared_ptr& cross_fill_provider = nullptr, + const std::shared_ptr& lightning_layer = nullptr, + const SliceMeshStorage* mesh = nullptr, + const Polygons& prevent_small_exposed_to_air = Polygons()); /*! * Generate the wall toolpaths of an infill area. It will return the inner contour and set the inner-contour. @@ -133,21 +220,37 @@ class Infill * \param settings [in] A settings storage to use for generating variable-width walls. * \return The inner contour of the wall toolpaths */ - static Polygons generateWallToolPaths(std::vector& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings, int layer_idx, SectionType section_type); + static Polygons generateWallToolPaths( + std::vector& toolpaths, + Polygons& outer_contour, + const size_t wall_line_count, + const coord_t line_width, + const coord_t infill_overlap, + const Settings& settings, + int layer_idx, + SectionType section_type); + private: /*! * Generate the infill pattern without the infill_multiplier functionality */ - void _generate(std::vector& toolpaths, Polygons& result_polygons, Polygons& result_lines, const Settings& settings, const SierpinskiFillProvider* cross_fill_pattern = nullptr, const LightningLayer * lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr); + void _generate( + std::vector& toolpaths, + Polygons& result_polygons, + Polygons& result_lines, + const Settings& settings, + const std::shared_ptr& cross_fill_pattern = nullptr, + const std::shared_ptr& lightning_layer = nullptr, + const SliceMeshStorage* mesh = nullptr); /*! * Multiply the infill lines, so that any single line becomes [infill_multiplier] lines next to each other. - * + * * This is done in a way such that there is not overlap between the lines * except the middle original one if the multiplier is odd. - * + * * This introduces a lot of line segments. - * + * * \param[in,out] result_polygons The polygons to be multiplied (input and output) * \param[in,out] result_lines The lines to be multiplied (input and output) */ @@ -165,21 +268,28 @@ class Infill */ InfillLineSegment(const Point start, const size_t start_segment, const size_t start_polygon, const Point end, const size_t end_segment, const size_t end_polygon) : start(start) + , altered_start(start) , start_segment(start_segment) , start_polygon(start_polygon) , end(end) + , altered_end(end) , end_segment(end_segment) , end_polygon(end_polygon) , previous(nullptr) - , next(nullptr) - { - }; + , next(nullptr){}; /*! * Where the line segment starts. */ Point start; + /*! + * If the line-segment starts at a different point due to prevention of crossing near the boundary, it gets saved here. + * + * The original start-point is still used to determine ordering then, so it can't just be overwritten. + */ + Point altered_start; + /*! * Which polygon line segment the start of this infill line belongs to. * @@ -197,11 +307,23 @@ class Infill */ size_t start_polygon; + /*! + * If the line-segment needs to prevent crossing with another line near its start, a point is inserted near the start. + */ + std::optional start_bend; + /*! * Where the line segment ends. */ Point end; + /*! + * If the line-segment ends at a different point due to prevention of crossing near the boundary, it gets saved here. + * + * The original end-point is still used to determine ordering then, so it can't just be overwritten. + */ + Point altered_end; + /*! * Which polygon line segment the end of this infill line belongs to. * @@ -219,6 +341,11 @@ class Infill */ size_t end_polygon; + /*! + * If the line-segment needs to prevent crossing with another line near its end, a point is inserted near the end. + */ + std::optional end_bend; + /*! * The previous line segment that this line segment is connected to, if * any. @@ -236,7 +363,21 @@ class Infill * This is necessary for putting line segments in a hash set. * \param other The line segment to compare this line segment with. */ - bool operator ==(const InfillLineSegment& other) const; + bool operator==(const InfillLineSegment& other) const; + + /*! + * Invert the direction of the line-segment. + * + * Useful when the next move is from end to start instead of 'forwards'. + */ + void swapDirection(); + + /*! + * Append this line-segment to the results, start, bends and end. + * + * \param include_start Wether to include the start point or not, useful when tracing a poly-line. + */ + void appendTo(PolygonRef& result_polyline, const bool include_start = true); }; /*! @@ -252,17 +393,17 @@ class Infill * \param result_polygons (output) The resulting polygons, if zigzagging accidentally happened to connect gyroid lines in a circle. */ void generateGyroidInfill(Polygons& result_polylines, Polygons& result_polygons); - + /*! * Generate lightning fill aka minfill aka 'Ribbed Support Vault Infill', see Tricard,Claux,Lefebvre/'Ribbed Support Vaults for 3D Printing of Hollowed Objects' * see https://hal.archives-ouvertes.fr/hal-02155929/document * \param result (output) The resulting polygons */ - void generateLightningInfill(const LightningLayer* lightning_layer, Polygons& result_lines); + void generateLightningInfill(const std::shared_ptr& lightning_layer, Polygons& result_lines); /*! * Generate sparse concentric infill - * + * * \param toolpaths (output) The resulting toolpaths. Binned by inset_idx. * \param inset_value The offset between each consecutive two polygons */ @@ -295,7 +436,7 @@ class Infill /*! * Generate a single shifting square grid of infill lines. * This is used in tetrahedral infill (Octet infill) and in Quarter Cubic infill. - * + * * \param pattern_z_shift The amount by which to shift the whole pattern down * \param angle_shift The angle to add to the infill_angle * \param[out] result (output) The resulting lines @@ -332,94 +473,101 @@ class Infill /*! * Convert a mapping from scanline to line_segment-scanline-intersections (\p cut_list) into line segments, using the even-odd rule * \param[out] result (output) The resulting lines - * \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill + * \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill * \param scanline_min_idx The lowest index of all scanlines crossing the polygon * \param line_distance The distance between two lines which are in the same direction * \param boundary The axis aligned boundary box within which the polygon is * \param cut_list A mapping of each scanline to all y-coordinates (in the space transformed by rotation_matrix) where the polygons are crossing the scanline * \param total_shift total shift of the scanlines in the direction perpendicular to the fill_angle. */ - void addLineInfill( Polygons& result, - const PointMatrix& rotation_matrix, - const int scanline_min_idx, - const int line_distance, - const AABB boundary, - std::vector>& cut_list, - coord_t total_shift); + void addLineInfill( + Polygons& result, + const PointMatrix& rotation_matrix, + const int scanline_min_idx, + const int line_distance, + const AABB boundary, + std::vector>& cut_list, + coord_t total_shift); /*! * generate lines within the area of \p in_outline, at regular intervals of \p line_distance - * + * * idea: * intersect a regular grid of 'scanlines' with the area inside \p in_outline - * + * * \param[out] result (output) The resulting lines * \param line_distance The distance between two lines which are in the same direction * \param infill_rotation The angle of the generated lines * \param extra_shift extra shift of the scanlines in the direction perpendicular to the infill_rotation */ void generateLineInfill(Polygons& result, int line_distance, const double& infill_rotation, coord_t extra_shift); - + /*! * Function for creating linear based infill types (Lines, ZigZag). - * + * * This function implements the basic functionality of Infill::generateLineInfill (see doc of that function), * but makes calls to a ZigzagConnectorProcessor which handles what to do with each line segment - scanline intersection. - * + * * It is called only from Infill::generateLineinfill and Infill::generateZigZagInfill. * * \param[out] result (output) The resulting lines * \param line_distance The distance between two lines which are in the same direction - * \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill + * \param rotation_matrix The rotation matrix (un)applied to enforce the angle of the infill * \param zigzag_connector_processor The processor used to generate zigzag connectors * \param connected_zigzags Whether to connect the endpiece zigzag segments on both sides to the same infill line * \param extra_shift extra shift of the scanlines in the direction perpendicular to the fill_angle */ - void generateLinearBasedInfill(Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, coord_t extra_shift); + void generateLinearBasedInfill( + Polygons& result, + const int line_distance, + const PointMatrix& rotation_matrix, + ZigzagConnectorProcessor& zigzag_connector_processor, + const bool connected_zigzags, + coord_t extra_shift); /*! - * + * * generate lines within the area of [in_outline], at regular intervals of [line_distance] * idea: * intersect a regular grid of 'scanlines' with the area inside [in_outline] (see generateLineInfill) * zigzag: * include pieces of boundary, connecting the lines, forming an accordion like zigzag instead of separate lines |_|^|_| - * + * * Note that ZigZag consists of 3 types: * - without endpieces * - with disconnected endpieces * - with connected endpieces - * + * * <-- * ___ * | | | * | | | * | |___| * --> - * + * * ^ = even scanline * ^ ^ no endpieces - * + * * start boundary from even scanline! :D - * - * + * + * * v disconnected end piece: leave out last line segment * _____ * | | | \ . * | | | | * |_____| |__/ - * + * * ^ ^ ^ scanlines - * - * + * + * * v connected end piece * ________ * | | | \ . * | | | | * |_____| |__/ . - * + * * ^ ^ ^ scanlines - * + * * \param[out] result (output) The resulting lines * \param line_distance The distance between two lines which are in the same direction * \param infill_rotation The angle of the generated lines @@ -435,6 +583,29 @@ class Infill */ coord_t getShiftOffsetFromInfillOriginAndRotation(const double& infill_rotation); + /*! + * Used to prevent intersections of linear-based infill. + * + * When connecting infill, and the infill crosses itself near the boundary, small 'loops' can occur, which have large internal angles. + * Prevent this by altering the two crossing line-segments just before the crossing takes place: + * + * \ / \ / + * \ / \ / + * X | | + * / \ | | + * --- - + * ======= ======= + * before after + * + * \param at_distance At which distance the offset of the bisector takes place (will be the length of the resulting connection along the edge). + * \param intersect The point at which these line-segments intersect. + * \param connect_start Input; the original point at the border which the first line-segment touches. Output; the updated point. + * \param connect_end Input; the original point at the border which the second line-segment touches. Output; the updated point. + * \param a The first line-segment. + * \param b The second line-segment. + */ + void resolveIntersection(const coord_t at_distance, const Point& intersect, Point& connect_start, Point& connect_end, InfillLineSegment* a, InfillLineSegment* b); + /*! * Connects infill lines together so that they form polylines. * @@ -445,7 +616,8 @@ class Infill */ void connectLines(Polygons& result_lines); }; +static_assert(concepts::semiregular, "Infill should be semiregular"); -}//namespace cura +} // namespace cura #endif // INFILL_H diff --git a/include/infill/LightningLayer.h b/include/infill/LightningLayer.h index 2ef03a4e0c..64540ea896 100644 --- a/include/infill/LightningLayer.h +++ b/include/infill/LightningLayer.h @@ -1,21 +1,20 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef LIGHTNING_LAYER_H #define LIGHTNING_LAYER_H -#include "../utils/polygonUtils.h" #include "../utils/SquareGrid.h" +#include "../utils/polygonUtils.h" +#include "infill/LightningTreeNode.h" -#include -#include #include +#include #include +#include namespace cura { -class LightningTreeNode; - using LightningTreeNodeSPtr = std::shared_ptr; using SparseLightningTreeNodeGrid = SparsePointGridInclusive>; @@ -36,28 +35,24 @@ class LightningLayer public: std::vector tree_roots; - void generateNewTrees - ( + void generateNewTrees( const Polygons& current_overhang, const Polygons& current_outlines, const LocToLineGrid& outline_locator, const coord_t supporting_radius, - const coord_t wall_supporting_radius - ); + const coord_t wall_supporting_radius); /*! Determine & connect to connection point in tree/outline. * \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree */ - GroundingLocation getBestGroundingLocation - ( + GroundingLocation getBestGroundingLocation( const Point& unsupported_location, const Polygons& current_outlines, const LocToLineGrid& outline_locator, const coord_t supporting_radius, const coord_t wall_supporting_radius, const SparseLightningTreeNodeGrid& tree_node_locator, - const LightningTreeNodeSPtr& exclude_tree = nullptr - ); + const LightningTreeNodeSPtr& exclude_tree = nullptr); /*! * \param[out] new_child The new child node introduced @@ -66,14 +61,12 @@ class LightningLayer */ bool attach(const Point& unsupported_location, const GroundingLocation& ground, LightningTreeNodeSPtr& new_child, LightningTreeNodeSPtr& new_root); - void reconnectRoots - ( + void reconnectRoots( std::vector& to_be_reconnected_tree_roots, const Polygons& current_outlines, const LocToLineGrid& outline_locator, const coord_t supporting_radius, - const coord_t wall_supporting_radius - ); + const coord_t wall_supporting_radius); Polygons convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const; diff --git a/include/infill/LightningTreeNode.h b/include/infill/LightningTreeNode.h index b24f1e37c5..21c63bde3b 100644 --- a/include/infill/LightningTreeNode.h +++ b/include/infill/LightningTreeNode.h @@ -1,26 +1,22 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef LIGHTNING_TREE_NODE_H #define LIGHTNING_TREE_NODE_H +#include "../utils/polygon.h" +#include "../utils/polygonUtils.h" + #include #include #include #include -#include "../utils/polygonUtils.h" -#include "../utils/polygon.h" - namespace cura { constexpr coord_t locator_cell_size = 4000; -class LightningTreeNode; - -using LightningTreeNodeSPtr = std::shared_ptr; - // NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next. // NOTE: Reasons for implementing this with some separate closures: // - keep clear deliniation during development @@ -37,13 +33,19 @@ using LightningTreeNodeSPtr = std::shared_ptr; */ class LightningTreeNode : public std::enable_shared_from_this { + using LightningTreeNodeSPtr = std::shared_ptr; + public: // Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765 - template LightningTreeNodeSPtr static create(Arg&&...arg) + template + LightningTreeNodeSPtr static create(Arg&&... arg) { struct EnableMakeShared : public LightningTreeNode { - EnableMakeShared(Arg&&...arg) : LightningTreeNode(std::forward(arg)...) {} + EnableMakeShared(Arg&&... arg) + : LightningTreeNode(std::forward(arg)...) + { + } }; return std::make_shared(std::forward(arg)...); } @@ -94,15 +96,13 @@ class LightningTreeNode : public std::enable_shared_from_this * \param max_remove_colinear_dist The maximum distance of a line-segment * from which straightening may remove a colinear point. */ - void propagateToNextLayer - ( + void propagateToNextLayer( std::vector& next_trees, const Polygons& next_outlines, const LocToLineGrid& outline_locator, const coord_t prune_distance, const coord_t smooth_magnitude, - const coord_t max_remove_colinear_dist - ) const; + const coord_t max_remove_colinear_dist) const; /*! * Executes a given function for every line segment in this node's sub-tree. @@ -147,7 +147,10 @@ class LightningTreeNode : public std::enable_shared_from_this * \return ``true`` if this node is the root (no parents) or ``false`` if it * is a child node of some other node. */ - bool isRoot() const { return is_root; } + bool isRoot() const + { + return is_root; + } /*! * Reverse the parent-child relationship all the way to the root, from this node onward. @@ -229,11 +232,11 @@ class LightningTreeNode : public std::enable_shared_from_this public: /*! * Convert the tree into polylines - * + * * At each junction one line is chosen at random to continue - * + * * The lines start at a leaf and end in a junction - * + * * \param output all branches in this tree connected into polylines */ void convertToPolylines(Polygons& output, const coord_t line_width) const; @@ -247,11 +250,11 @@ class LightningTreeNode : public std::enable_shared_from_this protected: /*! * Convert the tree into polylines - * + * * At each junction one line is chosen at random to continue - * + * * The lines start at a leaf and end in a junction - * + * * \param long_line a reference to a polyline in \p output which to continue building on in the recursion * \param output all branches in this tree connected into polylines */ @@ -264,7 +267,7 @@ class LightningTreeNode : public std::enable_shared_from_this std::weak_ptr parent; std::vector children; - std::optional last_grounding_location; // last_grounding_location; //, 8> children; //!< pointers to this cube's eight octree children static std::vector cube_properties_per_recursion_step; //!< precomputed array of basic properties of cubes based on recursion depth. static Ratio radius_multiplier; //!< multiplier for the bounding radius when determining if a cube should be subdivided static Point3Matrix rotation_matrix; //!< The rotation matrix to get from axis aligned cubes to cubes standing on a corner point aligned with the infill_angle @@ -107,5 +104,5 @@ class SubDivCube static coord_t radius_addition; //!< addition to the bounding radius when determining if a cube should be subdivided }; -} -#endif //INFILL_SUBDIVCUBE_H +} // namespace cura +#endif // INFILL_SUBDIVCUBE_H diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index e845fd1df4..da4efd30aa 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -1,105 +1,91 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef PATH_PLANNING_G_CODE_PATH_H #define PATH_PLANNING_G_CODE_PATH_H -#include "../SpaceFillType.h" -#include "../sliceDataStorage.h" -#include "../settings/types/Ratio.h" -#include "../utils/IntPoint.h" +#include +#include + +#include "GCodePathConfig.h" +#include "SpaceFillType.h" #include "TimeMaterialEstimates.h" +#include "settings/types/Ratio.h" +#include "sliceDataStorage.h" +#include "utils/IntPoint.h" -namespace cura +namespace cura { -class GCodePathConfig; - /*! * A class for representing a planned path. - * + * * A path consists of several segments of the same type of movement: retracted travel, infill extrusion, etc. - * + * * This is a compact premature representation in which are line segments have the same config, i.e. the config of this path. - * - * In the final representation (gcode) each line segment may have different properties, + * + * In the final representation (gcode) each line segment may have different properties, * which are added when the generated GCodePaths are processed. */ -class GCodePath +struct GCodePath { -public: - const GCodePathConfig* config; //!< The configuration settings of the path. - const SliceMeshStorage* mesh; //!< Which mesh this path belongs to, if any. If it's not part of any mesh, the mesh should be nullptr; - SpaceFillType space_fill_type; //!< The type of space filling of which this path is a part - Ratio flow; //!< A type-independent flow configuration - Ratio width_factor; //!< Adjustment to the line width. Similar to flow, but causes the speed_back_pressure_factor to be adjusted. - Ratio speed_factor; //!< A speed factor that is multiplied with the travel speed. This factor can be used to change the travel speed. - Ratio speed_back_pressure_factor; // mesh; //!< Which mesh this path belongs to, if any. If it's not part of any mesh, the mesh should be nullptr; + SpaceFillType space_fill_type{}; //!< The type of space filling of which this path is a part + Ratio flow{}; //!< A type-independent flow configuration + Ratio width_factor{}; //!< Adjustment to the line width. Similar to flow, but causes the speed_back_pressure_factor to be adjusted. + bool spiralize{}; //!< Whether to gradually increment the z position during the printing of this path. A sequence of spiralized paths should start at the given layer height and + //!< end in one layer higher. + Ratio speed_factor{ 1.0 }; //!< A speed factor that is multiplied with the travel speed. This factor can be used to change the travel speed. + Ratio speed_back_pressure_factor{ 1.0 }; // points; //!< The points constituting this path. - bool done; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. - - bool spiralize; //!< Whether to gradually increment the z position during the printing of this path. A sequence of spiralized paths should start at the given layer height and end in one layer higher. - - double fan_speed; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise - - TimeMaterialEstimates estimates; //!< Naive time and material estimates - - /*! - * \brief Creates a new g-code path. - * - * \param config The line configuration to use when printing this path. - * \param mesh_id The mesh that this path is part of. - * \param space_fill_type The type of space filling of which this path is a - * part. - * \param flow The flow rate to print this path with. - * \param width_factor A multiplier on the line width. - * \param spiralize Gradually increment the z-coordinate while traversing - * \param speed_factor The factor that the travel speed will be multiplied with - * this path. - */ - GCodePath(const GCodePathConfig& config, const SliceMeshStorage* mesh_id, const SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor = 1.0); + bool done{ false }; //!< Path is finished, no more moves should be added, and a new path should be started instead of any appending done to this one. + double fan_speed{ GCodePathConfig::FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise + TimeMaterialEstimates estimates{}; //!< Naive time and material estimates /*! * Whether this config is the config of a travel path. - * + * * \return Whether this config is the config of a travel path. */ - bool isTravelPath() const; + [[nodiscard]] bool isTravelPath() const noexcept; /*! * Get the material flow in mm^3 per mm traversed. - * + * * \warning Can only be called after the layer height has been set (which is done while writing the gcode!) - * + * * \return The flow */ - double getExtrusionMM3perMM() const; + [[nodiscard]] double getExtrusionMM3perMM() const noexcept; /*! * Get the actual line width (modulated by the flow) * \return the actual line width as shown in layer view */ - coord_t getLineWidthForLayerView() const; + [[nodiscard]] coord_t getLineWidthForLayerView() const noexcept; /*! * Set fan_speed * * \param fan_speed the fan speed to use for this path */ - void setFanSpeed(double fan_speed); + void setFanSpeed(const double fanspeed) noexcept; /*! * Get the fan speed for this path * \return the value of fan_speed if it is in the range 0-100, otherwise the value from the config */ - double getFanSpeed() const; + [[nodiscard]] double getFanSpeed() const noexcept; }; -}//namespace cura +} // namespace cura -#endif//PATH_PLANNING_G_CODE_PATH_H +#endif // PATH_PLANNING_G_CODE_PATH_H diff --git a/include/pathPlanning/SpeedDerivatives.h b/include/pathPlanning/SpeedDerivatives.h new file mode 100644 index 0000000000..d4f47788b7 --- /dev/null +++ b/include/pathPlanning/SpeedDerivatives.h @@ -0,0 +1,38 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PATHPLANNING_SPEEDDERIVATIVES_H +#define PATHPLANNING_SPEEDDERIVATIVES_H + +#include "settings/types/LayerIndex.h" +#include "settings/types/Velocity.h" + +namespace cura +{ + +struct SpeedDerivatives +{ + Velocity speed{}; //!< movement speed (mm/s) + Acceleration acceleration{}; //!< acceleration of head movement (mm/s^2) + Velocity jerk{}; //!< jerk of the head movement (around stand still) as instantaneous speed change (mm/s) + + constexpr bool operator==(const SpeedDerivatives& other) const noexcept = default; + constexpr auto operator<=>(const SpeedDerivatives& other) const noexcept = default; + + /*! + * Set the speed to somewhere between the speed of @p first_layer_config and the iconic speed. + * + * \warning This functions should not be called with @p layer_nr > @p max_speed_layer ! + * + * \warning Calling this function twice will smooth the speed more toward \p first_layer_config + * + * \param first_layer_config The speed settings at layer zero + * \param layer_nr The layer number + * \param max_speed_layer The layer number for which the speed_iconic should be used. + */ + void smoothSpeed(const SpeedDerivatives& first_layer_config, const LayerIndex layer_nr, const LayerIndex max_speed_layer_nr); +}; + +} // namespace cura + +#endif // PATHPLANNING_SPEEDDERIVATIVES_H diff --git a/include/pathPlanning/TimeMaterialEstimates.h b/include/pathPlanning/TimeMaterialEstimates.h index 4a00526553..4b30011a5e 100644 --- a/include/pathPlanning/TimeMaterialEstimates.h +++ b/include/pathPlanning/TimeMaterialEstimates.h @@ -1,126 +1,86 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H #define PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H -namespace cura +namespace cura { -class ExtruderPlan; // forward declaration so that TimeMaterialEstimates can be a friend - -/*! - * Time and material estimates for a portion of paths, e.g. layer, extruder plan, path. - */ -class TimeMaterialEstimates +struct TimeMaterialEstimates { - friend class ExtruderPlan; // cause there the naive estimates are calculated -private: - double extrude_time; //!< Time in seconds occupied by extrusion - double extrude_time_at_slowest_path_speed; //!< Time in seconds occupied by extrusion assuming paths are printed at slowest path speed, usually the outer wall speed - double extrude_time_at_minimum_speed; //!< Time in seconds occupied by extrusion assuming paths are printed at the user specified Minimum Speed - double unretracted_travel_time; //!< Time in seconds occupied by non-retracted travel (non-extrusion) - double retracted_travel_time; //!< Time in seconds occupied by retracted travel (non-extrusion) - double material; //!< Material used (in mm^3) -public: - /*! - * Basic contructor - * - * \param extrude_time Time in seconds occupied by extrusion - * \param unretracted_travel_time Time in seconds occupied by non-retracted travel (non-extrusion) - * \param retracted_travel_time Time in seconds occupied by retracted travel (non-extrusion) - * \param material Material used (in mm^3) - */ - TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material); - - /*! - * Basic constructor initializing all estimates to zero. - */ - TimeMaterialEstimates(); - - /*! - * Set all estimates to zero. - */ - void reset(); - - /*! - * Pointwise addition of estimate stats - * - * \param other The estimates to add to these estimates. - * \return The resulting estimates - */ - TimeMaterialEstimates operator+(const TimeMaterialEstimates& other); - - /*! - * In place pointwise addition of estimate stats - * - * \param other The estimates to add to these estimates. - * \return These estimates - */ - TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other); - - /*! - * \brief Subtracts the specified estimates from these estimates and returns - * the result. - * - * Each of the estimates in this class are individually subtracted. - * - * \param other The estimates to subtract from these estimates. - * \return These estimates with the specified estimates subtracted. - */ - TimeMaterialEstimates operator-(const TimeMaterialEstimates& other); - - /*! - * \brief Subtracts the specified elements from these estimates. - * - * This causes the estimates in this instance to change. Each of the - * estimates in this class are individually subtracted. - * - * \param other The estimates to subtract from these estimates. - * \return A reference to this instance. - */ - TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other); - - /*! - * Get total time estimate. The different time estimate member values added together. - * - * \return the total of all different time estimate values - */ - double getTotalTime() const; - - /*! - * Get the total time during which the head is not retracted. - * - * This includes extrusion time and non-retracted travel time - * - * \return the total time during which the head is not retracted. - */ - double getTotalUnretractedTime() const; - - /*! - * Get the total travel time. - * - * This includes the retracted travel time as well as the unretracted travel time. - * - * \return the total travel time. - */ - double getTravelTime() const; - - /*! - * Get the extrusion time. - * - * \return extrusion time. - */ - double getExtrudeTime() const; - - /*! - * Get the amount of material used in mm^3. - * - * \return amount of material - */ - double getMaterial() const; + double extrude_time{ 0.0 }; //!< Time in seconds occupied by extrusion + double unretracted_travel_time{ 0.0 }; //!< Time in seconds occupied by non-retracted travel (non-extrusion) + double retracted_travel_time{ 0.0 }; //!< Time in seconds occupied by retracted travel (non-extrusion) + double material{ 0.0 }; //!< Material used (in mm^3) + double extrude_time_at_slowest_path_speed{ 0.0 }; //!< Time in seconds occupied by extrusion assuming paths are printed at slowest path speed, usually the outer wall speed + double extrude_time_at_minimum_speed{ 0.0 }; //!< Time in seconds occupied by extrusion assuming paths are printed at the user specified Minimum Speed + + constexpr TimeMaterialEstimates& operator+=(const TimeMaterialEstimates& other) noexcept + { + extrude_time += other.extrude_time; + extrude_time_at_slowest_path_speed += other.extrude_time_at_slowest_path_speed; + extrude_time_at_minimum_speed += other.extrude_time_at_minimum_speed; + unretracted_travel_time += other.unretracted_travel_time; + retracted_travel_time += other.retracted_travel_time; + material += other.material; + return *this; + } + + constexpr TimeMaterialEstimates& operator-=(const TimeMaterialEstimates& other) noexcept + { + extrude_time -= other.extrude_time; + extrude_time_at_slowest_path_speed -= other.extrude_time_at_slowest_path_speed; + extrude_time_at_minimum_speed -= other.extrude_time_at_minimum_speed; + unretracted_travel_time -= other.unretracted_travel_time; + retracted_travel_time -= other.retracted_travel_time; + material -= other.material; + return *this; + } + + constexpr TimeMaterialEstimates operator+(const TimeMaterialEstimates& other) const noexcept + { + return { .extrude_time = extrude_time + other.extrude_time, + .unretracted_travel_time = unretracted_travel_time + other.unretracted_travel_time, + .retracted_travel_time = retracted_travel_time + other.retracted_travel_time, + .material = material + other.material, + .extrude_time_at_slowest_path_speed = extrude_time_at_slowest_path_speed + other.extrude_time_at_slowest_path_speed, + .extrude_time_at_minimum_speed = extrude_time_at_minimum_speed + other.extrude_time_at_minimum_speed }; + }; + + constexpr TimeMaterialEstimates operator-(const TimeMaterialEstimates& other) const noexcept + { + return { .extrude_time = extrude_time - other.extrude_time, + .unretracted_travel_time = unretracted_travel_time - other.unretracted_travel_time, + .retracted_travel_time = retracted_travel_time - other.retracted_travel_time, + .material = material - other.material, + .extrude_time_at_slowest_path_speed = extrude_time_at_slowest_path_speed - other.extrude_time_at_slowest_path_speed, + .extrude_time_at_minimum_speed = extrude_time_at_minimum_speed - other.extrude_time_at_minimum_speed }; + }; + + constexpr auto operator<=>(const TimeMaterialEstimates& other) const noexcept = default; + + constexpr void reset() noexcept + { + *this = TimeMaterialEstimates{}; + } + + [[nodiscard]] constexpr auto getTotalTime() const noexcept + { + return extrude_time + unretracted_travel_time + retracted_travel_time; + } + + [[nodiscard]] constexpr auto getTotalUnretractedTime() const noexcept + { + return extrude_time + unretracted_travel_time; + } + + [[nodiscard]] constexpr auto getTravelTime() const noexcept + { + return retracted_travel_time + unretracted_travel_time; + } }; -}//namespace cura +} // namespace cura -#endif//PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H +#endif // PATH_PLANNING_TIME_MATERIAL_ESTIMATES_H diff --git a/include/plugins/broadcasts.h b/include/plugins/broadcasts.h new file mode 100644 index 0000000000..b73b753a64 --- /dev/null +++ b/include/plugins/broadcasts.h @@ -0,0 +1,53 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_BROADCAST_H +#define PLUGINS_BROADCAST_H + +#include "cura/plugins/slots/broadcast/v0/broadcast.grpc.pb.h" +#include "cura/plugins/v0/slot_id.pb.h" +#include "plugins/converters.h" + +#include +#include + +#include + +namespace cura::plugins::details +{ + +template +struct is_broadcast_channel +{ + inline static constexpr bool value{ T1 == T2 }; +}; + +template +inline constexpr bool is_broadcast_channel_v = is_broadcast_channel::value; + +template +struct broadcast_stub +{ + using derived_type = T; + friend derived_type; + + constexpr auto operator()(auto&&... args) + { + return request_(std::forward(args)...); + } + +private: + C request_{}; +}; + +template +requires is_broadcast_channel_v +struct broadcast_rpc : public broadcast_stub, broadcast_settings_request> +{ + using ClientRPC = agrpc::ClientRPC<&Stub::PrepareAsyncBroadcastSettings>; +}; + + +} // namespace cura::plugins::details + +#endif // PLUGINS_BROADCAST_H \ No newline at end of file diff --git a/include/plugins/converters.h b/include/plugins/converters.h new file mode 100644 index 0000000000..01870b2056 --- /dev/null +++ b/include/plugins/converters.h @@ -0,0 +1,132 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_CONVERTERS_H +#define PLUGINS_CONVERTERS_H + +#include "Cura.pb.h" +#include "WallToolPaths.h" +#include "cura/plugins/slots/broadcast/v0/broadcast.grpc.pb.h" +#include "cura/plugins/slots/broadcast/v0/broadcast.pb.h" +#include "cura/plugins/slots/gcode_paths/v0/modify.grpc.pb.h" +#include "cura/plugins/slots/gcode_paths/v0/modify.pb.h" +#include "cura/plugins/slots/handshake/v0/handshake.grpc.pb.h" +#include "cura/plugins/slots/handshake/v0/handshake.pb.h" +#include "cura/plugins/slots/infill/v0/generate.grpc.pb.h" +#include "cura/plugins/slots/infill/v0/generate.pb.h" +#include "cura/plugins/slots/postprocess/v0/modify.grpc.pb.h" +#include "cura/plugins/slots/postprocess/v0/modify.pb.h" +#include "cura/plugins/slots/simplify/v0/modify.grpc.pb.h" +#include "cura/plugins/slots/simplify/v0/modify.pb.h" +#include "pathPlanning/GCodePath.h" +#include "pathPlanning/SpeedDerivatives.h" +#include "plugins/metadata.h" +#include "plugins/types.h" +#include "settings/Settings.h" +#include "settings/types/LayerIndex.h" +#include "utils/polygon.h" + +#include +#include +#include + +#include +#include +#include + + +namespace cura::plugins +{ + +namespace details +{ +template +struct converter +{ + using derived_type = T; ///< The derived type. + using value_type = Msg; ///< The protobuf message type. + using native_value_type = Native; ///< The native value type. + friend derived_type; + + constexpr auto operator()(auto&&... args) const + { + return static_cast(this)->operator()(std::forward(args)...); + } +}; +} // namespace details + +struct empty +{ + using value_type = google::protobuf::Empty; ///< The protobuf message type. + using native_value_type = std::nullptr_t; ///< The native value type. + + value_type operator()() const; + + constexpr native_value_type operator()(const value_type&) const; +}; + +struct broadcast_settings_request : public details::converter +{ + value_type operator()(const native_value_type& slice_message) const; +}; + +struct handshake_request : public details::converter +{ + value_type operator()(const std::string& name, const std::string& version, const native_value_type& slot_info) const; +}; + +struct handshake_response : public details::converter +{ + native_value_type operator()(const value_type& message, std::string_view peer) const; +}; + +struct simplify_request : public details::converter +{ + value_type operator()(const native_value_type& polygons, const coord_t max_resolution, const coord_t max_deviation, const coord_t max_area_deviation) const; +}; + +struct simplify_response : public details::converter +{ + native_value_type operator()([[maybe_unused]] const native_value_type& original_value, const value_type& message) const; +}; + +struct postprocess_request : public details::converter +{ + value_type operator()(const native_value_type& gcode) const; +}; + +struct postprocess_response : public details::converter +{ + native_value_type operator()([[maybe_unused]] const native_value_type& original_value, const value_type& message) const; +}; + +struct infill_generate_request : public details::converter +{ + value_type operator()(const native_value_type& inner_contour, const std::string& pattern, const Settings& settings) const; +}; + +struct infill_generate_response + : public details::converter>, Polygons, Polygons>> +{ + native_value_type operator()(const value_type& message) const; +}; + +struct gcode_paths_modify_request : public details::converter> +{ + [[nodiscard]] static constexpr v0::SpaceFillType getSpaceFillType(const SpaceFillType space_fill_type) noexcept; + [[nodiscard]] static constexpr v0::PrintFeature getPrintFeature(const PrintFeatureType print_feature_type) noexcept; + value_type operator()(const native_value_type& gcode, const size_t extruder_nr, const LayerIndex layer_nr) const; +}; + +struct gcode_paths_modify_response : public details::converter> +{ + [[nodiscard]] static constexpr PrintFeatureType getPrintFeatureType(const v0::PrintFeature feature) noexcept; + [[nodiscard]] static GCodePathConfig buildConfig(const v0::GCodePath& path); + [[nodiscard]] static constexpr SpaceFillType getSpaceFillType(const v0::SpaceFillType space_fill_type) noexcept; + native_value_type operator()(native_value_type& original_value, const value_type& message) const; +}; + +} // namespace cura::plugins + + +#endif diff --git a/include/plugins/exception.h b/include/plugins/exception.h new file mode 100644 index 0000000000..f593c2d69b --- /dev/null +++ b/include/plugins/exception.h @@ -0,0 +1,74 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_EXCEPTION_H +#define PLUGINS_EXCEPTION_H + +#include "plugins/metadata.h" +#include "plugins/types.h" +#include "plugins/validator.h" + +#include + +#include +#include +#include +#include + +namespace cura::plugins::exceptions +{ + +class ValidatorException : public std::exception +{ + std::string msg_; + +public: + ValidatorException(const auto& validator, const slot_metadata& slot_info) noexcept + : msg_(fmt::format("Failed to validation plugin on Slot '{}'", slot_info.slot_id)){}; + + ValidatorException(const auto& validator, const slot_metadata& slot_info, const plugin_metadata& plugin_info) noexcept + : msg_(fmt::format( + "Failed to validate plugin '{}-{}' running at [{}] for slot '{}', version '{}' incompatible with plugin specified slot-version-range '{}'.", + plugin_info.plugin_name, + plugin_info.plugin_version, + plugin_info.peer, + slot_info.slot_id, + slot_info.version, + plugin_info.slot_version_range)) + { + } + + virtual const char* what() const noexcept override + { + return msg_.c_str(); + } +}; + +class RemoteException : public std::exception +{ + std::string msg_; + +public: + RemoteException(const slot_metadata& slot_info, std::string_view error_msg) noexcept + : msg_(fmt::format("Remote exception on Slot '{}': {}", slot_info.slot_id, error_msg)){}; + + RemoteException(const slot_metadata& slot_info, const plugin_metadata& plugin_info, std::string_view error_msg) noexcept + : msg_(fmt::format( + "Remote exception for plugin '{}-{}' running at [{}] for slot '{}': {}", + plugin_info.plugin_name, + plugin_info.plugin_version, + plugin_info.peer, + slot_info.slot_id, + error_msg)) + { + } + + virtual const char* what() const noexcept override + { + return msg_.c_str(); + } +}; + +} // namespace cura::plugins::exceptions + +#endif // PLUGINS_EXCEPTION_H \ No newline at end of file diff --git a/include/plugins/metadata.h b/include/plugins/metadata.h new file mode 100644 index 0000000000..af3e210342 --- /dev/null +++ b/include/plugins/metadata.h @@ -0,0 +1,37 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_INCLUDE_PLUGINS_METADATA_H +#define CURAENGINE_INCLUDE_PLUGINS_METADATA_H + +#include "cura/plugins/v0/slot_id.pb.h" +#include "plugins/types.h" + +#include +#include +#include +#include +#include + +namespace cura::plugins +{ + +struct plugin_metadata +{ + std::string slot_version_range; + std::string plugin_name; + std::string plugin_version; + std::string peer; + std::set broadcast_subscriptions; +}; + +struct slot_metadata +{ + plugins::v0::SlotID slot_id; + std::string_view version; + std::string_view engine_uuid; +}; + +} // namespace cura::plugins + +#endif // CURAENGINE_INCLUDE_PLUGINS_METADATA_H diff --git a/include/plugins/pluginproxy.h b/include/plugins/pluginproxy.h new file mode 100644 index 0000000000..2ff7b5c0c3 --- /dev/null +++ b/include/plugins/pluginproxy.h @@ -0,0 +1,350 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_PLUGINPROXY_H +#define PLUGINS_PLUGINPROXY_H + +#include "Application.h" +#include "cura/plugins/slots/broadcast/v0/broadcast.grpc.pb.h" +#include "cura/plugins/slots/broadcast/v0/broadcast.pb.h" +#include "cura/plugins/slots/handshake/v0/handshake.grpc.pb.h" +#include "cura/plugins/slots/handshake/v0/handshake.pb.h" +#include "cura/plugins/v0/slot_id.pb.h" +#include "plugins/broadcasts.h" +#include "plugins/exception.h" +#include "plugins/metadata.h" +#include "utils/format/thread_id.h" +#include "utils/types/char_range_literal.h" +#include "utils/types/generic.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if __has_include() +#include +#elif __has_include() +#include +#define USE_EXPERIMENTAL_COROUTINE +#endif +#include +#include +#include +#include + +namespace cura::plugins +{ + +/** + * @brief A plugin proxy class template. + * + * Template arguments are: + * SlotID - plugin slot ID + * SlotVersion - slot version used -- will be the only version available in the engine for that slot, since there is just the latest version of the slot a.t.m. + * Stub - process stub type + * ValidatorTp - validator type + * RequestTp - gRPC convertible request type, or dummy -- if stub is a proper invoke-stub, this is enforced by the specialization of the invoke component + * ResponseTp - gRPC convertible response type, or dummy -- if stub is a proper invoke-stub, this is enforced by the specialization of the invoke component + * + * Class provides methods for validating the plugin, making requests and processing responses. + */ +template +class PluginProxy +{ + // type aliases for easy use + using value_type = typename ResponseTp::native_value_type; + using validator_type = ValidatorTp; + using req_converter_type = RequestTp; + using rsp_converter_type = ResponseTp; + using rsp_msg_type = typename ResponseTp::value_type; + using invoke_stub_t = Stub; + using broadcast_stub_t = slots::broadcast::v0::BroadcastService::Stub; + + ranges::semiregular_box invoke_stub_; ///< The gRPC Invoke stub for communication. + ranges::semiregular_box broadcast_stub_; ///< The gRPC Broadcast stub for communication. +public: + /** + * @brief Constructs a PluginProxy object. + * + * This constructor initializes the PluginProxy object by establishing communication + * channels with the plugin identified by the given slot ID. It performs plugin validation + * and checks for communication errors. + * + * @param channel A shared pointer to the gRPC channel for communication with the plugin. + * + * @throws std::runtime_error if the plugin fails validation or communication errors occur. + */ + constexpr PluginProxy() = default; + + PluginProxy(const std::string& name, const std::string& version, std::shared_ptr channel) + : invoke_stub_{ channel } + , broadcast_stub_{ channel } + { + // Connect to the plugin and exchange a handshake + agrpc::GrpcContext grpc_context; + grpc::Status status; + slots::handshake::v0::HandshakeService::Stub handshake_stub(channel); + plugin_metadata plugin_info; + + boost::asio::co_spawn( + grpc_context, + [this, &grpc_context, &status, &plugin_info, &handshake_stub, &name, &version]() -> boost::asio::awaitable + { + using RPC = agrpc::ClientRPC<&slots::handshake::v0::HandshakeService::Stub::PrepareAsyncCall>; + grpc::ClientContext client_context{}; + prep_client_context(client_context, slot_info_); + + // Construct request + handshake_request handshake_req; + handshake_request::value_type request{ handshake_req(name, version, slot_info_) }; + + // Make unary request + handshake_response::value_type response; + status = co_await RPC::request(grpc_context, handshake_stub, client_context, request, response, boost::asio::use_awaitable); + handshake_response handshake_rsp; + plugin_info = handshake_rsp(response, client_context.peer()); + valid_ = validator_type{ slot_info_, plugin_info }; + if (valid_) + { + spdlog::info("Using plugin: '{}-{}' running at [{}] for slot {}", plugin_info.plugin_name, plugin_info.plugin_version, plugin_info.peer, slot_info_.slot_id); + if (! plugin_info.broadcast_subscriptions.empty()) + { + spdlog::info("Subscribing plugin '{}' to the following broadcasts {}", plugin_info.plugin_name, plugin_info.broadcast_subscriptions); + } + } + }, + boost::asio::detached); + grpc_context.run(); + + if (! status.ok()) // TODO: handle different kind of status codes + { + spdlog::error(status.error_message()); + throw exceptions::RemoteException(slot_info_, status.error_message()); + } + if (! plugin_info.plugin_name.empty() && ! plugin_info.slot_version_range.empty()) + { + plugin_info_.emplace(plugin_info); + } + }; + + constexpr PluginProxy(const PluginProxy&) = default; + constexpr PluginProxy(PluginProxy&&) noexcept = default; + constexpr PluginProxy& operator=(const PluginProxy& other) + { + if (this != &other) + { + invoke_stub_ = other.invoke_stub_; + broadcast_stub_ = other.broadcast_stub_; + valid_ = other.valid_; + plugin_info_ = other.plugin_info_; + slot_info_ = other.slot_info_; + } + return *this; + } + constexpr PluginProxy& operator=(PluginProxy&& other) noexcept + { + if (this != &other) + { + invoke_stub_ = std::move(other.invoke_stub_); + broadcast_stub_ = std::move(other.broadcast_stub_); + valid_ = std::move(other.valid_); + plugin_info_ = std::move(other.plugin_info_); + slot_info_ = std::move(other.slot_info_); + } + return *this; + } + ~PluginProxy() = default; + + value_type generate(auto&&... args) + { + agrpc::GrpcContext grpc_context; + value_type ret_value{}; + grpc::Status status; + + boost::asio::co_spawn( + grpc_context, + [this, &grpc_context, &status, &ret_value, &args...]() + { + return this->generateCall(grpc_context, status, ret_value, std::forward(args)...); + }, + boost::asio::detached); + grpc_context.run(); + + if (! status.ok()) // TODO: handle different kind of status codes + { + if (plugin_info_.has_value()) + { + spdlog::error( + "Plugin '{}' running at [{}] for slot {} failed with error: {}", + plugin_info_.value().plugin_name, + plugin_info_.value().peer, + slot_info_.slot_id, + status.error_message()); + throw exceptions::RemoteException(slot_info_, plugin_info_.value(), status.error_message()); + } + spdlog::error("Plugin for slot {} failed with error: {}", slot_info_.slot_id, status.error_message()); + throw exceptions::RemoteException(slot_info_, status.error_message()); + } + return ret_value; + } + + value_type modify(auto& original_value, auto&&... args) + { + agrpc::GrpcContext grpc_context; + value_type ret_value{}; + grpc::Status status; + + boost::asio::co_spawn( + grpc_context, + [this, &grpc_context, &status, &ret_value, &original_value, &args...]() + { + return this->modifyCall(grpc_context, status, ret_value, original_value, std::forward(args)...); + }, + boost::asio::detached); + grpc_context.run(); + + if (! status.ok()) // TODO: handle different kind of status codes + { + if (plugin_info_.has_value()) + { + spdlog::error( + "Plugin '{}' running at [{}] for slot {} failed with error: {}", + plugin_info_.value().plugin_name, + plugin_info_.value().peer, + slot_info_.slot_id, + status.error_message()); + throw exceptions::RemoteException(slot_info_, plugin_info_.value(), status.error_message()); + } + spdlog::error("Plugin for slot {} failed with error: {}", slot_info_.slot_id, status.error_message()); + throw exceptions::RemoteException(slot_info_, status.error_message()); + } + return ret_value; + } + + template + void broadcast(auto&&... args) + { + if (! plugin_info_.value().broadcast_subscriptions.contains(Subscription)) + { + return; + } + agrpc::GrpcContext grpc_context; + grpc::Status status; + + boost::asio::co_spawn( + grpc_context, + [this, &grpc_context, &status, &args...]() + { + return this->broadcastCall(grpc_context, status, std::forward(args)...); + }, + boost::asio::detached); + grpc_context.run(); + + if (! status.ok()) // TODO: handle different kind of status codes + { + if (plugin_info_.has_value()) + { + spdlog::error( + "Plugin '{}' running at [{}] for slot {} failed with error: {}", + plugin_info_.value().plugin_name, + plugin_info_.value().peer, + slot_info_.slot_id, + status.error_message()); + throw exceptions::RemoteException(slot_info_, plugin_info_.value(), status.error_message()); + } + spdlog::error("Plugin for slot {} failed with error: {}", slot_info_.slot_id, status.error_message()); + throw exceptions::RemoteException(slot_info_, status.error_message()); + } + } + +private: + inline static void prep_client_context(grpc::ClientContext& client_context, const slot_metadata& slot_info, const std::chrono::milliseconds& timeout = std::chrono::minutes(5)) + { + // Set time-out + client_context.set_deadline(std::chrono::system_clock::now() + timeout); + + // Metadata + client_context.AddMetadata("cura-engine-uuid", slot_info.engine_uuid.data()); + client_context.AddMetadata("cura-thread-id", fmt::format("{}", std::this_thread::get_id())); + } + + /** + * @brief Executes the invokeCall operation with the plugin. + * + * Sends a request to the plugin and saves the response. + * + * @param grpc_context - The gRPC context to use for the call + * @param status - Status of the gRPC call which gets updated in this method + * @param ret_value - Reference to the value in which response to be stored + * @param args - Request arguments + * @return A boost::asio::awaitable indicating completion of the operation + */ + boost::asio::awaitable generateCall(agrpc::GrpcContext& grpc_context, grpc::Status& status, value_type& ret_value, auto&&... args) + { + using RPC = agrpc::ClientRPC<&invoke_stub_t::PrepareAsyncCall>; + grpc::ClientContext client_context{}; + prep_client_context(client_context, slot_info_); + + // Construct request + auto request{ req_(std::forward(args)...) }; + + // Make unary request + rsp_msg_type response; + status = co_await RPC::request(grpc_context, invoke_stub_, client_context, request, response, boost::asio::use_awaitable); + ret_value = rsp_(response); + co_return; + } + + boost::asio::awaitable modifyCall(agrpc::GrpcContext& grpc_context, grpc::Status& status, value_type& ret_value, auto& original_value, auto&&... args) + { + using RPC = agrpc::ClientRPC<&invoke_stub_t::PrepareAsyncCall>; + grpc::ClientContext client_context{}; + prep_client_context(client_context, slot_info_); + + // Construct request + auto request{ req_(original_value, std::forward(args)...) }; + + // Make unary request + rsp_msg_type response; + status = co_await RPC::request(grpc_context, invoke_stub_, client_context, request, response, boost::asio::use_awaitable); + ret_value = std::move(rsp_(original_value, response)); + co_return; + } + + template + boost::asio::awaitable broadcastCall(agrpc::GrpcContext& grpc_context, grpc::Status& status, auto&&... args) + { + grpc::ClientContext client_context{}; + prep_client_context(client_context, slot_info_); + using RPC = agrpc::ClientRPC<&broadcast_stub_t::PrepareAsyncBroadcastSettings>; + + details::broadcast_rpc requester{}; + auto request = requester(std::forward(args)...); + + auto response = google::protobuf::Empty{}; + status = co_await RPC::request(grpc_context, broadcast_stub_, client_context, request, response, boost::asio::use_awaitable); + co_return; + } + + validator_type valid_{}; ///< The validator object for plugin validation. + req_converter_type req_{}; ///< The Invoke request converter object. + rsp_converter_type rsp_{}; ///< The Invoke response converter object. + slot_metadata slot_info_{ .slot_id = SlotID, + .version = SlotVersion.value, + .engine_uuid = Application::getInstance().instance_uuid }; ///< Holds information about the plugin slot. + std::optional plugin_info_{ std::optional(std::nullopt) }; ///< Optional object that holds the plugin metadata, set after handshake +}; + +} // namespace cura::plugins + +#endif // PLUGINS_PLUGINPROXY_H diff --git a/include/plugins/slotproxy.h b/include/plugins/slotproxy.h new file mode 100644 index 0000000000..037db10a19 --- /dev/null +++ b/include/plugins/slotproxy.h @@ -0,0 +1,112 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_SLOTPROXY_H +#define PLUGINS_SLOTPROXY_H + +#include "plugins/converters.h" +#include "plugins/pluginproxy.h" +#include "plugins/types.h" +#include "plugins/validator.h" +#include "utils/types/char_range_literal.h" + +#include + +#include +#include +#include +#include +#include + +namespace cura::plugins +{ + +/** + * @brief A class template representing a proxy for a plugin slot. + * + * The SlotProxy class template acts as a proxy for a plugin slot and provides an interface + * for communication with plugins assigned to the slot. It delegates plugin requests to the + * corresponding PluginProxy object and provides a default behavior when no plugin is available. + * + * @tparam SlotID The plugin slot ID. + * @tparam SlotVersion The version of the indicated slot. + * @tparam Stub The process stub type. + * @tparam ValidatorTp The type used for validating the plugin. + * @tparam RequestTp The gRPC convertible request type. + * @tparam ResponseTp The gRPC convertible response type. + * @tparam Default The default behavior when no plugin is available. + */ +template +class SlotProxy +{ + Default default_process{}; + using value_type = PluginProxy; + std::optional plugin_{ std::nullopt }; + +public: + static constexpr plugins::v0::SlotID slot_id{ SlotID }; + + /** + * @brief Default constructor. + * + * Constructs a SlotProxy object without initializing the plugin. + */ + SlotProxy() noexcept = default; + + /** + * @brief Constructs a SlotProxy object with a plugin. + * + * Constructs a SlotProxy object and initializes the plugin using the provided gRPC channel. + * + * @param channel A shared pointer to the gRPC channel for communication with the plugin. + */ + SlotProxy(const std::string& name, const std::string& version, std::shared_ptr channel) + : plugin_{ value_type{ name, version, channel } } {}; + + /** + * @brief Executes the plugin operation. + * + * This operator allows the SlotProxy object to be invoked as a callable, which delegates the + * plugin request to the corresponding PluginProxy object if available. If no plugin is available, + * it invokes the default behavior provided by the `Default` callable object. + * + * @tparam Args The argument types for the plugin request. + * @param args The arguments for the plugin request. + * @return The result of the plugin request or the default behavior. + */ + constexpr auto generate(auto&&... args) + { + if (plugin_.has_value()) + { + return plugin_.value().generate(std::forward(args)...); + } + return std::invoke(default_process, std::forward(args)...); + } + + constexpr auto modify(auto& original_value, auto&&... args) + { + if (plugin_.has_value()) + { + return plugin_.value().modify(original_value, std::forward(args)...); + } + if constexpr (sizeof...(args) == 0) + { + return std::invoke(default_process, original_value); + } + return std::invoke(default_process, original_value, std::forward(args)...); + } + + template + void broadcast(auto&&... args) + { + if (plugin_.has_value()) + { + plugin_.value().template broadcast(std::forward(args)...); + } + } +}; + +} // namespace cura::plugins + + +#endif // PLUGINS_SLOTPROXY_H diff --git a/include/plugins/slots.h b/include/plugins/slots.h new file mode 100644 index 0000000000..ed7c91a4e6 --- /dev/null +++ b/include/plugins/slots.h @@ -0,0 +1,240 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_SLOTS_H +#define PLUGINS_SLOTS_H + +#include "WallToolPaths.h" +#include "cura/plugins/slots/broadcast/v0/broadcast.grpc.pb.h" +#include "cura/plugins/slots/gcode_paths/v0/modify.grpc.pb.h" +#include "cura/plugins/slots/infill/v0/generate.grpc.pb.h" +#include "cura/plugins/slots/postprocess/v0/modify.grpc.pb.h" +#include "cura/plugins/slots/simplify/v0/modify.grpc.pb.h" +#include "cura/plugins/v0/slot_id.pb.h" +#include "infill.h" +#include "plugins/converters.h" +#include "plugins/slotproxy.h" +#include "plugins/types.h" +#include "plugins/validator.h" +#include "utils/IntPoint.h" +#include "utils/Simplify.h" // TODO: Remove once the simplify slot has been removed +#include "utils/polygon.h" +#include "utils/types/char_range_literal.h" + +#include +#include +#include +#include + +namespace cura +{ +namespace plugins +{ +namespace details +{ +struct default_process +{ + constexpr auto operator()(auto&& arg, auto&&...) + { + return std::forward(arg); + }; +}; + +struct simplify_default +{ + auto operator()(auto&& arg, auto&&... args) + { + const Simplify simplify{ std::forward(args)... }; + return simplify.polygon(std::forward(arg)); + } +}; + +struct infill_generate_default +{ + std::tuple, Polygons, Polygons> operator()(auto&&... args) + { + // this code is only reachable when no slot is registered while the infill type is requested to be + // generated by a plugin; this should not be possible to set up in the first place. Return an empty + // infill. + return {}; + } +}; + +/** + * @brief Alias for the Simplify slot. + * + * This alias represents the Simplify slot, which is used for simplifying polygons. + * + * @tparam Default The default behavior when no plugin is registered. + */ +template +using slot_simplify_ + = SlotProxy; + +template +using slot_infill_generate_ = SlotProxy< + v0::SlotID::INFILL_GENERATE, + "0.1.0-alpha", + slots::infill::v0::generate::InfillGenerateService::Stub, + Validator, + infill_generate_request, + infill_generate_response, + Default>; + +/** + * @brief Alias for the Postprocess slot. + * + * This alias represents the Postprocess slot, which is used for post-processing G-code. + * + * @tparam Default The default behavior when no plugin is registered. + */ +template +using slot_postprocess_ = SlotProxy< + v0::SlotID::POSTPROCESS_MODIFY, + "0.1.0-alpha", + slots::postprocess::v0::modify::PostprocessModifyService::Stub, + Validator, + postprocess_request, + postprocess_response, + Default>; + +template +using slot_settings_broadcast_ + = SlotProxy; + +template +using slot_gcode_paths_modify_ = SlotProxy< + v0::SlotID::GCODE_PATHS_MODIFY, + "0.1.0-alpha", + slots::gcode_paths::v0::modify::GCodePathsModifyService::Stub, + Validator, + gcode_paths_modify_request, + gcode_paths_modify_response, + Default>; + +template +struct Typelist +{ +}; + +template class Unit> +class Registry; + +template class Unit> +class Registry, Unit> +{ +public: + constexpr void connect([[maybe_unused]] auto&&... args) noexcept + { + } + + template + constexpr void broadcast([[maybe_unused]] auto&&... args) noexcept + { + } // Base case, do nothing +}; + +template class Unit> +class Registry, Unit> : public Registry, Unit> +{ +public: + using ValueType = T; + using Base = Registry, Unit>; + using Base::broadcast; + friend Base; + + template + constexpr auto& get() + { + return get_type().proxy; + } + + template + constexpr auto modify(auto& original_value, auto&&... args) + { + return get().modify(original_value, std::forward(args)...); + } + + template + constexpr auto generate(auto&&... args) + { + return get().generate(std::forward(args)...); + } + + void connect(const v0::SlotID& slot_id, auto name, auto& version, auto&& channel) + { + if (slot_id == T::slot_id) + { + using Tp = typename Unit::value_type; + value_.proxy = Tp{ name, version, std::forward(channel) }; + return; + } + Base::connect(slot_id, name, version, std::forward(channel)); + } + + template + void broadcast(auto&&... args) + { + value_.proxy.template broadcast(std::forward(args)...); + Base::template broadcast(std::forward(args)...); + } + +protected: + template + constexpr auto& get_type() + { + return get_helper(std::bool_constant{}); + } + + template + constexpr auto& get_helper(std::true_type) + { + return value_; + } + + template + constexpr auto& get_helper(std::false_type) + { + return Base::template get_type(); + } + + Unit value_; +}; + +template class Unit> +class SingletonRegistry +{ +public: + static Registry& instance() + { + static Registry instance; + return instance; + } + +private: + constexpr SingletonRegistry() = default; +}; + +template +struct Holder +{ + using value_type = T; + T proxy; +}; + +} // namespace details + +using slot_gcode_paths_modify = details::slot_gcode_paths_modify_<>; +using slot_infill_generate = details::slot_infill_generate_; +using slot_postprocess = details::slot_postprocess_<>; +using slot_settings_broadcast = details::slot_settings_broadcast_<>; +using slot_simplify = details::slot_simplify_; + +using SlotTypes = details::Typelist; + +} // namespace plugins +using slots = plugins::details::SingletonRegistry; + +} // namespace cura + +#endif // PLUGINS_SLOTS_H diff --git a/include/plugins/types.h b/include/plugins/types.h new file mode 100644 index 0000000000..1585d5ff47 --- /dev/null +++ b/include/plugins/types.h @@ -0,0 +1,79 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_TYPES_H +#define PLUGINS_TYPES_H + +#include "cura/plugins/v0/slot_id.pb.h" +#include "utils/IntPoint.h" +#include "utils/polygon.h" + +#include + +#include +#include +#include + +namespace fmt +{ +// Custom formatter for humanreadable slot_id's +template<> +struct formatter +{ + template + auto format(cura::plugins::v0::SlotID slot_id, FormatContext& ctx) + { + std::string slot_name; + + switch (slot_id) + { + case cura::plugins::v0::SlotID::SETTINGS_BROADCAST: + slot_name = "SettingsBroadcastService"; + break; + case cura::plugins::v0::SlotID::SIMPLIFY_MODIFY: + slot_name = "SimplifyService"; + break; + case cura::plugins::v0::SlotID::POSTPROCESS_MODIFY: + slot_name = "PostprocessService"; + break; + case cura::plugins::v0::SlotID::INFILL_MODIFY: + slot_name = "InfillModifyService"; + break; + case cura::plugins::v0::SlotID::GCODE_PATHS_MODIFY: + slot_name = "GcodePathsModifyService"; + break; + case cura::plugins::v0::SlotID::INFILL_GENERATE: + slot_name = "InfillGenerateService"; + break; + default: + slot_name = "Unknown"; + break; + } + + return fmt::format_to(ctx.out(), "{}", slot_name); + } + + template + auto parse(ParseContext& ctx) + { + return ctx.begin(); + } +}; + +template<> +struct formatter +{ + constexpr auto parse(format_parse_context& ctx) + { + return ctx.end(); + } + + template + auto format(const grpc::string_ref& str, FormatContext& ctx) + { + return format_to(ctx.out(), "{}", std::string_view{ str.data(), str.size() }); + } +}; + +} // namespace fmt +#endif // PLUGINS_TYPES_H diff --git a/include/plugins/validator.h b/include/plugins/validator.h new file mode 100644 index 0000000000..ffe9cbba8e --- /dev/null +++ b/include/plugins/validator.h @@ -0,0 +1,54 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef PLUGINS_VALIDATOR_H +#define PLUGINS_VALIDATOR_H + +#include "plugins/metadata.h" +#include "plugins/types.h" + +#include +#include + +#include + +namespace cura::plugins +{ + +class Validator +{ + bool include_prerelease_{ true }; ///< Flag indicating whether to include prerelease versions. + bool valid_{ false }; ///< Flag indicating the validity of the version. + +public: + /** + * @brief Default constructor. + */ + constexpr Validator() noexcept = default; + + /** + * @brief Constructor that sets the version to validate. + * + * @param version The version to validate. + */ + Validator(const slot_metadata& slot_info, const plugin_metadata& plugin_info) + { + auto slot_range = semver::range::detail::range(plugin_info.slot_version_range); + auto slot_version = semver::from_string(slot_info.version); + valid_ = slot_range.satisfies(slot_version, include_prerelease_); + }; + + /** + * @brief Conversion operator to bool. + * + * @return True if the version is valid, false otherwise. + */ + constexpr operator bool() const noexcept + { + return valid_; + } +}; + +} // namespace cura::plugins + +#endif // PLUGINS_VALIDATOR_H diff --git a/include/progress/Progress.h b/include/progress/Progress.h index 6b9c83d532..c1e20cc6c7 100644 --- a/include/progress/Progress.h +++ b/include/progress/Progress.h @@ -1,72 +1,102 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef PROGRESS_H #define PROGRESS_H +#include +#include #include +#include + +#include "utils/gettime.h" namespace cura { -class TimeKeeper; +struct LayerIndex; -#define N_PROGRESS_STAGES 7 +static constexpr size_t N_PROGRESS_STAGES = 7; /*! * Class for handling the progress bar and the progress logging. - * + * * The progress bar is based on a single slicing of a rather large model which needs some complex support; * the relative timing of each stage is currently based on that of the slicing of dragon_65_tilted_large.stl */ -class Progress +class Progress { public: /*! - * The stage in the whole slicing process + * The stage in the whole slicing process */ enum class Stage : unsigned int { - START = 0, - SLICING = 1, - PARTS = 2, - INSET_SKIN = 3, - SUPPORT = 4, - EXPORT = 5, - FINISH = 6 + START = 0, + SLICING = 1, + PARTS = 2, + INSET_SKIN = 3, + SUPPORT = 4, + EXPORT = 5, + FINISH = 6 }; + private: - static double times [N_PROGRESS_STAGES]; //!< Time estimates per stage - static std::string names[N_PROGRESS_STAGES]; //!< name of each stage - static double accumulated_times [N_PROGRESS_STAGES]; //!< Time past before each stage + static constexpr std::array times{ + 0.0, // START = 0, + 5.269, // SLICING = 1, + 1.533, // PARTS = 2, + 71.811, // INSET_SKIN = 3 + 51.009, // SUPPORT = 4, + 154.62, // EXPORT = 5, + 0.1 // FINISH = 6 + }; + + static constexpr std::array names{ "start", "slice", "layerparts", "inset+skin", "support", "export", "process" }; + static std::array accumulated_times; //!< Time past before each stage static double total_timing; //!< An estimate of the total time + static std::optional first_skipped_layer; //!< The index of the layer for which we skipped time reporting /*! * Give an estimate between 0 and 1 of how far the process is. - * + * * \param stage The current stage of processing * \param stage_process How far we currently are in the \p stage * \return An estimate of the overall progress. */ - static float calcOverallProgress(Stage stage, float stage_progress); + static double calcOverallProgress(Stage stage, double stage_progress); + public: static void init(); //!< Initialize some values needed in a fast computation of the progress /*! * Message progress over the CommandSocket and to the terminal (if the command line arg '-p' is provided). - * + * * \param stage The current stage of processing * \param progress_in_stage Any number giving the progress within the stage * \param progress_in_stage_max The maximal value of \p progress_in_stage */ static void messageProgress(Stage stage, int progress_in_stage, int progress_in_stage_max); + /*! * Message the progress stage over the command socket. - * + * * \param stage The current stage * \param timeKeeper The stapwatch keeping track of the timings for each stage (optional) */ static void messageProgressStage(Stage stage, TimeKeeper* timeKeeper); + + /*! + * Message the layer progress over the command socket and into logging output. + * + * \param layer_nr The processed layer number + * \param total_layers The total number of layers to be processed + * \param total_time The total layer processing time, in seconds + * \param stage The detailed stages time reporting for this layer + * \param skip_threshold The time threshold under which we consider that the full layer time reporting should be skipped + * because it is not relevant + */ + static void messageProgressLayer(LayerIndex layer_nr, size_t total_layers, double total_time, const TimeKeeper::RegisteredTimes& stages, double skip_threshold = 0.1); }; -} // name space cura -#endif//PROGRESS_H +} // namespace cura +#endif // PROGRESS_H diff --git a/include/settings/EnumSettings.h b/include/settings/EnumSettings.h index 42818beb5b..1d82feb83c 100644 --- a/include/settings/EnumSettings.h +++ b/include/settings/EnumSettings.h @@ -1,5 +1,5 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef ENUMSETTINGS_H #define ENUMSETTINGS_H @@ -27,7 +27,8 @@ enum class EFillMethod CROSS_3D, GYROID, LIGHTNING, - NONE // NOTE: Should remain last! (May be used in testing to enumarate the enum.) + NONE, // NOTE: Should remain second last! Before PLUGIN (Might be used in testing to enumerate the enum.) + PLUGIN, // Place plugin after none to prevent it from being tested in the gtest suite. }; /*! @@ -38,7 +39,8 @@ enum class EPlatformAdhesion SKIRT, BRIM, RAFT, - NONE + NONE, + PLUGIN, }; /*! @@ -48,7 +50,8 @@ enum class ESupportType { NONE, PLATFORM_ONLY, - EVERYWHERE + EVERYWHERE, + PLUGIN, }; /*! @@ -57,7 +60,8 @@ enum class ESupportType enum class ESupportStructure { NORMAL, - TREE + TREE, + PLUGIN, }; enum class EZSeamType @@ -70,7 +74,8 @@ enum class EZSeamType /* The 'Skirt/brim' type behaves like shortest, except it doesn't try to do tie-breaking for similar locations to * the last attempt, as that gives a different result when the seams are next to each other instead of on top. */ - SKIRT_BRIM + SKIRT_BRIM, + PLUGIN, }; enum class EZSeamCornerPrefType @@ -79,26 +84,30 @@ enum class EZSeamCornerPrefType Z_SEAM_CORNER_PREF_INNER, Z_SEAM_CORNER_PREF_OUTER, Z_SEAM_CORNER_PREF_ANY, - Z_SEAM_CORNER_PREF_WEIGHTED + Z_SEAM_CORNER_PREF_WEIGHTED, + PLUGIN, }; enum class ESurfaceMode { NORMAL, SURFACE, - BOTH + BOTH, + PLUGIN, }; enum class FillPerimeterGapMode { NOWHERE, - EVERYWHERE + EVERYWHERE, + PLUGIN, }; enum class BuildPlateShape { RECTANGULAR, - ELLIPTIC + ELLIPTIC, + PLUGIN, }; enum class CombingMode @@ -107,7 +116,8 @@ enum class CombingMode ALL, NO_SKIN, NO_OUTER_SURFACES, - INFILL + INFILL, + PLUGIN, }; /*! @@ -115,21 +125,24 @@ enum class CombingMode */ enum class DraftShieldHeightLimitation { - FULL, //Draft shield takes full height of the print. - LIMITED //Draft shield is limited by draft_shield_height setting. + FULL, // Draft shield takes full height of the print. + LIMITED, // Draft shield is limited by draft_shield_height setting. + PLUGIN, }; enum class SupportDistPriority { XY_OVERRIDES_Z, - Z_OVERRIDES_XY + Z_OVERRIDES_XY, + PLUGIN, }; enum class SlicingTolerance { MIDDLE, INCLUSIVE, - EXCLUSIVE + EXCLUSIVE, + PLUGIN, }; /*! * Different flavors of GCode. Some machines require different types of GCode. @@ -137,75 +150,76 @@ enum class SlicingTolerance */ enum class EGCodeFlavor { -/** - * Marlin flavored GCode is Marlin/Sprinter based GCode. - * This is the most commonly used GCode set. - * G0 for moves, G1 for extrusion. - * E values give mm of filament extrusion. - * Retraction is done on E values with G1. Start/end code is added. - * M106 Sxxx and M107 are used to turn the fan on/off. - **/ + /** + * Marlin flavored GCode is Marlin/Sprinter based GCode. + * This is the most commonly used GCode set. + * G0 for moves, G1 for extrusion. + * E values give mm of filament extrusion. + * Retraction is done on E values with G1. Start/end code is added. + * M106 Sxxx and M107 are used to turn the fan on/off. + **/ MARLIN = 0, -/** - * UltiGCode flavored is Marlin based GCode. - * UltiGCode uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode. - * G0 for moves, G1 for extrusion. - * E values give mm^3 of filament extrusion. Ignores the filament diameter setting. - * Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction. - * Start/end code is not added. - * M106 Sxxx and M107 are used to turn the fan on/off. - **/ + /** + * UltiGCode flavored is Marlin based GCode. + * UltiGCode uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode. + * G0 for moves, G1 for extrusion. + * E values give mm^3 of filament extrusion. Ignores the filament diameter setting. + * Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction. + * Start/end code is not added. + * M106 Sxxx and M107 are used to turn the fan on/off. + **/ ULTIGCODE = 1, -/** - * Makerbot flavored GCode. - * Looks a lot like RepRap GCode with a few changes. Requires MakerWare to convert to X3G files. - * Heating needs to be done with M104 Sxxx T0 - * No G21 or G90 - * Fan ON is M126 T0 (No fan strength control?) - * Fan OFF is M127 T0 - * Homing is done with G162 X Y F2000 - **/ + /** + * Makerbot flavored GCode. + * Looks a lot like RepRap GCode with a few changes. Requires MakerWare to convert to X3G files. + * Heating needs to be done with M104 Sxxx T0 + * No G21 or G90 + * Fan ON is M126 T0 (No fan strength control?) + * Fan OFF is M127 T0 + * Homing is done with G162 X Y F2000 + **/ MAKERBOT = 2, -/** - * Bits From Bytes GCode. - * BFB machines use RPM instead of E. Which is coupled to the F instead of independed. (M108 S[deciRPM]) - * Need X,Y,Z,F on every line. - * Needs extruder ON/OFF (M101, M103), has auto-retrection (M227 S[2560*mm] P[2560*mm]) - **/ + /** + * Bits From Bytes GCode. + * BFB machines use RPM instead of E. Which is coupled to the F instead of independed. (M108 S[deciRPM]) + * Need X,Y,Z,F on every line. + * Needs extruder ON/OFF (M101, M103), has auto-retrection (M227 S[2560*mm] P[2560*mm]) + **/ BFB = 3, -/** - * MACH3 GCode - * MACH3 is CNC control software, which expects A/B/C/D for extruders, instead of E. - **/ + /** + * MACH3 GCode + * MACH3 is CNC control software, which expects A/B/C/D for extruders, instead of E. + **/ MACH3 = 4, -/** - * RepRap volumatric flavored GCode is Marlin based GCode. - * Volumatric uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode. - * G0 for moves, G1 for extrusion. - * E values give mm^3 of filament extrusion. Ignores the filament diameter setting. - * Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction. - * M106 Sxxx and M107 are used to turn the fan on/off. - **/ + /** + * RepRap volumatric flavored GCode is Marlin based GCode. + * Volumatric uses less settings on the slicer and puts more settings in the firmware. This makes for more hardware/material independed GCode. + * G0 for moves, G1 for extrusion. + * E values give mm^3 of filament extrusion. Ignores the filament diameter setting. + * Retraction is done with G10 and G11. Retraction settings are ignored. G10 S1 is used for multi-extruder switch retraction. + * M106 Sxxx and M107 are used to turn the fan on/off. + **/ MARLIN_VOLUMATRIC = 5, -/** - * Griffin flavored is Marlin based GCode. - * This is a type of RepRap used for machines with multiple extruder trains. - * G0 for moves, G1 for extrusion. - * E values give mm of filament extrusion. - * E values are stored separately per extruder train. - * Retraction is done on E values with G1. Start/end code is added. - * M227 is used to initialize a single extrusion train. - **/ + /** + * Griffin flavored is Marlin based GCode. + * This is a type of RepRap used for machines with multiple extruder trains. + * G0 for moves, G1 for extrusion. + * E values give mm of filament extrusion. + * E values are stored separately per extruder train. + * Retraction is done on E values with G1. Start/end code is added. + * M227 is used to initialize a single extrusion train. + **/ GRIFFIN = 6, REPETIER = 7, -/** - * Real RepRap GCode suitable for printers using RepRap firmware (e.g. Duet controllers) - **/ + /** + * Real RepRap GCode suitable for printers using RepRap firmware (e.g. Duet controllers) + **/ REPRAP = 8, + PLUGIN = 9, }; /*! @@ -227,9 +241,10 @@ enum class InsetDirection * If the innermost wall is a central wall, it is printed last. Otherwise * prints the same as inside out. */ - CENTER_LAST + CENTER_LAST, + PLUGIN, }; -} //Cura namespace. +} // namespace cura -#endif //ENUMSETTINGS_H \ No newline at end of file +#endif // ENUMSETTINGS_H \ No newline at end of file diff --git a/include/settings/MeshPathConfigs.h b/include/settings/MeshPathConfigs.h new file mode 100644 index 0000000000..61c993b9a8 --- /dev/null +++ b/include/settings/MeshPathConfigs.h @@ -0,0 +1,36 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef SETTINGS_TYPES_MESHPATHCONFIGS_H +#define SETTINGS_TYPES_MESHPATHCONFIGS_H + +#include "GCodePathConfig.h" +#include "settings/types/LayerIndex.h" +#include "settings/types/Ratio.h" +#include "sliceDataStorage.h" + +namespace cura +{ +struct MeshPathConfigs +{ + GCodePathConfig inset0_config{}; + GCodePathConfig insetX_config{}; + GCodePathConfig inset0_roofing_config{}; + GCodePathConfig insetX_roofing_config{}; + GCodePathConfig bridge_inset0_config{}; + GCodePathConfig bridge_insetX_config{}; + GCodePathConfig skin_config{}; + GCodePathConfig bridge_skin_config{}; // used for first bridge layer + GCodePathConfig bridge_skin_config2{}; // used for second bridge layer + GCodePathConfig bridge_skin_config3{}; // used for third bridge layer + GCodePathConfig roofing_config{}; + std::vector infill_config{}; + GCodePathConfig ironing_config{}; + + MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t layer_thickness, const LayerIndex layer_nr, const std::vector& line_width_factor_per_extruder); + void smoothAllSpeeds(const SpeedDerivatives& first_layer_config, const LayerIndex layer_nr, const LayerIndex max_speed_layer); +}; + +} // namespace cura + +#endif // SETTINGS_TYPES_MESHPATHCONFIGS_H diff --git a/include/settings/PathConfigStorage.h b/include/settings/PathConfigStorage.h index 2480e345e9..feafea23bf 100644 --- a/include/settings/PathConfigStorage.h +++ b/include/settings/PathConfigStorage.h @@ -1,19 +1,21 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef SETTINGS_PATH_CONFIGS_H #define SETTINGS_PATH_CONFIGS_H #include -#include "../GCodePathConfig.h" -#include "../utils/Coord_t.h" +#include "GCodePathConfig.h" +#include "pathPlanning/SpeedDerivatives.h" +#include "settings/MeshPathConfigs.h" +#include "settings/types/LayerIndex.h" +#include "utils/Coord_t.h" namespace cura { class ExtruderTrain; -struct LayerIndex; class SliceDataStorage; class SliceMeshStorage; @@ -35,26 +37,8 @@ class PathConfigStorage const std::vector line_width_factor_per_extruder; static std::vector getLineWidthFactorPerExtruder(const LayerIndex& layer_nr); -public: - class MeshPathConfigs - { - public: - GCodePathConfig inset0_config; - GCodePathConfig insetX_config; - GCodePathConfig bridge_inset0_config; - GCodePathConfig bridge_insetX_config; - GCodePathConfig skin_config; - GCodePathConfig bridge_skin_config; // used for first bridge layer - GCodePathConfig bridge_skin_config2; // used for second bridge layer - GCodePathConfig bridge_skin_config3; // used for third bridge layer - GCodePathConfig roofing_config; - std::vector infill_config; - GCodePathConfig ironing_config; - - MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t layer_thickness, const LayerIndex& layer_nr, const std::vector& line_width_factor_per_extruder); - void smoothAllSpeeds(GCodePathConfig::SpeedDerivatives first_layer_config, const LayerIndex& layer_nr, const LayerIndex& max_speed_layer); - }; +public: GCodePathConfig raft_base_config; GCodePathConfig raft_interface_config; GCodePathConfig raft_surface_config; @@ -64,7 +48,9 @@ class PathConfigStorage std::vector prime_tower_config_per_extruder; //!< Configuration for the prime tower per extruder. std::vector support_infill_config; //!< The config used to print the normal support, rather than the support interface + std::vector support_fractional_infill_config; //!< The config used to print the normal support on fractional layer-height parts. GCodePathConfig support_roof_config; //!< The config used to print the dense roofs of support. + GCodePathConfig support_fractional_roof_config; //!< The config used to print the dense roofs of support on fractional layer-height parts. GCodePathConfig support_bottom_config; //!< The config to use to print the dense bottoms of support std::vector mesh_configs; //!< For each meash the config for all its feature types diff --git a/include/settings/Settings.h b/include/settings/Settings.h index 4bb74cca3c..4f25fe97ce 100644 --- a/include/settings/Settings.h +++ b/include/settings/Settings.h @@ -1,5 +1,5 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef SETTINGS_SETTINGS_H #define SETTINGS_SETTINGS_H @@ -64,7 +64,8 @@ class Settings * \param key The key of the setting to get. * \return The setting's value, cast to the desired type. */ - template A get(const std::string& key) const; + template + A get(const std::string& key) const; /*! * \brief Get a string containing all settings in this container. @@ -95,6 +96,10 @@ class Settings */ void setParent(Settings* new_parent); + std::unordered_map getFlattendSettings() const; + + std::vector getKeys() const; + /* * \brief Write the settings to a stream. * @@ -126,7 +131,6 @@ class Settings std::string getWithoutLimiting(const std::string& key) const; }; -} //namespace cura - -#endif //SETTINGS_SETTINGS_H +} // namespace cura +#endif // SETTINGS_SETTINGS_H diff --git a/include/settings/types/LayerIndex.h b/include/settings/types/LayerIndex.h index 1e48bbcb68..b1a7524428 100644 --- a/include/settings/types/LayerIndex.h +++ b/include/settings/types/LayerIndex.h @@ -1,126 +1,209 @@ -//Copyright (c) 2019 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef LAYERINDEX_H #define LAYERINDEX_H +#include "utils/types/generic.h" + #include namespace cura { -/* - * \brief Struct behaving like a layer number. - * - * This is a facade. It behaves exactly like an integer but is used to indicate - * that it is a layer number. - */ struct LayerIndex { - /* - * \brief Default constructor setting the layer index to 0. - */ - constexpr LayerIndex() : value(0) {}; + using value_type = int64_t; + using difference_type = std::ptrdiff_t; + + value_type value{}; + + constexpr LayerIndex() noexcept = default; + + constexpr LayerIndex(const LayerIndex& other) noexcept = default; + constexpr LayerIndex(LayerIndex&& other) noexcept = default; + + constexpr explicit LayerIndex(const utils::floating_point auto val) noexcept + : value{ static_cast(val) } {}; - /* - * \brief Casts an integer to a LayerIndex instance. - */ - constexpr LayerIndex(int value) : value(value) {}; + constexpr LayerIndex(const utils::integral auto val) noexcept + : value{ static_cast(val) } {}; - /* - * \brief Casts the LayerIndex instance to an integer. - */ - constexpr operator int() const + constexpr LayerIndex& operator=(const LayerIndex& other) noexcept = default; + + constexpr LayerIndex& operator=(const utils::integral auto& other) noexcept { - return value; + this->value = static_cast(other); + return *this; } - /* - * Some operators to add and subtract layer numbers. - */ - LayerIndex operator +(const LayerIndex& other) const + constexpr LayerIndex& operator=(LayerIndex&& other) noexcept = default; + constexpr LayerIndex& operator=(const utils::integral auto&& other) noexcept { - return LayerIndex(value + other.value); + this->value = static_cast(other); + return *this; } - template LayerIndex operator +(const E& other) const + + ~LayerIndex() noexcept = default; + + constexpr operator value_type() const noexcept { - return LayerIndex(value + other); + return value; } - LayerIndex operator -(const LayerIndex& other) const + constexpr bool operator==(const LayerIndex& other) const noexcept { - return LayerIndex(value - other.value); + return value == other.value; } - template LayerIndex operator -(const E& other) const + + constexpr bool operator==(const utils::integral auto& other) const noexcept { - return LayerIndex(value - other); + return value == static_cast(other); } - LayerIndex& operator +=(const LayerIndex& other) + constexpr auto operator<=>(const LayerIndex& other) const noexcept = default; + constexpr auto operator<=>(const utils::integral auto& other) const noexcept + { + return value <=> static_cast(other); + }; + + constexpr LayerIndex& operator+=(const LayerIndex& other) noexcept { value += other.value; return *this; } - template LayerIndex& operator +=(const E& other) + + constexpr LayerIndex& operator+=(const utils::integral auto& other) noexcept { - value += other; + value += static_cast(other); return *this; } - LayerIndex& operator -=(const LayerIndex& other) + constexpr LayerIndex& operator-=(const LayerIndex& other) noexcept { value -= other.value; return *this; } - template LayerIndex& operator -=(const E& other) + + constexpr LayerIndex& operator-=(const utils::integral auto& other) noexcept { - value -= other; + value -= static_cast(other); return *this; } - LayerIndex& operator ++() + constexpr LayerIndex& operator*=(const LayerIndex& other) noexcept { - value++; + value *= other.value; return *this; } - LayerIndex operator ++(int) //Postfix. + + constexpr LayerIndex& operator*=(const utils::integral auto& other) noexcept + { + value *= static_cast(other); + return *this; + } + + constexpr LayerIndex& operator/=(const LayerIndex& other) + { + value /= other.value; + return *this; + } + + constexpr LayerIndex& operator/=(const utils::integral auto& other) + { + value /= static_cast(other); + return *this; + } + + constexpr LayerIndex operator+(const LayerIndex& other) const noexcept + { + return { value + other.value }; + } + + constexpr LayerIndex operator+(LayerIndex&& other) const noexcept + { + return { value + other.value }; + } + + constexpr LayerIndex operator+(const utils::integral auto& other) const noexcept + { + return { value + static_cast(other) }; + } + + constexpr LayerIndex operator-(const LayerIndex& other) const noexcept + { + return { value - other.value }; + } + + constexpr LayerIndex operator-(const utils::integral auto& other) const noexcept + { + return { value - static_cast(other) }; + } + + constexpr LayerIndex operator*(const LayerIndex& other) const noexcept + { + return { value * other.value }; + } + + constexpr LayerIndex operator*(const utils::integral auto& other) const noexcept { - LayerIndex original_value(value); - operator++(); //Increment myself. - return original_value; + return { value * static_cast(other) }; } - LayerIndex& operator --() + + constexpr LayerIndex operator/(const LayerIndex& other) const + { + return { value / other.value }; + } + + constexpr LayerIndex operator/(const utils::integral auto& other) const { - value--; + return { value / static_cast(other) }; + } + + constexpr LayerIndex operator-() const noexcept + { + return { -value }; + } + + constexpr LayerIndex& operator++() noexcept + { + ++value; return *this; } - LayerIndex operator --(int) //Postfix. + + LayerIndex operator++(int) noexcept { - LayerIndex original_value(value); - operator--(); //Decrement myself. - return original_value; + LayerIndex tmp{ *this }; + operator++(); + return tmp; } - /* - * \brief The actual layer index. - * - * Note that this could be negative for raft layers. - */ - int value = 0; + constexpr LayerIndex& operator--() noexcept + { + --value; + return *this; + } + + LayerIndex operator--(int) noexcept + { + LayerIndex tmp{ *this }; + operator--(); + return tmp; + } }; -} +} // namespace cura namespace std { - template<> - struct hash +template<> +struct hash +{ + auto operator()(const cura::LayerIndex& layer_index) const { - size_t operator()(const cura::LayerIndex& layer_index) const - { - return hash()(layer_index.value); - } - }; -} + return hash()(layer_index.value); + } +}; +} // namespace std -#endif //LAYERINDEX_H +#endif // LAYERINDEX_H diff --git a/include/settings/types/Ratio.h b/include/settings/types/Ratio.h index a20cd2e2d6..76871ff301 100644 --- a/include/settings/types/Ratio.h +++ b/include/settings/types/Ratio.h @@ -1,9 +1,12 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef RATIO_H #define RATIO_H +#include "utils/types/numeric_facade.h" + + namespace cura { @@ -12,104 +15,28 @@ namespace cura * * This is a facade. It behaves like a double. */ -class Ratio +struct Ratio : public utils::NumericFacade { -public: - /* - * \brief Default constructor setting the ratio to 1. - */ - constexpr Ratio() : value(1.0) {}; + using base_type = utils::NumericFacade; + using base_type::NumericFacade; - /* - * \brief Casts a double to a Ratio instance. - */ - constexpr Ratio(double value) : value(value) {}; + constexpr Ratio(const base_type& base) noexcept + : base_type{ base } {}; /*! * Create the Ratio with a numerator and a divisor from arbitrary types - * \tparam E1 required to be castable to a double - * \tparam E2 required to be castable to a double * \param numerator the numerator of the ratio * \param divisor the divisor of the ratio */ - template - constexpr Ratio(const E1& numerator, const E2& divisor) - : value(static_cast(numerator) / static_cast(divisor)) {}; - - /* - * \brief Casts the Ratio instance to a double. - */ - operator double() const - { - return value; - } - - /* - * Some Relational operators - */ - template - constexpr bool operator==(const E& rhs) const - { - return value == static_cast(rhs); - } - - template - constexpr bool operator!=(const E& rhs) const - { - return !(rhs == *this); - } - - /* - * Some operators for arithmetic on ratios. - */ - Ratio operator *(const Ratio& other) const - { - return Ratio(value * other.value); - } - template Ratio operator *(const E& other) const - { - return Ratio(value * other); - } - Ratio operator /(const Ratio& other) const - { - return Ratio(value / other.value); - } - template Ratio operator /(const E& other) const - { - return Ratio(value / other); - } - Ratio& operator *=(const Ratio& other) - { - value *= other.value; - return *this; - } - template Ratio& operator *=(const E& other) - { - value *= other; - return *this; - } - Ratio& operator /=(const Ratio& other) - { - value /= other.value; - return *this; - } - template Ratio& operator /=(const E& other) - { - value /= other; - return *this; - } - - /* - * \brief The actual ratio, as a double. - */ - double value = 0; + constexpr Ratio(const utils::numeric auto numerator, const utils::numeric auto divisor) + : base_type{ static_cast(numerator) / static_cast(divisor) } {}; }; -constexpr Ratio operator "" _r(const long double ratio) +constexpr Ratio operator"" _r(const long double ratio) { - return Ratio(ratio); + return { ratio }; } -} +} // namespace cura -#endif //RATIO_H +#endif // RATIO_H diff --git a/include/settings/types/Velocity.h b/include/settings/types/Velocity.h index 0a56c077d4..f897c13c97 100644 --- a/include/settings/types/Velocity.h +++ b/include/settings/types/Velocity.h @@ -1,9 +1,11 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef VELOCITY_H #define VELOCITY_H +#include "utils/types/numeric_facade.h" + namespace cura { @@ -12,74 +14,24 @@ namespace cura * * This is a facade. It behaves like a double, only it can't be negative. */ -struct Velocity +struct Velocity : public utils::NumericFacade { - /* - * \brief Default constructor setting velocity to 0. - */ - constexpr Velocity() : value(0.0) {}; - - /* - * \brief Casts a double to a Velocity instance. - */ - constexpr Velocity(double value) : value(value) {}; + using base_type = utils::NumericFacade; + using base_type::NumericFacade; - /* - * \brief Casts the Temperature instance to a double. - */ - constexpr operator double() const - { - return value; - } + constexpr Velocity(const base_type& base) noexcept + : base_type{ base } {}; +}; - /* - * Some operators for arithmetic on velocities. - */ - Velocity operator *(const Velocity& other) const - { - return Velocity(value * other.value); - } - template Velocity operator *(const E& other) const - { - return Velocity(value * other); - } - Velocity operator /(const Velocity& other) const - { - return Velocity(value / other.value); - } - template Velocity operator /(const E& other) const - { - return Velocity(value / other); - } - Velocity& operator *=(const Velocity& other) - { - value *= other.value; - return *this; - } - template Velocity& operator *=(const E& other) - { - value *= other; - return *this; - } - Velocity& operator /=(const Velocity& other) - { - value /= other.value; - return *this; - } - template Velocity& operator /=(const E& other) - { - value /= other; - return *this; - } +struct Acceleration : public utils::NumericFacade +{ + using base_type = utils::NumericFacade; + using base_type::NumericFacade; - /* - * \brief The actual temperature, as a double. - */ - double value = 0; + constexpr Acceleration(const base_type& base) noexcept + : base_type{ base } {}; }; -using Acceleration = Velocity; //Use the same logic for acceleration variables. - -} +} // namespace cura -#endif //VELOCITY_H \ No newline at end of file +#endif // VELOCITY_H \ No newline at end of file diff --git a/include/skin.h b/include/skin.h index 6beeae1643..de96d6e1c3 100644 --- a/include/skin.h +++ b/include/skin.h @@ -1,5 +1,5 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef SKIN_H #define SKIN_H @@ -7,7 +7,7 @@ #include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" -namespace cura +namespace cura { class Polygons; @@ -21,10 +21,9 @@ class SliceMeshStorage; class SkinInfillAreaComputation { public: - /*! * \brief Initialize the parameters for skin and infill area computation. - * + * * \param layer_nr The index of the layer for which to generate the skins * and infill. * \param mesh The storage where the layer outline information (input) is @@ -41,11 +40,11 @@ class SkinInfillAreaComputation /*! * \brief Combines the infill of multiple layers for a specified mesh. - * + * * The infill layers are combined while the thickness of each layer is * multiplied such that the infill should fill up again to the full height of * all combined layers. - * + * * \param mesh The mesh to combine the infill layers of. */ static void combineInfillLayers(SliceMeshStorage& mesh); @@ -69,10 +68,10 @@ class SkinInfillAreaComputation /*! * Limit the infill areas to places where they support internal overhangs. - * + * * This function uses the part.infill_area and part.infill_area_own * and computes a new part.infill_area_own - * + * * \param mesh The mesh for which to recalculate the infill areas */ static void generateInfillSupport(SliceMeshStorage& mesh); @@ -85,7 +84,7 @@ class SkinInfillAreaComputation /*! * Generate the skin areas (outlines) of one part in a layer - * + * * \param part The part for which to generate skins. */ void generateSkinAndInfillAreas(SliceLayerPart& part); @@ -112,7 +111,7 @@ class SkinInfillAreaComputation * Apply skin expansion: * expand skins into infill area * where the skin is broad enough - * + * * \param original_outline The outline within which skin and infill lie (inner bounds of innermost walls) * \param[in,out] upskin The top skin areas to grow * \param[in,out] downskin The bottom skin areas to grow @@ -125,12 +124,12 @@ class SkinInfillAreaComputation * where the infill areas (output) are stored. * \param skin The skin areas on the layer of the \p part */ - void generateInfill(SliceLayerPart& part, const Polygons& skin); + void generateInfill(SliceLayerPart& part); /*! - * Remove the areas which are 'directly' under air from the \ref SkinPart::inner_infill and + * Remove the areas which are 'directly' under air from the \ref SkinPart::inner_infill and * save them in the \ref SkinPart::roofing_fill of the \p part. - * + * * \param[in,out] part Where to get the SkinParts to get the outline info from and to store the roofing areas */ void generateRoofingFillAndSkinFill(SliceLayerPart& part); @@ -194,6 +193,6 @@ class SkinInfillAreaComputation Polygons getOutlineOnLayer(const SliceLayerPart& part_here, const LayerIndex layer2_nr); }; -}//namespace cura +} // namespace cura -#endif//SKIN_H +#endif // SKIN_H diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index b34c722fbf..6db1cd605a 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -1,16 +1,18 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef SLICE_DATA_STORAGE_H #define SLICE_DATA_STORAGE_H #include +#include #include #include "PrimeTower.h" #include "RetractionConfig.h" #include "SupportInfillPart.h" #include "TopSurface.h" +#include "WipeScriptConfig.h" #include "settings/Settings.h" //For MAX_EXTRUDERS. #include "settings/types/Angle.h" //Infill angles. #include "settings/types/LayerIndex.h" @@ -19,7 +21,6 @@ #include "utils/IntPoint.h" #include "utils/NoCopy.h" #include "utils/polygon.h" -#include "WipeScriptConfig.h" // libArachne #include "utils/ExtrusionLine.h" @@ -32,14 +33,15 @@ class SierpinskiFillProvider; class LightningGenerator; /*! - * A SkinPart is a connected area designated as top and/or bottom skin. + * A SkinPart is a connected area designated as top and/or bottom skin. * Surrounding each non-bridged skin area with an outline may result in better top skins. * It's filled during FffProcessor.processSliceData(.) and used in FffProcessor.writeGCode(.) to generate the final gcode. */ class SkinPart { public: - PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module. Includes both roofing and non-roofing. + PolygonsPart outline; //!< The skinOutline is the area which needs to be 100% filled to generate a proper top&bottom filling. It's filled by the "skin" module. Includes both + //!< roofing and non-roofing. Polygons skin_fill; //!< The part of the skin which is not roofing. Polygons roofing_fill; //!< The inner infill which has air directly above Polygons top_most_surface_fill; //!< The inner infill of the uppermost top layer which has air directly above. @@ -64,7 +66,7 @@ class SliceLayerPart //!< Too small parts will be omitted compared to the outline. Polygons spiral_wall; //!< The centerline of the wall used by spiralize mode. Only computed if spiralize mode is enabled. Polygons inner_area; //!< The area of the outline, minus the walls. This will be filled with either skin or infill. - std::vector skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets. + std::vector skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets. std::vector wall_toolpaths; //!< toolpaths for walls, will replace(?) the insets. Binned by inset_idx. std::vector infill_wall_toolpaths; //!< toolpaths for the walls of the infill areas. Binned by inset_idx. @@ -125,7 +127,7 @@ class SliceLayerPart * This maximum number of layers we can combine is a user setting. This number, say "n", means the maximum number of layers we can combine into one. * On the combined layers, the extrusion amount will be higher than the normal extrusion amount because it needs to extrude for multiple layers instead of one. * - * infill_area[x][n] is infill_area of (n+1) layers thick. + * infill_area[x][n] is infill_area of (n+1) layers thick. * * infill_area[0] corresponds to the most dense infill area. * infill_area[x] will lie fully inside infill_area[x+1]. @@ -161,9 +163,9 @@ class SliceLayerPart class SliceLayer { public: - coord_t printZ; //!< The height at which this layer needs to be printed. Can differ from sliceZ due to the raft. - coord_t thickness; //!< The thickness of this layer. Can be different when using variable layer heights. - std::vector parts; //!< An array of LayerParts which contain the actual data. The parts are printed one at a time to minimize travel outside of the 3D model. + coord_t printZ; //!< The height at which this layer needs to be printed. Can differ from sliceZ due to the raft. + coord_t thickness; //!< The thickness of this layer. Can be different when using variable layer heights. + std::vector parts; //!< An array of LayerParts which contain the actual data. The parts are printed one at a time to minimize travel outside of the 3D model. Polygons openPolyLines; //!< A list of lines which were never hooked up into a 2D polygon. (Currently unused in normal operation) /*! @@ -174,9 +176,16 @@ class SliceLayer */ TopSurface top_surface; + /*! + * \brief The parts of the model that are exposed at the bottom(s) of the model. + * + * Note: Filled only when needed. + */ + Polygons bottom_surface; + /*! * Get the all outlines of all layer parts in this layer. - * + * * \param external_polys_only Whether to only include the outermost outline of each layer part * \return A collection of all the outline polygons */ @@ -185,7 +194,7 @@ class SliceLayer /*! * Get the all outlines of all layer parts in this layer. * Add those polygons to @p result. - * + * * \param external_polys_only Whether to only include the outermost outline of each layer part * \param result The result: a collection of all the outline polygons */ @@ -197,14 +206,15 @@ class SliceLayer /******************/ - - class SupportLayer { public: - std::vector support_infill_parts; //!< a list of support infill parts + std::vector support_infill_parts; //!< a list of support infill parts Polygons support_bottom; //!< Piece of support below the support and above the model. This must not overlap with any of the support_infill_parts or support_roof. Polygons support_roof; //!< Piece of support above the support and below the model. This must not overlap with any of the support_infill_parts or support_bottom. + // NOTE: This is _all_ of the support_roof, and as such, overlaps with support_fractional_roof! + Polygons support_fractional_roof; //!< If the support distance is not exactly a multiple of the layer height, + // the first part of support just underneath the model needs to be printed at a fracional layer height. Polygons support_mesh_drop_down; //!< Areas from support meshes which should be supported by more support Polygons support_mesh; //!< Areas from support meshes which should NOT be supported by more support Polygons anti_overhang; //!< Areas where no overhang should be detected. @@ -216,6 +226,24 @@ class SupportLayer * \param exclude_polygons_boundary_box The boundary box for the polygons to exclude */ void excludeAreasFromSupportInfillAreas(const Polygons& exclude_polygons, const AABB& exclude_polygons_boundary_box); + + /* Fill up the infill parts for the support with the given support polygons. The support polygons will be split into parts. This also takes into account fractional-height + * support layers. + * + * \param layer_nr Current layer index. + * \param support_fill_per_layer All of the (infill) support (since the layer above might be needed). + * \param support_line_width Line width of the support extrusions. + * \param wall_line_count Wall-line count around the fill. + * \param grow_layer_above (optional, default to 0) In cases where support shrinks per layer up, an appropriate offset may be nescesary. + * \param unionAll (optional, default to false) Wether to 'union all' for the split into parts bit. + */ + void fillInfillParts( + const LayerIndex layer_nr, + const std::vector& support_fill_per_layer, + const coord_t support_line_width, + const coord_t wall_line_count, + const coord_t grow_layer_above = 0, + const bool unionAll = false); }; class SupportStorage @@ -231,7 +259,7 @@ class SupportStorage std::vector support_bottom_angles; //!< a list of angle values which is cycled through to determine the infill angle of each layer std::vector supportLayers; - SierpinskiFillProvider* cross_fill_provider; //!< the fractal pattern for the cross (3d) filling pattern + std::shared_ptr cross_fill_provider; //!< the fractal pattern for the cross (3d) filling pattern SupportStorage(); ~SupportStorage(); @@ -253,14 +281,16 @@ class SliceMeshStorage std::vector roofing_angles; //!< a list of angle values which is cycled through to determine the roofing angle of each layer std::vector skin_angles; //!< a list of angle values which is cycled through to determine the skin angle of each layer std::vector overhang_areas; //!< For each layer the areas that are classified as overhang on this mesh. - std::vector full_overhang_areas; //!< For each layer the full overhang without the tangent of the overhang angle removed, such that the overhang area adjoins the areas of the next layers. - std::vector> overhang_points; //!< For each layer a list of points where point-overhang is detected. This is overhang that hasn't got any surface area, such as a corner pointing downwards. + std::vector full_overhang_areas; //!< For each layer the full overhang without the tangent of the overhang angle removed, such that the overhang area adjoins the + //!< areas of the next layers. + std::vector> overhang_points; //!< For each layer a list of points where point-overhang is detected. This is overhang that hasn't got any surface area, + //!< such as a corner pointing downwards. AABB3D bounding_box; //!< the mesh's bounding box - SubDivCube* base_subdiv_cube; - SierpinskiFillProvider* cross_fill_provider; //!< the fractal pattern for the cross (3d) filling pattern + std::shared_ptr base_subdiv_cube; + std::shared_ptr cross_fill_provider; //!< the fractal pattern for the cross (3d) filling pattern - LightningGenerator* lightning_generator; //!< Pre-computed structure for Lightning type infill + std::shared_ptr lightning_generator; //!< Pre-computed structure for Lightning type infill RetractionAndWipeConfig retraction_wipe_config; //!< Per-Object retraction and wipe settings. @@ -273,8 +303,6 @@ class SliceMeshStorage */ SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count); - virtual ~SliceMeshStorage(); - /*! * \param extruder_nr The extruder for which to check * \return whether a particular extruder is used by this mesh @@ -317,27 +345,26 @@ class SliceDataStorage : public NoCopy Point3 model_size, model_min, model_max; AABB3D machine_size; //!< The bounding box with the width, height and depth of the printer. - std::vector meshes; + std::vector> meshes; std::vector retraction_wipe_config_per_extruder; //!< Config for retractions, extruder switch retractions, and wipes, per extruder. SupportStorage support; - + std::vector skirt_brim[MAX_EXTRUDERS]; //!< Skirt/brim polygons per extruder, ordered from inner to outer polygons. Polygons support_brim; //!< brim lines for support, going from the edge of the support inward. \note Not ordered by inset. - Polygons raftOutline; //Storage for the outline of the raft. Will be filled with lines when the GCode is generated. - Polygons primeRaftOutline; // ... the raft underneath the prime-tower will have to be printed first, if there is one. (When the raft has top layers with a different extruder for example.) + Polygons raftOutline; // Storage for the outline of the raft. Will be filled with lines when the GCode is generated. int max_print_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder std::vector max_print_height_per_extruder; //!< For each extruder the highest layer number at which it is used. std::vector max_print_height_order; //!< Ordered indices into max_print_height_per_extruder: back() will return the extruder number with the highest print height. std::vector spiralize_seam_vertex_indices; //!< the index of the seam vertex for each layer - std::vector spiralize_wall_outlines; //!< the wall outline polygons for each layer + std::vector spiralize_wall_outlines; //!< the wall outline polygons for each layer PrimeTower primeTower; - std::vector oozeShield; //oozeShield per layer + std::vector oozeShield; // oozeShield per layer Polygons draft_protection_shield; //!< The polygons for a heightened skirt which protects from warping by gusts of wind and acts as a heated chamber. /*! @@ -352,7 +379,7 @@ class SliceDataStorage : public NoCopy /*! * Get all outlines within a given layer. - * + * * \param layer_nr The index of the layer for which to get the outlines * (negative layer numbers indicate the raft). * \param include_support Whether to include support in the outline. @@ -361,11 +388,13 @@ class SliceDataStorage : public NoCopy * \param external_polys_only Whether to disregard all hole polygons. * \param extruder_nr (optional) only give back outlines for this extruder (where the walls are printed with this extruder) */ - Polygons getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only = false, const int extruder_nr = -1) const; + Polygons + getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only = false, const int extruder_nr = -1) + const; /*! * Get the extruders used. - * + * * \return A vector of booleans indicating whether the extruder with the * corresponding index is used in the mesh group. */ @@ -373,7 +402,7 @@ class SliceDataStorage : public NoCopy /*! * Get the extruders used on a particular layer. - * + * * \param layer_nr the layer for which to check * \return a vector of bools indicating whether the extruder with corresponding index is used in this layer. */ @@ -402,6 +431,6 @@ class SliceDataStorage : public NoCopy std::vector initializeRetractionAndWipeConfigs(); }; -}//namespace cura +} // namespace cura -#endif//SLICE_DATA_STORAGE_H +#endif // SLICE_DATA_STORAGE_H diff --git a/include/support.h b/include/support.h index ce4c9e4ca3..16819c0c90 100644 --- a/include/support.h +++ b/include/support.h @@ -1,13 +1,18 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef SUPPORT_H #define SUPPORT_H +#include "settings/types/LayerIndex.h" +#include "utils/polygon.h" + +#include +#include + namespace cura { -struct LayerIndex; class Settings; class SliceDataStorage; class SliceMeshStorage; @@ -19,7 +24,7 @@ class AreaSupport /*! * \brief Move support mesh outlines from slicer data into the support * storage. - * + * * \param[out] storage Where to store the support areas. * \param mesh_settings Where to get the settings from what kind of support * mesh it is. @@ -69,7 +74,8 @@ class AreaSupport * \param global_support_areas_per_layer the global support areas per layer * \param total_layer_count total number of layers */ - static void splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage& storage, const std::vector& global_support_areas_per_layer, unsigned int total_layer_count); + static void + splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage& storage, const std::vector& global_support_areas_per_layer, unsigned int total_layer_count); /*! * Generate gradual support on the already generated support areas. This must be called after generateSupportAreas(). @@ -100,11 +106,11 @@ class AreaSupport /*! * \brief Combines the support infill of multiple layers. - * + * * The support infill layers are combined while the thickness of each layer is * multiplied such that the infill should fill up again to the full height of * all combined layers. - * + * * \param storage data storage containing the input layer outline data and containing the output support storage per layer */ static void combineSupportInfillLayers(SliceDataStorage& storage); @@ -133,7 +139,7 @@ class AreaSupport * meshes with drop down and once for all support meshes without drop down. * The \p mesh_idx should then correspond to an empty \ref SliceMeshStorage * of one support mesh with the given value of support_mesh_drop_down. - * + * * \param storage Data storage containing the input layer outline data. * \param infill_settings The settings which are based on the infill of the * support. @@ -145,7 +151,14 @@ class AreaSupport * areas. * \param layer_count Total number of layers. */ - static void generateSupportAreasForMesh(SliceDataStorage& storage, const Settings& infill_settings, const Settings& roof_settings, const Settings& bottom_settings, const size_t mesh_idx, const size_t layer_count, std::vector& support_areas); + static void generateSupportAreasForMesh( + SliceDataStorage& storage, + const Settings& infill_settings, + const Settings& roof_settings, + const Settings& bottom_settings, + const size_t mesh_idx, + const size_t layer_count, + std::vector& support_areas); /*! * Generate support bottom areas for a given mesh. @@ -192,7 +205,13 @@ class AreaSupport * \param minimum_interface_area Minimum area size for resulting interface polygons. * \param[out] interface_polygons The resulting interface layer. Do not use `interface` in windows! */ - static void generateSupportInterfaceLayer(Polygons& support_areas, const Polygons mesh_outlines, const coord_t safety_offset, const coord_t outline_offset, const double minimum_interface_area, Polygons& interface_polygons); + static void generateSupportInterfaceLayer( + Polygons& support_areas, + const Polygons mesh_outlines, + const coord_t safety_offset, + const coord_t outline_offset, + const double minimum_interface_area, + Polygons& interface_polygons); /*! * \brief Join current support layer with the support of the layer above, @@ -209,20 +228,26 @@ class AreaSupport /*! * Move the support up from model (cut away polygons to ensure bottom z distance) * and apply stair step transformation. - * + * * If the bottom stairs defined only by the step height are too wide, * the top half of the step will be as wide as the stair step width * and the bottom half will follow the model. - * + * * \param storage Where to get model outlines from - * \param[in,out] stair_removal The polygons to be removed for stair stepping on the current layer (input) and for the next layer (output). Only changed every [step_height] layers. - * \param[in,out] support_areas The support areas before and after this function - * \param layer_idx The layer number of the support layer we are processing - * \param bottom_empty_layer_count The number of empty layers between the bottom of support and the top of the model on which support rests - * \param bottom_stair_step_layer_count The max height (in nr of layers) of the support bottom stairs - * \param support_bottom_stair_step_width The max width of the support bottom stairs + * \param[in,out] stair_removal The polygons to be removed for stair stepping on the current layer (input) and for the next layer (output). Only changed every [step_height] + * layers. \param[in,out] support_areas The support areas before and after this function \param layer_idx The layer number of the support layer we are processing \param + * bottom_empty_layer_count The number of empty layers between the bottom of support and the top of the model on which support rests \param bottom_stair_step_layer_count The + * max height (in nr of layers) of the support bottom stairs \param support_bottom_stair_step_width The max width of the support bottom stairs */ - static void moveUpFromModel(const SliceDataStorage& storage, Polygons& stair_removal, Polygons& sloped_areas, Polygons& support_areas, const size_t layer_idx, const size_t bottom_empty_layer_count, const size_t bottom_stair_step_layer_count, const coord_t support_bottom_stair_step_width); + static void moveUpFromModel( + const SliceDataStorage& storage, + Polygons& stair_removal, + Polygons& sloped_areas, + Polygons& support_areas, + const size_t layer_idx, + const size_t bottom_empty_layer_count, + const size_t bottom_stair_step_layer_count, + const coord_t support_bottom_stair_step_width); /*! * Joins the layer part outlines of all meshes and collects the overhang @@ -231,26 +256,26 @@ class AreaSupport * \param mesh Output mesh to store the resulting overhang points in. */ static void detectOverhangPoints(const SliceDataStorage& storage, SliceMeshStorage& mesh); - + /*! * \brief Compute the basic overhang and full overhang of a layer. * * The basic overhang consists of the parts of this layer which are too far * away from the layer below to be supported. The full overhang consists of * the basic overhang extended toward the border of the layer below. - * + * * layer 2 * layer 1 ______________| * _______| ^^^^^ basic overhang * ^^^^^^^^^^^^^^ full overhang - * + * * \param storage The slice data storage. * \param mesh The mesh for which to compute the basic overhangs. * \param layer_idx The layer for which to compute the overhang. * \return A pair of basic overhang and full overhang. */ - static std::pair computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const unsigned int layer_idx); - + static std::pair computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const LayerIndex& layer_idx); + /*! * \brief Adds tower pieces to the current support layer. * @@ -272,9 +297,8 @@ class AreaSupport std::vector& tower_roofs, std::vector>& overhang_points, LayerIndex layer_idx, - size_t layer_count - ); - + size_t layer_count); + /*! * \brief Adds struts (towers against a wall) to the current layer. * \param settings The settings to use to create the wall struts. @@ -286,10 +310,10 @@ class AreaSupport /*! * Clean up the SupportInfillParts. * Remove parts which have nothing to be printed. - * + * * Remove parts which are too small for the first wall. * For parts without walls: remove if combined into upper layers. - * + * */ static void cleanup(SliceDataStorage& storage); @@ -306,6 +330,6 @@ class AreaSupport }; -}//namespace cura +} // namespace cura -#endif//SUPPORT_H +#endif // SUPPORT_H diff --git a/include/timeEstimate.h b/include/timeEstimate.h index ea80d9a6f9..4ce741a6ba 100644 --- a/include/timeEstimate.h +++ b/include/timeEstimate.h @@ -1,21 +1,21 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #ifndef TIME_ESTIMATE_H #define TIME_ESTIMATE_H -#include -#include -#include - #include "PrintFeature.h" #include "settings/types/Duration.h" //Print time estimates. +#include "settings/types/Ratio.h" #include "settings/types/Velocity.h" //Speeds and accelerations at which we print. +#include +#include +#include + namespace cura { -class Ratio; class Settings; /*! @@ -36,18 +36,31 @@ class TimeEstimateCalculator class Position { public: - Position() {for(unsigned int n=0;n blocks; + public: /*! * \brief Set the movement configuration of the firmware. @@ -96,8 +110,9 @@ class TimeEstimateCalculator void setMaxXyJerk(const Velocity& jerk); //!< Set the max xy jerk to \p jerk void reset(); - + std::vector calculate(); + private: void reversePass(); void forwardPass(); @@ -108,14 +123,14 @@ class TimeEstimateCalculator void recalculateTrapezoids(); // Calculates trapezoid parameters so that the entry- and exit-speed is compensated by the provided factors. - void calculateTrapezoidForBlock(Block *block, const Ratio entry_factor, const Ratio exit_factor); + void calculateTrapezoidForBlock(Block* block, const Ratio entry_factor, const Ratio exit_factor); // The kernel called by accelerationPlanner::calculate() when scanning the plan from last to first entry. - void plannerReversePassKernel(Block *previous, Block *current, Block *next); + void plannerReversePassKernel(Block* previous, Block* current, Block* next); // The kernel called by accelerationPlanner::calculate() when scanning the plan from first to last entry. - void plannerForwardPassKernel(Block *previous, Block *current, Block *next); + void plannerForwardPassKernel(Block* previous, Block* current, Block* next); }; -}//namespace cura -#endif//TIME_ESTIMATE_H +} // namespace cura +#endif // TIME_ESTIMATE_H diff --git a/include/utils/AABB3D.h b/include/utils/AABB3D.h index 7b344b292f..c7fca9c486 100644 --- a/include/utils/AABB3D.h +++ b/include/utils/AABB3D.h @@ -1,16 +1,15 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef UTILS_AABB3D_H #define UTILS_AABB3D_H #include "IntPoint.h" +#include "utils/AABB.h" namespace cura { -class AABB; - /*! An Axis Aligned Bounding Box. Has a min and max vector, representing minimal and maximal coordinates in the three axes. */ @@ -44,9 +43,9 @@ struct AABB3D /*! * Check whether this aabb overlaps with another. - * + * * In the boundary case false is returned. - * + * * \param other the aabb to check for overlaps with * \return Whether the two aabbs overlap */ @@ -91,7 +90,7 @@ struct AABB3D /*! * Offset the bounding box in the horizontal direction; outward or inward. - * + * * \param outset the distance (positive or negative) to expand the bounding box outward * \return this object (which has changed) */ @@ -99,13 +98,12 @@ struct AABB3D /*! * Offset the bounding box in the horizontal direction; outward or inward. - * + * * \param outset the distance (positive or negative) to expand the bounding box outward * \return this object (which has changed) */ AABB3D expandXY(coord_t outset); }; -}//namespace cura -#endif//UTILS_AABB3D_H - +} // namespace cura +#endif // UTILS_AABB3D_H diff --git a/include/utils/FMatrix4x3.h b/include/utils/FMatrix4x3.h index 441d693ac5..64d44138ca 100644 --- a/include/utils/FMatrix4x3.h +++ b/include/utils/FMatrix4x3.h @@ -1,15 +1,16 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef FMATRIX4X3_H #define FMATRIX4X3_H +#include "settings/types/Ratio.h" + namespace cura { class FPoint3; class Point3; -class Ratio; /*! * A 4x3 affine transformation matrix. @@ -32,7 +33,7 @@ class FMatrix4x3 /*! * The matrix data, row-endian. - * + * * The first index is the column. The second index is the row. */ double m[4][3]; @@ -60,5 +61,5 @@ class FMatrix4x3 Point3 apply(const Point3& p) const; }; -} //namespace cura -#endif //FMATRIX4X3_H \ No newline at end of file +} // namespace cura +#endif // FMATRIX4X3_H \ No newline at end of file diff --git a/include/utils/IntPoint.h b/include/utils/IntPoint.h index 68aa4f84da..ce5cbc9785 100644 --- a/include/utils/IntPoint.h +++ b/include/utils/IntPoint.h @@ -1,5 +1,5 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef UTILS_INT_POINT_H #define UTILS_INT_POINT_H @@ -10,21 +10,19 @@ Integer points are used to avoid floating point rounding errors, and because Cli */ #define INLINE static inline -//Include Clipper to get the ClipperLib::IntPoint definition, which we reuse as Point definition. -#include +// Include Clipper to get the ClipperLib::IntPoint definition, which we reuse as Point definition. +#include "../utils/math.h" // for M_PI. Use relative path to avoid pulling +#include "Point3.h" //For applying Point3Matrices. + #include #include // for hash function object #include // auto-serialization / auto-toString() #include +#include #include -#include "Point3.h" //For applying Point3Matrices. - - -#include "../utils/math.h" // for M_PI. Use relative path to avoid pulling - #ifdef __GNUC__ -#define DEPRECATED(func) func __attribute__ ((deprecated)) +#define DEPRECATED(func) func __attribute__((deprecated)) #elif defined(_MSC_VER) #define DEPRECATED(func) __declspec(deprecated) func #else @@ -45,22 +43,62 @@ typedef ClipperLib::IntPoint Point; static Point no_point(std::numeric_limits::min(), std::numeric_limits::min()); /* Extra operators to make it easier to do math with the 64bit Point objects */ -INLINE Point operator-(const Point& p0) { return Point(-p0.X, -p0.Y); } -INLINE Point operator+(const Point& p0, const Point& p1) { return Point(p0.X+p1.X, p0.Y+p1.Y); } -INLINE Point operator-(const Point& p0, const Point& p1) { return Point(p0.X-p1.X, p0.Y-p1.Y); } -INLINE Point operator*(const Point& p0, const coord_t i) { return Point(p0.X * i, p0.Y * i); } -template::value, T>::type> //Use only for numeric types. -INLINE Point operator*(const Point& p0, const T i) { return Point(p0.X * i, p0.Y * i); } -template::value, T>::type> //Use only for numeric types. -INLINE Point operator*(const T i, const Point& p0) { return p0 * i; } -INLINE Point operator/(const Point& p0, const coord_t i) { return Point(p0.X/i, p0.Y/i); } -INLINE Point operator/(const Point& p0, const Point& p1) { return Point(p0.X/p1.X, p0.Y/p1.Y); } -INLINE Point operator%(const Point& p0, const coord_t i) { return Point(p0.X%i, p0.Y%i); } - -INLINE Point& operator += (Point& p0, const Point& p1) { p0.X += p1.X; p0.Y += p1.Y; return p0; } -INLINE Point& operator -= (Point& p0, const Point& p1) { p0.X -= p1.X; p0.Y -= p1.Y; return p0; } - -INLINE bool operator < (const Point& p0, const Point& p1) { return p0.X < p1.X || (p0.X == p1.X && p0.Y < p1.Y); } +INLINE Point operator-(const Point& p0) +{ + return Point(-p0.X, -p0.Y); +} +INLINE Point operator+(const Point& p0, const Point& p1) +{ + return Point(p0.X + p1.X, p0.Y + p1.Y); +} +INLINE Point operator-(const Point& p0, const Point& p1) +{ + return Point(p0.X - p1.X, p0.Y - p1.Y); +} +INLINE Point operator*(const Point& p0, const coord_t i) +{ + return Point(p0.X * i, p0.Y * i); +} +template::value, T>::type> // Use only for numeric types. +INLINE Point operator*(const Point& p0, const T i) +{ + return Point(std::llrint(p0.X * i), std::llrint(p0.Y * i)); +} +template::value, T>::type> // Use only for numeric types. +INLINE Point operator*(const T i, const Point& p0) +{ + return p0 * i; +} +INLINE Point operator/(const Point& p0, const coord_t i) +{ + return Point(p0.X / i, p0.Y / i); +} +INLINE Point operator/(const Point& p0, const Point& p1) +{ + return Point(p0.X / p1.X, p0.Y / p1.Y); +} +INLINE Point operator%(const Point& p0, const coord_t i) +{ + return Point(p0.X % i, p0.Y % i); +} + +INLINE Point& operator+=(Point& p0, const Point& p1) +{ + p0.X += p1.X; + p0.Y += p1.Y; + return p0; +} +INLINE Point& operator-=(Point& p0, const Point& p1) +{ + p0.X -= p1.X; + p0.Y -= p1.Y; + return p0; +} + +INLINE bool operator<(const Point& p0, const Point& p1) +{ + return p0.X < p1.X || (p0.X == p1.X && p0.Y < p1.Y); +} /* ***** NOTE ***** TL;DR: DO NOT implement operators *= and /= because of the default values in ClipperLib::IntPoint's constructor. @@ -71,16 +109,16 @@ INLINE bool operator < (const Point& p0, const Point& p1) { return p0.X < p1.X | an IntPoint(5, y = 0) and you end up with wrong results. */ -//INLINE bool operator==(const Point& p0, const Point& p1) { return p0.X==p1.X&&p0.Y==p1.Y; } -//INLINE bool operator!=(const Point& p0, const Point& p1) { return p0.X!=p1.X||p0.Y!=p1.Y; } +// INLINE bool operator==(const Point& p0, const Point& p1) { return p0.X==p1.X&&p0.Y==p1.Y; } +// INLINE bool operator!=(const Point& p0, const Point& p1) { return p0.X!=p1.X||p0.Y!=p1.Y; } INLINE coord_t vSize2(const Point& p0) { - return p0.X*p0.X+p0.Y*p0.Y; + return p0.X * p0.X + p0.Y * p0.Y; } INLINE float vSize2f(const Point& p0) { - return static_cast(p0.X)*static_cast(p0.X)+static_cast(p0.Y)*static_cast(p0.Y); + return static_cast(p0.X) * static_cast(p0.X) + static_cast(p0.Y) * static_cast(p0.Y); } INLINE bool shorterThen(const Point& p0, const coord_t len) @@ -110,7 +148,7 @@ INLINE double vSizeMM(const Point& p0) { double fx = INT2MM(p0.X); double fy = INT2MM(p0.Y); - return sqrt(fx*fx+fy*fy); + return sqrt(fx * fx + fy * fy); } INLINE Point normal(const Point& p0, coord_t len) @@ -146,7 +184,8 @@ INLINE coord_t cross(const Point& p0, const Point& p1) INLINE int angle(const Point& p) { double angle = std::atan2(p.X, p.Y) / M_PI * 180.0; - if (angle < 0.0) angle += 360.0; + if (angle < 0.0) + angle += 360.0; return angle; } @@ -156,12 +195,14 @@ INLINE const Point& make_point(const Point& p) return p; } -}//namespace cura +} // namespace cura -namespace std { -template <> -struct hash { - size_t operator()(const cura::Point & pp) const +namespace std +{ +template<> +struct hash +{ + size_t operator()(const cura::Point& pp) const { static int prime = 31; int result = 89; @@ -170,7 +211,7 @@ struct hash { return result; } }; -} +} // namespace std namespace cura { @@ -234,8 +275,8 @@ class PointMatrix PointMatrix ret; double det = matrix[0] * matrix[3] - matrix[1] * matrix[2]; ret.matrix[0] = matrix[3] / det; - ret.matrix[1] = - matrix[1] / det; - ret.matrix[2] = - matrix[2] / det; + ret.matrix[1] = -matrix[1] / det; + ret.matrix[2] = -matrix[2] / det; ret.matrix[3] = matrix[0] / det; return ret; } @@ -278,9 +319,10 @@ class Point3Matrix Point3 apply(const Point3 p) const { - return Point3(p.x * matrix[0] + p.y * matrix[1] + p.z * matrix[2] - , p.x * matrix[3] + p.y * matrix[4] + p.z * matrix[5] - , p.x * matrix[6] + p.y * matrix[7] + p.z * matrix[8]); + return Point3( + std::llrint(p.x * matrix[0] + p.y * matrix[1] + p.z * matrix[2]), + std::llrint(p.x * matrix[3] + p.y * matrix[4] + p.z * matrix[5]), + std::llrint(p.x * matrix[6] + p.y * matrix[7] + p.z * matrix[8])); } /*! @@ -319,33 +361,38 @@ class Point3Matrix }; -inline Point3 operator+(const Point3& p3, const Point& p2) { +inline Point3 operator+(const Point3& p3, const Point& p2) +{ return Point3(p3.x + p2.X, p3.y + p2.Y, p3.z); } -inline Point3& operator+=(Point3& p3, const Point& p2) { +inline Point3& operator+=(Point3& p3, const Point& p2) +{ p3.x += p2.X; p3.y += p2.Y; return p3; } -inline Point operator+(const Point& p2, const Point3& p3) { +inline Point operator+(const Point& p2, const Point3& p3) +{ return Point(p3.x + p2.X, p3.y + p2.Y); } -inline Point3 operator-(const Point3& p3, const Point& p2) { +inline Point3 operator-(const Point3& p3, const Point& p2) +{ return Point3(p3.x - p2.X, p3.y - p2.Y, p3.z); } -inline Point3& operator-=(Point3& p3, const Point& p2) { +inline Point3& operator-=(Point3& p3, const Point& p2) +{ p3.x -= p2.X; p3.y -= p2.Y; return p3; } -inline Point operator-(const Point& p2, const Point3& p3) { +inline Point operator-(const Point& p2, const Point3& p3) +{ return Point(p2.X - p3.x, p2.Y - p3.y); } -}//namespace cura -#endif//UTILS_INT_POINT_H - +} // namespace cura +#endif // UTILS_INT_POINT_H diff --git a/include/utils/Simplify.h b/include/utils/Simplify.h index a5a8d95e65..6cad5fcaf5 100644 --- a/include/utils/Simplify.h +++ b/include/utils/Simplify.h @@ -133,6 +133,27 @@ class Simplify */ constexpr static coord_t min_resolution = 5; //5 units, regardless of how big those are, to allow for rounding errors. + template + bool detectSmall(const Polygonal& polygon, const coord_t& min_size) const + { + if (polygon.size() < min_size) //For polygon, 2 or fewer vertices is degenerate. Delete it. For polyline, 1 vertex is degenerate. + { + return true; + } + if (polygon.size() == min_size) + { + const auto a = getPosition(polygon[0]); + const auto b = getPosition(polygon[1]); + const auto c = getPosition(polygon[polygon.size() - 1]); + if (std::max(std::max(vSize2(b - a), vSize2(c - a)), vSize2(c - b)) < min_resolution * min_resolution) + { + // ... unless they are degenetate. + return true; + } + } + return false; + } + /*! * The main simplification algorithm starts here. * \tparam Polygonal A polygonal object, which is a list of vertices. @@ -144,7 +165,7 @@ class Simplify Polygonal simplify(const Polygonal& polygon, const bool is_closed) const { const size_t min_size = is_closed ? 3 : 2; - if(polygon.size() < min_size) //For polygon, 2 or fewer vertices is degenerate. Delete it. For polyline, 1 vertex is degenerate. + if (detectSmall(polygon, min_size)) { return createEmpty(polygon); } @@ -160,32 +181,41 @@ class Simplify }; std::priority_queue, std::vector>, decltype(comparator)> by_importance(comparator); - //Add the initial points. - for(size_t i = 0; i < polygon.size(); ++i) - { - const coord_t vertex_importance = importance(polygon, to_delete, i, is_closed); - by_importance.emplace(i, vertex_importance); - } - - //Iteratively remove the least important point until a threshold. Polygonal result = polygon; //Make a copy so that we can also shift vertices. - coord_t vertex_importance = 0; - while(by_importance.size() > min_size) + for (int64_t current_removed = -1; (polygon.size() - current_removed) > min_size && current_removed != 0;) { - std::pair vertex = by_importance.top(); - by_importance.pop(); - //The importance may have changed since this vertex was inserted. Re-compute it now. - //If it doesn't change, it's safe to process. - vertex_importance = importance(result, to_delete, vertex.first, is_closed); - if(vertex_importance != vertex.second) + current_removed = 0; + + //Add the initial points. + for (size_t i = 0; i < result.size(); ++i) { - by_importance.emplace(vertex.first, vertex_importance); //Re-insert with updated importance. - continue; + if (to_delete[i]) + { + continue; + } + const coord_t vertex_importance = importance(result, to_delete, i, is_closed); + by_importance.emplace(i, vertex_importance); } - if(vertex_importance <= max_deviation * max_deviation) + //Iteratively remove the least important point until a threshold. + coord_t vertex_importance = 0; + while (! by_importance.empty() && (polygon.size() - current_removed) > min_size) { - remove(result, to_delete, vertex.first, vertex_importance, is_closed); + std::pair vertex = by_importance.top(); + by_importance.pop(); + //The importance may have changed since this vertex was inserted. Re-compute it now. + //If it doesn't change, it's safe to process. + vertex_importance = importance(result, to_delete, vertex.first, is_closed); + if (vertex_importance != vertex.second) + { + by_importance.emplace(vertex.first, vertex_importance); //Re-insert with updated importance. + continue; + } + + if (vertex_importance <= max_deviation * max_deviation) + { + current_removed += remove(result, to_delete, vertex.first, vertex_importance, is_closed) ? 1 : 0; + } } } @@ -199,6 +229,10 @@ class Simplify } } + if (detectSmall(filtered, min_size)) + { + return createEmpty(filtered); + } return filtered; } @@ -260,17 +294,18 @@ class Simplify * \param vertex The index of the vertex to remove. * \param deviation2 The previously found deviation for this vertex. * \param is_closed Whether we're working on a closed polygon or an open + \return Whether something is actually removed * polyline. */ template - void remove(Polygonal& polygon, std::vector& to_delete, const size_t vertex, const coord_t deviation2, const bool is_closed) const + bool remove(Polygonal& polygon, std::vector& to_delete, const size_t vertex, const coord_t deviation2, const bool is_closed) const { if(deviation2 <= min_resolution * min_resolution) { //At less than the minimum resolution we're always allowed to delete the vertex. //Even if the adjacent line segments are very long. to_delete[vertex] = true; - return; + return true; } const size_t before = previousNotDeleted(vertex, to_delete); @@ -285,7 +320,7 @@ class Simplify { //Removing this vertex does little harm. No long lines will be shifted. to_delete[vertex] = true; - return; + return true; } //Otherwise, one edge next to this vertex is longer than max_resolution. The other is shorter. @@ -296,7 +331,7 @@ class Simplify { if(!is_closed && before == 0) //No edge before the short edge. { - return; //Edge cannot be deleted without shifting a long edge. Don't remove anything. + return false; //Edge cannot be deleted without shifting a long edge. Don't remove anything. } const size_t before_before = previousNotDeleted(before, to_delete); before_from = getPosition(polygon[before_before]); @@ -308,7 +343,7 @@ class Simplify { if(!is_closed && after == polygon.size() - 1) //No edge after the short edge. { - return; //Edge cannot be deleted without shifting a long edge. Don't remove anything. + return false; //Edge cannot be deleted without shifting a long edge. Don't remove anything. } const size_t after_after = nextNotDeleted(after, to_delete); before_from = getPosition(polygon[before]); @@ -320,14 +355,16 @@ class Simplify const bool did_intersect = LinearAlg2D::lineLineIntersection(before_from, before_to, after_from, after_to, intersection); if(!did_intersect) //Lines are parallel. { - return; //Cannot remove edge without shifting a long edge. Don't remove anything. + return false; //Cannot remove edge without shifting a long edge. Don't remove anything. } const coord_t intersection_deviation = LinearAlg2D::getDist2FromLineSegment(before_to, intersection, after_from); if(intersection_deviation <= max_deviation * max_deviation) //Intersection point doesn't deviate too much. Use it! { to_delete[vertex] = true; polygon[length2_before <= length2_after ? before : after] = createIntersection(polygon[before], intersection, polygon[after]); + return true; } + return false; } /*! diff --git a/include/utils/actions/smooth.h b/include/utils/actions/smooth.h new file mode 100644 index 0000000000..67e4adebe6 --- /dev/null +++ b/include/utils/actions/smooth.h @@ -0,0 +1,294 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_VIEWS_SMOOTH_H +#define UTILS_VIEWS_SMOOTH_H + +#include "settings/Settings.h" +#include "settings/types/Angle.h" +#include "utils/types/arachne.h" +#include "utils/types/generic.h" +#include "utils/types/geometry.h" +#include "utils/types/get.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace cura +{ +class SmoothTest_TestSmooth_Test; +} // namespace cura + +namespace cura::actions +{ + +struct smooth_fn +{ + friend class cura::SmoothTest_TestSmooth_Test; + + auto operator()(const Settings& settings) const + { + const auto fluid_motion_shift_distance = settings.get("meshfix_fluid_motion_shift_distance"); + const auto fluid_motion_small_distance = settings.get("meshfix_fluid_motion_small_distance"); + const auto fluid_motion_angle = settings.get("meshfix_fluid_motion_angle").value; + return ranges::make_action_closure(ranges::bind_back(smooth_fn{}, fluid_motion_shift_distance, fluid_motion_small_distance, fluid_motion_angle)); + } + + constexpr auto operator()( + const utils::integral auto fluid_motion_shift_distance, + const utils::integral auto fluid_motion_small_distance, + const utils::floating_point auto fluid_motion_angle) const + { + return ranges::make_action_closure(ranges::bind_back(smooth_fn{}, fluid_motion_shift_distance, fluid_motion_small_distance, fluid_motion_angle)); + } + + template + requires ranges::forward_range && ranges::sized_range && ranges::erasable_range, ranges::sentinel_t> &&( + utils::point2d> || utils::junctions)constexpr auto + operator()( + Rng&& rng, + const utils::integral auto fluid_motion_shift_distance, + const utils::integral auto fluid_motion_small_distance, + const utils::floating_point auto fluid_motion_angle) const + { + const auto size = ranges::distance(rng) - 1; + if (size < 4) + { + return static_cast(rng); + } + + const auto fluid_motion_shift_distance3 = 3 * fluid_motion_shift_distance; + const auto cos_fluid_motion_angle = std::cos(fluid_motion_angle); + + auto tmp = rng; // We don't want to shift the points of the in-going range, therefore we create a temporary copy + auto windows = ranges::views::concat(ranges::views::single(ranges::back(tmp)), ranges::views::concat(tmp, tmp | ranges::views::take(4))) | ranges::views::addressof; + + // Smooth the path, by moving over three segments at a time. If the middle segment is shorter than the max resolution, then we try shifting those points outwards. + // The previous and next segment should have a remaining length of at least the smooth distance, otherwise the point is not shifted, but deleted. + for (auto windows_it = ranges::begin(windows); ranges::distance(windows_it, ranges::end(windows)) > 2; ++windows_it) + { + const auto a = *windows_it; + const auto b = *std::next(windows_it, 1); + const auto c = *std::next(windows_it, 2); + const auto d = *std::next(windows_it, 3); + + const auto magnitude_ab = dist(*a, *b); + const auto magnitude_bc = dist(*b, *c); + const auto magnitude_cd = dist(*c, *d); + + if (magnitude_bc > fluid_motion_small_distance) + { + continue; + } + + // only if segments ab and cd are long enough, we can shift b and c + // 3 * fluid_motion_shift_distance is the minimum length of the segments ab and cd + // as this allows us to shift both ends with the shift distance and still have some room to spare + if (magnitude_ab < fluid_motion_shift_distance3 || magnitude_cd < fluid_motion_shift_distance3) + { + continue; + } + + if (! isSmooth(*a, *b, *c, *d, cos_fluid_motion_angle, magnitude_ab, magnitude_bc, magnitude_cd)) + { + *b = shiftPointTowards(*b, *a, fluid_motion_shift_distance, magnitude_ab); + *c = shiftPointTowards(*c, *d, fluid_motion_shift_distance, magnitude_cd); + } + } + + return tmp; + } + +private: + /* + * cosine of the angle between the vectors AB and BC + * + * B---------C + * | \ + * | angle + * | + * | + * A + * + */ + template + requires utils::point2d || utils::junction + inline constexpr auto cosAngle(Point& a, Point& b, Point& c) const noexcept + { + return cosAngle(a, b, c, dist(a, b), dist(b, c)); + } + + template + requires utils::point2d || utils::junction + inline constexpr auto cosAngle(Point& a, Point& b, Point& c, const utils::floating_point auto ab_magnitude, const utils::floating_point auto bc_magnitude) const noexcept + { + return cosAngle(a, b, b, c, ab_magnitude, bc_magnitude); + } + + /* + * cosine of the angle between the vectors AB and CD + * + * A C + * | \ + * | \ + * B D + * + * The angle will be calculated by shifting points A and C towards the origin, + * and then calculating the angle between the vectors AB and CD. + * + * A,C + * | \ + * | \ + * B D + * + */ + template + requires utils::point2d || utils::junction + inline constexpr auto cosAngle(Point& a, Point& b, Point& c, Point& d) const noexcept + { + return cosAngle(a, b, c, d, dist(a, b), dist(c, d)); + } + + template + requires utils::point2d || utils::junction + inline constexpr auto + cosAngle(Point& a, Point& b, Point& c, Point& d, const utils::floating_point auto ab_magnitude, const utils::floating_point auto bc_magnitude) const noexcept + { + Point vector_ab = { std::get<"X">(b) - std::get<"X">(a), std::get<"Y">(b) - std::get<"Y">(a) }; + Point vector_cd = { std::get<"X">(d) - std::get<"X">(c), std::get<"Y">(d) - std::get<"Y">(c) }; + + return cosAngle(vector_ab, vector_cd, ab_magnitude, bc_magnitude); + } + + /* + * cosine of the angle between the vectors A and B + * + * O (origin) + * | \ + * | \ + * A B + * + */ + template + requires utils::point2d || utils::junction + inline constexpr auto cosAngle(Vector& a, Vector& b) const noexcept + { + return cosAngle(a, b, magnitude(a), magnitude(b)); + } + + template + requires utils::point2d || utils::junction + inline constexpr auto cosAngle(Vector& a, Vector& b, const utils::floating_point auto a_magnitude, const utils::floating_point auto b_magnitude) const noexcept + { + if (a_magnitude <= std::numeric_limits::epsilon() || b_magnitude <= std::numeric_limits::epsilon()) + { + return static_cast(0.0); + } + return static_cast(dotProduct(a, b)) / (a_magnitude * b_magnitude); + } + + template + requires utils::point2d || utils::junction + inline constexpr Point shiftPointTowards(Point& p0, Point& p1, const utils::numeric auto move_distance) const noexcept + { + return shiftPointTowards(p0, p1, move_distance, dist(p0, p1)); + } + + template + requires utils::point2d || utils::junction + inline constexpr Point shiftPointTowards(Point& p0, Point& p1, const utils::numeric auto move_distance, const utils::floating_point auto p0p1_distance) const noexcept + { + using coord_type = std::remove_cvref_t(p0))>; + const auto shift_distance = move_distance / p0p1_distance; + return { + std::get<"X">(p0) + static_cast((std::get<"X">(p1) - std::get<"X">(p0)) * shift_distance), + std::get<"Y">(p0) + static_cast((std::get<"Y">(p1) - std::get<"Y">(p0)) * shift_distance), + }; + } + + template + requires utils::point2d || utils::junction + inline constexpr utils::floating_point auto dist(Point& point_0, Point& point_1) const noexcept + { + Point vector = { std::get<"X">(point_1) - std::get<"X">(point_0), std::get<"Y">(point_1) - std::get<"Y">(point_0) }; + return magnitude(vector); + } + + template + requires utils::point2d || utils::junction + inline constexpr utils::floating_point auto magnitude(Vector& v) const noexcept + { + return std::hypot(std::get<"X">(v), std::get<"Y">(v)); + } + + template + requires utils::point2d || utils::junction + inline constexpr auto dotProduct(Vector& point_0, Vector& point_1) const noexcept + { + return std::get<"X">(point_0) * std::get<"X">(point_1) + std::get<"Y">(point_0) * std::get<"Y">(point_1); + } + + template + requires utils::point2d || utils::junction + constexpr bool isSmooth(Point& a, Point& b, Point& c, Point& d, const utils::floating_point auto fluid_motion_angle) const noexcept + { + return isSmooth(a, b, c, d, fluid_motion_angle, dist(a, b), dist(b, c), dist(c, d)); + } + + template + requires utils::point2d || utils::junction + constexpr bool isSmooth( + Point& a, + Point& b, + Point& c, + Point& d, + const utils::floating_point auto fluid_motion_angle, + const utils::floating_point auto dist_ab, + const utils::floating_point auto dist_bc, + const utils::floating_point auto dist_cd) const noexcept + { + /* + * Move points A and D, so they are both at equal distance from B and C + * + * B--C + * / \ + * A_ D_ + * / \ + * / \ + * A \ + * \ + * D + * + * Points B, C are in a "fluid motion" with points A, D if + * vectors [A_,D_] and [B,C] are oriented within a certain angle + */ + constexpr auto shift_distance = 300.; + auto a_ = shiftPointTowards(b, a, shift_distance, dist_ab); + auto d_ = shiftPointTowards(c, d, shift_distance, dist_cd); + + const auto cos_angle_fluid = cosAngle(a_, d_, b, c, dist(a_, d_), dist_bc); + const auto cos_angle_abc = cosAngle(a_, b, c, shift_distance, dist_bc); + const auto cos_angle_bcd = cosAngle(b, c, d_, dist_bc, shift_distance); + + // The motion is fluid if either of the marker angles is smaller than the max angle + return cos_angle_fluid >= fluid_motion_angle || cos_angle_abc >= fluid_motion_angle || cos_angle_bcd >= fluid_motion_angle; + } +}; + +inline constexpr smooth_fn smooth{}; +} // namespace cura::actions + +#endif // UTILS_VIEWS_SMOOTH_H diff --git a/include/utils/channel.h b/include/utils/channel.h new file mode 100644 index 0000000000..4ff915ffe7 --- /dev/null +++ b/include/utils/channel.h @@ -0,0 +1,29 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CHANNEL_H +#define CHANNEL_H + +#include +#include +#include + +#ifndef ENABLE_REMOTE_PLUGINS +#define ENABLE_REMOTE_PLUGINS false +#endif + +namespace cura::utils +{ +struct ChannelSetupConfiguration +{ +public: + std::string host; + uint64_t port; + uint64_t shibolet = 0UL; // TODO: Either get from startup (server would receive this as well) or remove completely. +}; + +std::shared_ptr createChannel(const ChannelSetupConfiguration& config = { "localhost", 50010UL }); + +} // namespace cura::utils + +#endif // CHANNEL_H diff --git a/include/utils/concepts/generic.h b/include/utils/concepts/generic.h deleted file mode 100644 index 309c5c115f..0000000000 --- a/include/utils/concepts/generic.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2023 UltiMaker -// CuraEngine is released under the terms of the AGPLv3 or higher - -#ifndef CURAENGINE_GENERIC_H -#define CURAENGINE_GENERIC_H - -#include -#include - -namespace cura -{ -template -concept hashable = requires(T value) -{ - { std::hash{}(value) } -> concepts::convertible_to; -}; -} // namespace cura - -#endif // CURAENGINE_GENERIC_H diff --git a/include/utils/format/thread_id.h b/include/utils/format/thread_id.h new file mode 100644 index 0000000000..0806144200 --- /dev/null +++ b/include/utils/format/thread_id.h @@ -0,0 +1,28 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_FORMAT_THREAD_ID_H +#define UTILS_FORMAT_THREAD_ID_H + +#include + +#include +#include +#include + +namespace fmt +{ +template<> +struct formatter : formatter +{ + template + auto format(std::thread::id thread_id, FormatContext& ctx) + { + std::ostringstream oss; + oss << thread_id; + return formatter::format(oss.str(), ctx); + } +}; + +} // namespace fmt +#endif // UTILS_FORMAT_THREAD_ID_H diff --git a/include/utils/gettime.h b/include/utils/gettime.h index b2c8088a97..0f011360ad 100644 --- a/include/utils/gettime.h +++ b/include/utils/gettime.h @@ -1,57 +1,46 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef GETTIME_H #define GETTIME_H -#ifdef _WIN32 - #include -#else - #ifdef USE_CPU_TIME - #include -#endif +#include +#include +#include -#include -#include -#include -#endif +#include namespace cura { -static inline double getTime() -{ -#ifdef _WIN32 - return double(GetTickCount()) / 1000.0; -#else // not __WIN32 - #if USE_CPU_TIME // Use cpu usage time if available, otherwise wall clock time - struct rusage usage; - #ifdef DEBUG - int ret = getrusage(RUSAGE_SELF, &usage); - assert(ret == 0); - ((void)ret); - #else - getrusage(RUSAGE_SELF, &usage); - #endif - double user_time = double(usage.ru_utime.tv_sec) + double(usage.ru_utime.tv_usec) / 1000000.0; - double sys_time = double(usage.ru_stime.tv_sec) + double(usage.ru_stime.tv_usec) / 1000000.0; - return user_time + sys_time; - #else // not USE_CPU_TIME - struct timeval tv; - gettimeofday(&tv, nullptr); - return double(tv.tv_sec) + double(tv.tv_usec) / 1000000.0; - #endif // USE_CPU_TIME -#endif // __WIN32 -} class TimeKeeper { +public: + struct RegisteredTime + { + std::string stage; + double duration; + }; + + using RegisteredTimes = std::vector; + private: - double startTime; + spdlog::stopwatch watch; + double start_time; + RegisteredTimes registered_times; + public: TimeKeeper(); - + double restart(); + + void registerTime(const std::string& stage, double threshold = 0.01); + + const RegisteredTimes& getRegisteredTimes() const + { + return registered_times; + } }; -}//namespace cura -#endif//GETTIME_H +} // namespace cura +#endif // GETTIME_H diff --git a/include/utils/linearAlg2D.h b/include/utils/linearAlg2D.h index 21af13e5d6..13dd873497 100644 --- a/include/utils/linearAlg2D.h +++ b/include/utils/linearAlg2D.h @@ -1,5 +1,5 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef UTILS_LINEAR_ALG_2D_H #define UTILS_LINEAR_ALG_2D_H @@ -14,7 +14,7 @@ class LinearAlg2D static short pointLiesOnTheRightOfLine(const Point& p, const Point& p0, const Point& p1) { // no tests unless the segment p0-p1 is at least partly at, or to right of, p.X - if ( std::max(p0.X, p1.X) >= p.X ) + if (std::max(p0.X, p1.X) >= p.X) { const coord_t pd_y = p1.Y - p0.Y; if (pd_y < 0) // p0->p1 is 'falling' @@ -54,8 +54,7 @@ class LinearAlg2D // - p1 exactly matches p (might otherwise be missed) // - p0->p1 exactly horizontal, and includes p. // (we already tested std::max(p0.X,p1.X) >= p.X ) - if (p.X == p1.X || - (pd_y == 0 && std::min(p0.X, p1.X) <= p.X) ) + if (p.X == p1.X || (pd_y == 0 && std::min(p0.X, p1.X) <= p.X)) { return 0; } @@ -63,35 +62,34 @@ class LinearAlg2D } } return -1; - } static bool lineLineIntersection(const Point& a, const Point& b, const Point& c, const Point& d, Point& output) { - //Adapted from Apex: https://github.com/Ghostkeeper/Apex/blob/eb75f0d96e36c7193d1670112826842d176d5214/include/apex/line_segment.hpp#L91 - //Adjusted to work with lines instead of line segments. + // Adapted from Apex: https://github.com/Ghostkeeper/Apex/blob/eb75f0d96e36c7193d1670112826842d176d5214/include/apex/line_segment.hpp#L91 + // Adjusted to work with lines instead of line segments. const Point l1_delta = b - a; const Point l2_delta = d - c; - const coord_t divisor = cross(l1_delta, l2_delta); //Pre-compute divisor needed for the intersection check. - if(divisor == 0) + const coord_t divisor = cross(l1_delta, l2_delta); // Pre-compute divisor needed for the intersection check. + if (divisor == 0) { - //The lines are parallel if the cross product of their directions is zero. + // The lines are parallel if the cross product of their directions is zero. return false; } - //Create a parametric representation of each line. - //We'll equate the parametric equations to each other to find the intersection then. - //Parametric equation is L = P + Vt (where P and V are a starting point and directional vector). - //We'll map the starting point of one line onto the parameter system of the other line. - //Then using the divisor we can see whether and where they cross. + // Create a parametric representation of each line. + // We'll equate the parametric equations to each other to find the intersection then. + // Parametric equation is L = P + Vt (where P and V are a starting point and directional vector). + // We'll map the starting point of one line onto the parameter system of the other line. + // Then using the divisor we can see whether and where they cross. const Point starts_delta = a - c; const coord_t l1_parametric = cross(l2_delta, starts_delta); Point result = a + Point(round_divide_signed(l1_parametric * l1_delta.X, divisor), round_divide_signed(l1_parametric * l1_delta.Y, divisor)); - if(std::abs(result.X) > std::numeric_limits::max() || std::abs(result.Y) > std::numeric_limits::max()) + if (std::abs(result.X) > std::numeric_limits::max() || std::abs(result.Y) > std::numeric_limits::max()) { - //Intersection is so far away that it could lead to integer overflows. - //Even though the lines aren't 100% parallel, it's better to pretend they are. They are practically parallel. + // Intersection is so far away that it could lead to integer overflows. + // Even though the lines aren't 100% parallel, it's better to pretend they are. They are practically parallel. return false; } output = result; @@ -103,7 +101,7 @@ class LinearAlg2D * - properly on the line : zero returned * - closer to \p a : -1 returned * - closer to \p b : 1 returned - * + * * \param from The point to check in relation to the line segment * \param a The start point of the line segment * \param b The end point of the line segment @@ -126,60 +124,63 @@ class LinearAlg2D } /*! - * Find the point closest to \p from on the line segment from \p p0 to \p p1 - */ + * Find the point closest to \p from on the line segment from \p p0 to \p p1 + */ static Point getClosestOnLineSegment(const Point& from, const Point& p0, const Point& p1) { const Point direction = p1 - p0; const Point to_from = from - p0; - const coord_t projected_x = dot(to_from, direction) ; + const coord_t projected_x = dot(to_from, direction); const coord_t x_p0 = 0; const coord_t x_p1 = vSize2(direction); if (x_p1 == 0) { - //Line segment has length 0. + // Line segment has length 0. return p0; } if (projected_x <= x_p0) { - //Projection is beyond p0. + // Projection is beyond p0. return p0; } if (projected_x >= x_p1) { - //Projection is beyond p1. + // Projection is beyond p1. return p1; } else { - //Projection is between p0 and p1. - //Return direction-normalised projection (projected_x / vSize(direction)) on direction vector. - //vSize(direction) * vSize(direction) == vSize2(direction) == x_p1. + // Projection is between p0 and p1. + // Return direction-normalised projection (projected_x / vSize(direction)) on direction vector. + // vSize(direction) * vSize(direction) == vSize2(direction) == x_p1. return p0 + projected_x * direction / x_p1; } } /*! - * Find the point closest to \p from on the line through \p p0 to \p p1 - */ + * Find the point closest to \p from on the line through \p p0 to \p p1 + */ static Point getClosestOnLine(const Point& from, const Point& p0, const Point& p1) { - if (p1 == p0) { return p0; } + if (p1 == p0) + { + return p0; + } const Point direction = p1 - p0; const Point to_from = from - p0; const coord_t projected_x = dot(to_from, direction); - Point ret = p0 + projected_x / vSize(direction) * direction / vSize(direction); + Point ret = p0 + projected_x / vSize(direction) * direction / vSize(direction); return ret; } /*! * Find the two points on two line segments closest to each other. - * + * * Find the smallest line segment connecting the two line segments a and b. - * + * * \param a1 first point on line a * \param a2 second point on line a * \param b1 first point on line b @@ -189,39 +190,39 @@ class LinearAlg2D static std::pair getClosestConnection(Point a1, Point a2, Point b1, Point b2); /*! - * Get the squared distance from point \p b to a line *segment* from \p a to \p c. - * - * In case \p b is on \p a or \p c, \p b_is_beyond_ac should become 0. - * - * \param a the first point of the line segment - * \param b the point to measure the distance from - * \param c the second point on the line segment - * \param b_is_beyond_ac optional output parameter: whether \p b is closest to the line segment (0), to \p a (-1) or \p b (1) - */ + * Get the squared distance from point \p b to a line *segment* from \p a to \p c. + * + * In case \p b is on \p a or \p c, \p b_is_beyond_ac should become 0. + * + * \param a the first point of the line segment + * \param b the point to measure the distance from + * \param c the second point on the line segment + * \param b_is_beyond_ac optional output parameter: whether \p b is closest to the line segment (0), to \p a (-1) or \p b (1) + */ static coord_t getDist2FromLineSegment(const Point& a, const Point& b, const Point& c, int16_t* b_is_beyond_ac = nullptr) { - /* - * a, - * /| - * / | - * b,/__|, x - * \ | - * \ | - * \| - * 'c - * - * x = b projected on ac - * ax = ab dot ac / vSize(ac) - * xb = ab - ax - * error = vSize(xb) - */ + /* + * a, + * /| + * / | + * b,/__|, x + * \ | + * \ | + * \| + * 'c + * + * x = b projected on ac + * ax = ab dot ac / vSize(ac) + * xb = ab - ax + * error = vSize(xb) + */ const Point ac = c - a; const coord_t ac_size = vSize(ac); const Point ab = b - a; - if (ac_size == 0) + if (ac_size == 0) { - const coord_t ab_dist2 = vSize2(ab); + const coord_t ab_dist2 = vSize2(ab); if (ab_dist2 == 0 && b_is_beyond_ac) { *b_is_beyond_ac = 0; // a is on b is on c @@ -232,8 +233,8 @@ class LinearAlg2D const coord_t projected_x = dot(ab, ac); const coord_t ax_size = projected_x / ac_size; - if (ax_size < 0) - {// b is 'before' segment ac + if (ax_size < 0) + { // b is 'before' segment ac if (b_is_beyond_ac) { *b_is_beyond_ac = -1; @@ -241,7 +242,7 @@ class LinearAlg2D return vSize2(ab); } if (ax_size > ac_size) - {// b is 'after' segment ac + { // b is 'after' segment ac if (b_is_beyond_ac) { *b_is_beyond_ac = 1; @@ -256,13 +257,13 @@ class LinearAlg2D const Point ax = ac * ax_size / ac_size; const Point bx = ab - ax; return vSize2(bx); -// return vSize2(ab) - ax_size*ax_size; // less accurate + // return vSize2(ab) - ax_size*ax_size; // less accurate } /*! * Checks whether the minimal distance between two line segments is at most \p max_dist * The first line semgent is given by end points \p a and \p b, the second by \p c and \p d. - * + * * \param a One end point of the first line segment * \param b Another end point of the first line segment * \param c One end point of the second line segment @@ -273,16 +274,14 @@ class LinearAlg2D { const coord_t max_dist2 = max_dist * max_dist; - return getDist2FromLineSegment(a, c, b) <= max_dist2 - || getDist2FromLineSegment(a, d, b) <= max_dist2 - || getDist2FromLineSegment(c, a, d) <= max_dist2 - || getDist2FromLineSegment(c, b, d) <= max_dist2; + return getDist2FromLineSegment(a, c, b) <= max_dist2 || getDist2FromLineSegment(a, d, b) <= max_dist2 || getDist2FromLineSegment(c, a, d) <= max_dist2 + || getDist2FromLineSegment(c, b, d) <= max_dist2; } /*! * Get the minimal distance between two line segments * The first line semgent is given by end points \p a and \p b, the second by \p c and \p d. - * + * * \param a One end point of the first line segment * \param b Another end point of the first line segment * \param c One end point of the second line segment @@ -290,21 +289,17 @@ class LinearAlg2D */ static coord_t getDist2BetweenLineSegments(const Point& a, const Point& b, const Point& c, const Point& d) { - return - std::min(getDist2FromLineSegment(a, c, b), - std::min(getDist2FromLineSegment(a, d, b), - std::min(getDist2FromLineSegment(c, a, d), - getDist2FromLineSegment(c, b, d)))); + return std::min(getDist2FromLineSegment(a, c, b), std::min(getDist2FromLineSegment(a, d, b), std::min(getDist2FromLineSegment(c, a, d), getDist2FromLineSegment(c, b, d)))); } /*! * Check whether two line segments collide. - * + * * \warning Edge cases (end points of line segments fall on other line segment) register as a collision. - * + * * \note All points are assumed to be transformed by the transformation matrix of the vector from \p a_from to \p a_to. * I.e. a is a vertical line; the Y of \p a_from_transformed is the same as the Y of \p a_to_transformed. - * + * * \param a_from_transformed The transformed from location of line a * \param a_from_transformed The transformed to location of line a * \param b_from_transformed The transformed from location of line b @@ -315,16 +310,16 @@ class LinearAlg2D /*! * Compute the angle between two consecutive line segments. - * + * * The angle is computed from the left side of b when looking from a. - * + * * c * \ . * \ b * angle| * | * a - * + * * \param a start of first line segment * \param b end of first segment and start of second line segment * \param c end of second line segment @@ -334,10 +329,10 @@ class LinearAlg2D /*! * Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows. - * + * * The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b * The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b. - * + * * \param p the point to check * \param a the from point of the line * \param b the to point of the line @@ -350,9 +345,9 @@ class LinearAlg2D /*! * Get a point on the line segment (\p a - \p b)with a given distance to point \p p - * + * * In case there are two possible point that meet the criteria, choose the one closest to a. - * + * * \param p The reference point * \param a Start of the line segment * \param b End of the line segment @@ -387,12 +382,12 @@ class LinearAlg2D /*! * Check whether a corner is acute or obtuse. - * + * * This function is irrespective of the order between \p a and \p c; * the lowest angle among bot hsides of the corner is always chosen. - * + * * isAcuteCorner(a, b, c) === isAcuteCorner(c, b, a) - * + * * \param a start of first line segment * \param b end of first segment and start of second line segment * \param c end of second line segment @@ -419,13 +414,24 @@ class LinearAlg2D * Test whether a point is inside a corner. * Whether point \p query_point is left of the corner abc. * Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right. - * + * * Test whether the \p query_point is inside of a polygon w.r.t a single corner. */ static bool isInsideCorner(const Point a, const Point b, const Point c, const Point query_point); -}; + /*! + * Finds the vector for the bisection of a-b as seen from the intersection point. + * + * NOTE: The result has _not_ been normalized! This is done to prevent numerical instability later on. + * + * \param intersect The origin of the constellation. + * \param a The first point. + * \param b The second point. + * \param vec_len The lenght of the resultant vector. It's not wise to set this to 1, since we do tend to do integer math here. + */ + static Point getBisectorVector(const Point& intersect, const Point& a, const Point& b, const coord_t vec_len); +}; -}//namespace cura -#endif//UTILS_LINEAR_ALG_2D_H +} // namespace cura +#endif // UTILS_LINEAR_ALG_2D_H diff --git a/include/utils/polygon.h b/include/utils/polygon.h index 66574920e9..227097bb10 100644 --- a/include/utils/polygon.h +++ b/include/utils/polygon.h @@ -4,33 +4,34 @@ #ifndef UTILS_POLYGON_H #define UTILS_POLYGON_H -#include +#include "../settings/types/Angle.h" //For angles between vertices. +#include "../settings/types/Ratio.h" +#include "IntPoint.h" -#include -#include #include -#include - -#include // std::reverse, fill_n array -#include +#include // std::reverse, fill_n array +#include #include // fabs +#include +#include #include // int64_t.min #include - -#include - -#include "../settings/types/Angle.h" //For angles between vertices. -#include "../settings/types/Ratio.h" -#include "IntPoint.h" +#include +#include +#include #define CHECK_POLY_ACCESS #ifdef CHECK_POLY_ACCESS #define POLY_ASSERT(e) assert(e) #else -#define POLY_ASSERT(e) do {} while(0) +#define POLY_ASSERT(e) \ + do \ + { \ + } while (0) #endif -namespace cura { +namespace cura +{ template bool shorterThan(const T& shape, const coord_t check_length) @@ -75,12 +76,15 @@ class ConstPolygonRef friend class Polygon; friend class PolygonRef; friend class ConstPolygonPointer; + protected: ClipperLib::Path* path; + public: ConstPolygonRef(const ClipperLib::Path& polygon) - : path(const_cast(&polygon)) - {} + : path(const_cast(&polygon)) + { + } ConstPolygonRef() = delete; // you cannot have a reference without an object! @@ -105,7 +109,7 @@ class ConstPolygonRef */ bool empty() const; - const Point& operator[] (unsigned int index) const + const Point& operator[](unsigned int index) const { POLY_ASSERT(index < size()); return (*path)[index]; @@ -201,7 +205,7 @@ class ConstPolygonRef Point min() const { Point ret = Point(POINT_MAX, POINT_MAX); - for(Point p : *path) + for (Point p : *path) { ret.X = std::min(ret.X, p.X); ret.Y = std::min(ret.Y, p.Y); @@ -212,7 +216,7 @@ class ConstPolygonRef Point max() const { Point ret = Point(POINT_MIN, POINT_MIN); - for(Point p : *path) + for (Point p : *path) { ret.X = std::max(ret.X, p.X); ret.Y = std::max(ret.Y, p.Y); @@ -228,8 +232,8 @@ class ConstPolygonRef Point centerOfMass() const { double x = 0, y = 0; - Point p0 = (*path)[path->size()-1]; - for(unsigned int n=0; nsize(); n++) + Point p0 = (*path)[path->size() - 1]; + for (unsigned int n = 0; n < path->size(); n++) { Point p1 = (*path)[n]; double second_factor = (p0.X * p1.Y) - (p1.X * p0.Y); @@ -251,7 +255,7 @@ class ConstPolygonRef { Point ret = p; float bestDist = FLT_MAX; - for(unsigned int n=0; nsize(); n++) + for (unsigned int n = 0; n < path->size(); n++) { float dist = vSize2f(p - (*path)[n]); if (dist < bestDist) @@ -371,7 +375,18 @@ class ConstPolygonRef * \param shortcut_length The desired length ofthe shortcutting line * \param cos_angle The cosine on the angle in L 012 */ - static void smooth_corner_simple(const Point p0, const Point p1, const Point p2, const ListPolyIt p0_it, const ListPolyIt p1_it, const ListPolyIt p2_it, const Point v10, const Point v12, const Point v02, const int64_t shortcut_length, float cos_angle); + static void smooth_corner_simple( + const Point p0, + const Point p1, + const Point p2, + const ListPolyIt p0_it, + const ListPolyIt p1_it, + const ListPolyIt p2_it, + const Point v10, + const Point v12, + const Point v02, + const int64_t shortcut_length, + float cos_angle); /*! * Smooth out a complex corner where the shortcut bypasses more than two line segments @@ -405,7 +420,15 @@ class ConstPolygonRef * \param[in,out] forward_is_too_far Whether trying another step forward is blocked by the shortcut length condition. Updated for the next iteration. * \param[in,out] backward_is_too_far Whether trying another step backward is blocked by the shortcut length condition. Updated for the next iteration. */ - static void smooth_outward_step(const Point p1, const int64_t shortcut_length2, ListPolyIt& p0_it, ListPolyIt& p2_it, bool& forward_is_blocked, bool& backward_is_blocked, bool& forward_is_too_far, bool& backward_is_too_far); + static void smooth_outward_step( + const Point p1, + const int64_t shortcut_length2, + ListPolyIt& p0_it, + ListPolyIt& p2_it, + bool& forward_is_blocked, + bool& backward_is_blocked, + bool& forward_is_too_far, + bool& backward_is_too_far); }; @@ -416,14 +439,17 @@ class PolygonRef : public ConstPolygonRef friend class PolygonPointer; friend class Polygons; friend class PolygonsPart; + public: PolygonRef(ClipperLib::Path& polygon) - : ConstPolygonRef(polygon) - {} + : ConstPolygonRef(polygon) + { + } PolygonRef(const PolygonRef& other) - : ConstPolygonRef(*other.path) - {} + : ConstPolygonRef(*other.path) + { + } PolygonRef() = delete; // you cannot have a reference without an object! @@ -440,7 +466,7 @@ class PolygonRef : public ConstPolygonRef path->reserve(min_size); } - template + template ClipperLib::Path::iterator insert(ClipperLib::Path::const_iterator pos, iterator first, iterator last) { return path->insert(pos, first, last); @@ -449,7 +475,7 @@ class PolygonRef : public ConstPolygonRef PolygonRef& operator=(const ConstPolygonRef& other) = delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator PolygonRef& operator=(ConstPolygonRef& other) = delete; // polygon assignment is expensive and probably not what you want when you use the assignment operator -// { path = other.path; return *this; } + // { path = other.path; return *this; } PolygonRef& operator=(PolygonRef&& other) { @@ -457,13 +483,13 @@ class PolygonRef : public ConstPolygonRef return *this; } - Point& operator[] (unsigned int index) + Point& operator[](unsigned int index) { POLY_ASSERT(index < size()); return (*path)[index]; } - const Point& operator[] (unsigned int index) const + const Point& operator[](unsigned int index) const { POLY_ASSERT(index < size()); return (*path)[index]; @@ -504,7 +530,7 @@ class PolygonRef : public ConstPolygonRef return *path; } - template + template void emplace_back(Args&&... args) { path->emplace_back(args...); @@ -567,7 +593,8 @@ class PolygonRef : public ConstPolygonRef * * \param smallest_line_segment_squared maximal squared length of removed line segments - * \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is removed + * \param allowed_error_distance_squared The square of the distance of the middle point to the line segment of the consecutive and previous point for which the middle point is + removed */ void simplify(const coord_t smallest_line_segment_squared = MM2INT(0.01) * MM2INT(0.01), const coord_t allowed_error_distance_squared = 25); @@ -575,10 +602,11 @@ class PolygonRef : public ConstPolygonRef * See simplify(.) */ void simplifyPolyline(const coord_t smallest_line_segment_squared = 100, const coord_t allowed_error_distance_squared = 25); + protected: /*! * Private implementation for both simplify and simplifyPolygons. - * + * * Made private to avoid accidental use of the wrong function. */ void _simplify(const coord_t smallest_line_segment_squared = 100, const coord_t allowed_error_distance_squared = 25, bool processing_polylines = false); @@ -600,16 +628,20 @@ class ConstPolygonPointer { protected: const ClipperLib::Path* path; + public: ConstPolygonPointer() - : path(nullptr) - {} + : path(nullptr) + { + } ConstPolygonPointer(const ConstPolygonRef* ref) - : path(ref->path) - {} + : path(ref->path) + { + } ConstPolygonPointer(const ConstPolygonRef& ref) - : path(ref.path) - {} + : path(ref.path) + { + } ConstPolygonRef operator*() const { @@ -637,15 +669,18 @@ class PolygonPointer : public ConstPolygonPointer { public: PolygonPointer() - : ConstPolygonPointer(nullptr) - {} + : ConstPolygonPointer(nullptr) + { + } PolygonPointer(PolygonRef* ref) - : ConstPolygonPointer(ref) - {} + : ConstPolygonPointer(ref) + { + } PolygonPointer(PolygonRef& ref) - : ConstPolygonPointer(ref) - {} + : ConstPolygonPointer(ref) + { + } PolygonRef operator*() { @@ -707,9 +742,10 @@ struct hash return std::hash()(&*ref); } }; -}//namespace std +} // namespace std -namespace cura { +namespace cura +{ class Polygon : public PolygonRef { @@ -717,25 +753,25 @@ class Polygon : public PolygonRef ClipperLib::Path poly; Polygon() - : PolygonRef(poly) + : PolygonRef(poly) { } Polygon(const ConstPolygonRef& other) - : PolygonRef(poly) - , poly(*other.path) + : PolygonRef(poly) + , poly(*other.path) { } Polygon(const Polygon& other) - : PolygonRef(poly) - , poly(*other.path) + : PolygonRef(poly) + , poly(*other.path) { } Polygon(Polygon&& moved) - : PolygonRef(poly) - , poly(std::move(moved.poly)) + : PolygonRef(poly) + , poly(std::move(moved.poly)) { } @@ -744,11 +780,11 @@ class Polygon : public PolygonRef } Polygon& operator=(const ConstPolygonRef& other) = delete; // copying a single polygon is generally not what you want -// { -// path = other.path; -// poly = *other.path; -// return *this; -// } + // { + // path = other.path; + // poly = *other.path; + // return *this; + // } Polygon& operator=(Polygon&& other) //!< move assignment { @@ -765,6 +801,7 @@ class Polygons friend class PolygonRef; friend class ConstPolygonRef; friend class PolygonUtils; + public: ClipperLib::Paths paths; @@ -787,12 +824,12 @@ class Polygons unsigned int pointCount() const; //!< Return the amount of points in all polygons - PolygonRef operator[] (unsigned int index) + PolygonRef operator[](unsigned int index) { POLY_ASSERT(index < size() && index <= static_cast(std::numeric_limits::max())); return paths[index]; } - ConstPolygonRef operator[] (unsigned int index) const + ConstPolygonRef operator[](unsigned int index) const { POLY_ASSERT(index < size() && index <= static_cast(std::numeric_limits::max())); return paths[index]; @@ -886,7 +923,7 @@ class Polygons */ void addLine(const Point from, const Point to) { - paths.emplace_back(ClipperLib::Path{from, to}); + paths.emplace_back(ClipperLib::Path{ from, to }); } void emplace_back(const Polygon& poly) @@ -903,7 +940,7 @@ class Polygons { paths.emplace_back(*poly.path); } - + template void emplace_back(Args... args) { @@ -932,11 +969,23 @@ class Polygons return ConstPolygonRef(paths.back()); } - Polygons() {} + Polygons() + { + } - Polygons(const Polygons& other) { paths = other.paths; } - Polygons(Polygons&& other) { paths = std::move(other.paths); } - Polygons& operator=(const Polygons& other) { paths = other.paths; return *this; } + Polygons(const Polygons& other) + { + paths = other.paths; + } + Polygons(Polygons&& other) + { + paths = std::move(other.paths); + } + Polygons& operator=(const Polygons& other) + { + paths = other.paths; + return *this; + } Polygons& operator=(Polygons&& other) { if (this != &other) @@ -992,9 +1041,9 @@ class Polygons /*! * Intersect polylines with this area Polygons object. - * + * * \note Due to a clipper bug with polylines with nearly collinear segments, the polylines are cut up into separate polylines, and restitched back together at the end. - * + * * \param polylines The (non-closed!) polylines to limit to the area of this Polygons object * \param restitch Whether to stitch the resulting segments into longer polylines, or leave every segment as a single segment * \param max_stitch_distance The maximum distance for two polylines to be stitched together with a segment @@ -1031,7 +1080,7 @@ class Polygons return ret; } - Polygons execute (ClipperLib::PolyFillType pft = ClipperLib::pftEvenOdd) const + Polygons execute(ClipperLib::PolyFillType pft = ClipperLib::pftEvenOdd) const { Polygons ret; ClipperLib::Clipper clipper(clipper_init); @@ -1042,11 +1091,23 @@ class Polygons Polygons offset(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miter_limit = 1.2) const; - Polygons offsetPolyLine(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter) const + Polygons offsetPolyLine(int distance, ClipperLib::JoinType joinType = ClipperLib::jtMiter, bool inputPolyIsClosed = false) const { Polygons ret; double miterLimit = 1.2; - ClipperLib::EndType end_type = (joinType == ClipperLib::jtMiter)? ClipperLib::etOpenSquare : ClipperLib::etOpenRound; + ClipperLib::EndType end_type; + if (inputPolyIsClosed) + { + end_type = ClipperLib::etClosedLine; + } + else if (joinType == ClipperLib::jtMiter) + { + end_type = ClipperLib::etOpenSquare; + } + else + { + end_type = ClipperLib::etOpenRound; + } ClipperLib::ClipperOffset clipper(miterLimit, 10.0); clipper.AddPaths(paths, joinType, end_type); clipper.MiterLimit = miterLimit; @@ -1174,8 +1235,8 @@ class Polygons } } } -public: +public: void scale(const Ratio& ratio) { if (ratio == 1.) @@ -1232,16 +1293,16 @@ class Polygons /*! * Sort the polygons into bins where each bin has polygons which are contained within one of the polygons in the previous bin. - * + * * \warning When polygons are crossing each other the result is undefined. */ std::vector sortByNesting() const; /*! * Utility method for creating the tube (or 'donut') of a shape. - * \param inner_offset Offset relative to the original shape-outline towards the inside of the shape. Sort-of like a negative normal offset, except it's the offset part that's kept, not the shape. - * \param outer_offset Offset relative to the original shape-outline towards the outside of the shape. Comparable to normal offset. - * \return The resulting polygons. + * \param inner_offset Offset relative to the original shape-outline towards the inside of the shape. Sort-of like a negative normal offset, except it's the offset part that's + * kept, not the shape. \param outer_offset Offset relative to the original shape-outline towards the outside of the shape. Comparable to normal offset. \return The resulting + * polygons. */ Polygons tubeShape(const coord_t inner_offset, const coord_t outer_offset) const; @@ -1264,11 +1325,11 @@ class Polygons * \warning Note that this function reorders the polygons! */ PartsView splitIntoPartsView(bool unionAll = false); + private: void splitIntoPartsView_processPolyTreeNode(PartsView& partsView, Polygons& reordered, ClipperLib::PolyNode* node) const; + public: - - /*! * Removes polygons with area smaller than \p min_area_size (note that min_area_size is in mm^2, not in micron^2). * Unless \p remove_holes is true, holes are not removed even if their area is below \p min_area_size. @@ -1337,46 +1398,46 @@ class Polygons ConstPolygonRef poly_keep = (*this)[poly_keep_idx]; bool should_be_removed = false; if (poly_keep.size() > 0) -// for (int hole_poly_idx = 0; hole_poly_idx < to_be_removed.size(); hole_poly_idx++) - for (ConstPolygonRef poly_rem : to_be_removed) - { -// PolygonRef poly_rem = to_be_removed[hole_poly_idx]; - if (poly_rem.size() != poly_keep.size() || poly_rem.size() == 0) continue; - - // find closest point, supposing this point aligns the two shapes in the best way - int closest_point_idx = 0; - int smallestDist2 = -1; - for (unsigned int point_rem_idx = 0; point_rem_idx < poly_rem.size(); point_rem_idx++) + // for (int hole_poly_idx = 0; hole_poly_idx < to_be_removed.size(); hole_poly_idx++) + for (ConstPolygonRef poly_rem : to_be_removed) { - int dist2 = vSize2(poly_rem[point_rem_idx] - poly_keep[0]); - if (dist2 < smallestDist2 || smallestDist2 < 0) + // PolygonRef poly_rem = to_be_removed[hole_poly_idx]; + if (poly_rem.size() != poly_keep.size() || poly_rem.size() == 0) + continue; + + // find closest point, supposing this point aligns the two shapes in the best way + int closest_point_idx = 0; + int smallestDist2 = -1; + for (unsigned int point_rem_idx = 0; point_rem_idx < poly_rem.size(); point_rem_idx++) { - smallestDist2 = dist2; - closest_point_idx = point_rem_idx; + int dist2 = vSize2(poly_rem[point_rem_idx] - poly_keep[0]); + if (dist2 < smallestDist2 || smallestDist2 < 0) + { + smallestDist2 = dist2; + closest_point_idx = point_rem_idx; + } } - } - bool poly_rem_is_poly_keep = true; - // compare the two polygons on all points - if (smallestDist2 > same_distance * same_distance) - continue; - for (unsigned int point_idx = 0; point_idx < poly_rem.size(); point_idx++) - { - int dist2 = vSize2(poly_rem[(closest_point_idx + point_idx) % poly_rem.size()] - poly_keep[point_idx]); - if (dist2 > same_distance * same_distance) + bool poly_rem_is_poly_keep = true; + // compare the two polygons on all points + if (smallestDist2 > same_distance * same_distance) + continue; + for (unsigned int point_idx = 0; point_idx < poly_rem.size(); point_idx++) + { + int dist2 = vSize2(poly_rem[(closest_point_idx + point_idx) % poly_rem.size()] - poly_keep[point_idx]); + if (dist2 > same_distance * same_distance) + { + poly_rem_is_poly_keep = false; + break; + } + } + if (poly_rem_is_poly_keep) { - poly_rem_is_poly_keep = false; + should_be_removed = true; break; } } - if (poly_rem_is_poly_keep) - { - should_be_removed = true; - break; - } - } - if (!should_be_removed) + if (! should_be_removed) result.add(poly_keep); - } return result; } @@ -1416,9 +1477,9 @@ class Polygons Point min() const { Point ret = Point(POINT_MAX, POINT_MAX); - for(const ClipperLib::Path& polygon : paths) + for (const ClipperLib::Path& polygon : paths) { - for(Point p : polygon) + for (Point p : polygon) { ret.X = std::min(ret.X, p.X); ret.Y = std::min(ret.Y, p.Y); @@ -1430,9 +1491,9 @@ class Polygons Point max() const { Point ret = Point(POINT_MIN, POINT_MIN); - for(const ClipperLib::Path& polygon : paths) + for (const ClipperLib::Path& polygon : paths) { - for(Point p : polygon) + for (Point p : polygon) { ret.X = std::max(ret.X, p.X); ret.Y = std::max(ret.Y, p.Y); @@ -1443,9 +1504,9 @@ class Polygons void applyMatrix(const PointMatrix& matrix) { - for(unsigned int i=0; i> which is similar to a vector of PolygonParts, except the base of the container is indices to polygons into the original Polygons, instead of the polygons themselves + * Extension of vector> which is similar to a vector of PolygonParts, except the base of the container is indices to polygons into the original Polygons, + * instead of the polygons themselves */ class PartsView : public std::vector> { public: Polygons& polygons; - PartsView(Polygons& polygons) : polygons(polygons) { } + PartsView(Polygons& polygons) + : polygons(polygons) + { + } /*! * Get the index of the PolygonsPart of which the polygon with index \p poly_idx is part. * @@ -1525,6 +1590,6 @@ class PartsView : public std::vector> PolygonsPart assemblePart(unsigned int part_idx) const; }; -}//namespace cura +} // namespace cura -#endif//UTILS_POLYGON_H +#endif // UTILS_POLYGON_H diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index 13786e71c7..c4ea2c2db6 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -1,20 +1,20 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef UTILS_POLYGON_UTILS_H #define UTILS_POLYGON_UTILS_H #include // function #include -#include #include // unique_ptr +#include -#include "polygon.h" -#include "SparsePointGridInclusive.h" -#include "SparseLineGrid.h" #include "PolygonsPointIndex.h" +#include "SparseLineGrid.h" +#include "SparsePointGridInclusive.h" +#include "polygon.h" -namespace cura +namespace cura { /*! @@ -26,10 +26,23 @@ struct ClosestPolygonPoint ConstPolygonPointer poly; //!< Polygon in which the result was found (or nullptr if no result was found) unsigned int poly_idx; //!< The index of the polygon in some Polygons where ClosestPolygonPoint::poly can be found unsigned int point_idx; //!< Index to the first point in the polygon of the line segment on which the result was found - ClosestPolygonPoint(Point p, int pos, ConstPolygonRef poly) : location(p), poly(poly), poly_idx(NO_INDEX), point_idx(pos) {}; - ClosestPolygonPoint(Point p, int pos, ConstPolygonRef poly, int poly_idx) : location(p), poly(poly), poly_idx(poly_idx), point_idx(pos) {}; - ClosestPolygonPoint(ConstPolygonRef poly) : poly(poly), poly_idx(NO_INDEX), point_idx(NO_INDEX) {}; - ClosestPolygonPoint() : poly_idx(NO_INDEX), point_idx(NO_INDEX) {}; + ClosestPolygonPoint(Point p, int pos, ConstPolygonRef poly) + : location(p) + , poly(poly) + , poly_idx(NO_INDEX) + , point_idx(pos){}; + ClosestPolygonPoint(Point p, int pos, ConstPolygonRef poly, int poly_idx) + : location(p) + , poly(poly) + , poly_idx(poly_idx) + , point_idx(pos){}; + ClosestPolygonPoint(ConstPolygonRef poly) + : poly(poly) + , poly_idx(NO_INDEX) + , point_idx(NO_INDEX){}; + ClosestPolygonPoint() + : poly_idx(NO_INDEX) + , point_idx(NO_INDEX){}; Point p() const { // conformity with other classes return location; @@ -50,7 +63,7 @@ struct ClosestPolygonPoint namespace std { -template <> +template<> struct hash { size_t operator()(const cura::ClosestPolygonPoint& cpp) const @@ -58,12 +71,12 @@ struct hash return std::hash()(cpp.p()); } }; -}//namespace std +} // namespace std namespace std { -template +template struct hash> { size_t operator()(const std::pair& pair) const @@ -71,7 +84,7 @@ struct hash> return 31 * std::hash()(pair.first) + 59 * std::hash()(pair.second); } }; -}//namespace std +} // namespace std namespace cura @@ -88,18 +101,18 @@ struct GivenDistPoint typedef SparseLineGrid LocToLineGrid; -class PolygonUtils +class PolygonUtils { public: static const std::function no_penalty_function; //!< Function always returning zero /*! * compute the length of a segment of a polygon - * + * * if \p end == \p start then the full polygon is taken - * + * * \warning assumes that start and end lie on the same polygon! - * + * * \param start The start vertex of the segment * \param end the end vertex of the segment * \return the total length of all the line segments in between the two vertices. @@ -108,12 +121,12 @@ class PolygonUtils /*! * Generate evenly spread out dots along a segment of a polygon - * + * * Start at a distance from \p start and end at a distance from \p end, * unless \p end == \p start; then that point is in the result - * + * * \warning Assumes that start and end lie on the same polygon! - * + * * \param start The start vertex of the segment * \param end the end vertex of the segment * \param n_dots number of dots to spread out @@ -131,48 +144,54 @@ class PolygonUtils /*! * Whether a polygon intersects with a line-segment. If true, the closest collision point to 'b' is stored in the result. */ - static bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const Polygons& current_outlines, const LocToLineGrid& outline_locator, Point& result, const coord_t within_max_dist); + static bool lineSegmentPolygonsIntersection( + const Point& a, + const Point& b, + const Polygons& current_outlines, + const LocToLineGrid& outline_locator, + Point& result, + const coord_t within_max_dist); /*! * Get the normal of a boundary point, pointing outward. * Only the direction is set. * Nothing is said about the length of the vector returned. - * + * * \param poly The polygon. * \param point_idx The index of the point in the polygon. */ static Point getVertexInwardNormal(ConstPolygonRef poly, unsigned int point_idx); /*! - * Get a point from the \p poly with a given \p offset. - * - * \param poly The polygon. - * \param point_idx The index of the point in the polygon. - * \param offset The distance the point has to be moved outward from the polygon. - * \return A point at the given distance inward from the point on the boundary polygon. - */ + * Get a point from the \p poly with a given \p offset. + * + * \param poly The polygon. + * \param point_idx The index of the point in the polygon. + * \param offset The distance the point has to be moved outward from the polygon. + * \return A point at the given distance inward from the point on the boundary polygon. + */ static Point getBoundaryPointWithOffset(ConstPolygonRef poly, unsigned int point_idx, int64_t offset); /*! * Move a point away from the boundary by looking at the boundary normal of the nearest vert. - * + * * \param point_on_boundary The object holding the point on the boundary along with the information of which line segment the point is on. * \param offset The distance the point has to be moved inward from the polygon. */ static Point moveInsideDiagonally(ClosestPolygonPoint point_on_boundary, int64_t inset); /*! - * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. - * Given a \p distance more than zero, the point will end up inside, and conversely outside. - * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. - * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * - * \param polygons The polygons onto which to move the point - * \param from[in,out] The point to move. - * \param distance The distance by which to move the point. - * \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon. - * \return The index to the polygon onto which we have moved the point. - */ + * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. + * Given a \p distance more than zero, the point will end up inside, and conversely outside. + * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. + * When the point is in/outside by less than \p distance, \p from is moved to the correct place. + * + * \param polygons The polygons onto which to move the point + * \param from[in,out] The point to move. + * \param distance The distance by which to move the point. + * \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon. + * \return The index to the polygon onto which we have moved the point. + */ static unsigned int moveInside(const Polygons& polygons, Point& from, int distance = 0, int64_t max_dist2 = std::numeric_limits::max()); /** @@ -198,11 +217,11 @@ class PolygonUtils * Given a \p distance more than zero, the point will end up inside, and conversely outside. * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * + * * \warning If \p loc_to_line_grid is used, it's best to have all and only \p polygons in there. * If \p from is not closest to \p polygons this function may * return a ClosestPolygonPoint on a polygon in \p loc_to_line_grid which is not in \p polygons. - * + * * \param polygons The polygons onto which to move the point * \param from[in,out] The point to move. * \param distance The distance by which to move the point. @@ -212,18 +231,25 @@ class PolygonUtils * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. * \return The point on the polygon closest to \p from */ - static ClosestPolygonPoint moveInside2(const Polygons& polygons, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits::max(), const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function& penalty_function = no_penalty_function); + static ClosestPolygonPoint moveInside2( + const Polygons& polygons, + Point& from, + const int distance = 0, + const int64_t max_dist2 = std::numeric_limits::max(), + const Polygons* loc_to_line_polygons = nullptr, + const LocToLineGrid* loc_to_line_grid = nullptr, + const std::function& penalty_function = no_penalty_function); /*! * Moves the point \p from onto the nearest segment of \p polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * + * * \warning When a \p loc_to_line is given this function only considers nearby elements. * Even when the penalty function favours elements farther away. * Also using the \p loc_to_line_grid automatically considers \p all_polygons - * + * * \param loc_to_line_polygons All polygons which are present in the \p loc_to_line_grid of which \p polygon is an element * \param polygon The polygon onto which to move the point * \param from[in,out] The point to move. @@ -233,16 +259,23 @@ class PolygonUtils * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. * \return The point on the polygon closest to \p from */ - static ClosestPolygonPoint moveInside2(const Polygons& loc_to_line_polygons, ConstPolygonRef polygon, Point& from, const int distance = 0, const int64_t max_dist2 = std::numeric_limits::max(), const LocToLineGrid* loc_to_line_grid = nullptr, const std::function& penalty_function = no_penalty_function); + static ClosestPolygonPoint moveInside2( + const Polygons& loc_to_line_polygons, + ConstPolygonRef polygon, + Point& from, + const int distance = 0, + const int64_t max_dist2 = std::numeric_limits::max(), + const LocToLineGrid* loc_to_line_grid = nullptr, + const std::function& penalty_function = no_penalty_function); /*! * The opposite of moveInside. - * + * * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within \p distance. * Given a \p distance more than zero, the point will end up outside, and conversely inside. * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * + * * \param polygons The polygons onto which to move the point * \param from[in,out] The point to move. * \param distance The distance by which to move the point. @@ -251,23 +284,23 @@ class PolygonUtils * \return The index to the polygon onto which we have moved the point. */ static unsigned int moveOutside(const Polygons& polygons, Point& from, int distance = 0, int64_t max_dist2 = std::numeric_limits::max()); - + /*! * Compute a point at a distance from a point on the boundary in orthogonal direction to the boundary. * Given a \p distance more than zero, the point will end up inside, and conversely outside. - * + * * \param cpp The object holding the point on the boundary along with the information of which line segment the point is on. * \param distance The distance by which to move the point. * \return A point at a \p distance from the point in \p cpp orthogonal to the boundary there. */ static Point moveInside(const ClosestPolygonPoint& cpp, const int distance); - + /*! * The opposite of moveInside. - * + * * Compute a point at a distance from a point on the boundary in orthogonal direction to the boundary. * Given a \p distance more than zero, the point will end up outside, and conversely inside. - * + * * \param cpp The object holding the point on the boundary along with the information of which line segment the point is on. * \param distance The distance by which to move the point. * \return A point at a \p distance from the point in \p cpp orthogonal to the boundary there. @@ -279,15 +312,15 @@ class PolygonUtils * Given a \p distance more than zero, the point will end up inside, and conversely outside. * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * + * * \warning May give false positives. - * Some checking is done to make sure we end up inside the polygon, + * Some checking is done to make sure we end up inside the polygon, * but it might still be the case that we end up outside: * when the closest point on the boundary is very close to another polygon - * + * * \warning When using a \p loc_to_line_grid which contains more polygons than just \p polygons, * the results is only correct if \p from is already closest to \p polygons, rather than other polygons in the \p loc_to_line_grid. - * + * * \param polygons The polygons onto which to move the point * \param from[in,out] The point to move. * \param preferred_dist_inside The preferred distance from the boundary to the point @@ -297,22 +330,29 @@ class PolygonUtils * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. * \return The point on the polygon closest to \p from */ - static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, int preferred_dist_inside, int64_t max_dist2 = std::numeric_limits::max(), const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function& penalty_function = no_penalty_function); + static ClosestPolygonPoint ensureInsideOrOutside( + const Polygons& polygons, + Point& from, + int preferred_dist_inside, + int64_t max_dist2 = std::numeric_limits::max(), + const Polygons* loc_to_line_polygons = nullptr, + const LocToLineGrid* loc_to_line_grid = nullptr, + const std::function& penalty_function = no_penalty_function); /*! * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within \p distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. * When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned. * When the point is in/outside by less than \p distance, \p from is moved to the correct place. - * + * * \warning May give false positives. - * Some checking is done to make sure we end up inside the polygon, + * Some checking is done to make sure we end up inside the polygon, * but it might still be the case that we end up outside: * when the closest point on the boundary is very close to another polygon - * + * * \warning When using a \p loc_to_line_grid which contains more polygons than just \p polygons, * the results is only correct if \p from is already closest to \p polygons, rather than other polygons in the \p loc_to_line_grid. - * + * * \param polygons The polygons onto which to move the point * \param from[in,out] The point to move. * \param closest_polygon_point The point on \p polygons closest to \p from @@ -322,49 +362,56 @@ class PolygonUtils * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. * \return The point on the polygon closest to \p from */ - static ClosestPolygonPoint ensureInsideOrOutside(const Polygons& polygons, Point& from, const ClosestPolygonPoint& closest_polygon_point, int preferred_dist_inside, const Polygons* loc_to_line_polygons = nullptr, const LocToLineGrid* loc_to_line_grid = nullptr, const std::function& penalty_function = no_penalty_function); + static ClosestPolygonPoint ensureInsideOrOutside( + const Polygons& polygons, + Point& from, + const ClosestPolygonPoint& closest_polygon_point, + int preferred_dist_inside, + const Polygons* loc_to_line_polygons = nullptr, + const LocToLineGrid* loc_to_line_grid = nullptr, + const std::function& penalty_function = no_penalty_function); /*! - * - * \warning Assumes \p poly1_result and \p poly2_result have their pos and poly fields initialized! - */ + * + * \warning Assumes \p poly1_result and \p poly2_result have their pos and poly fields initialized! + */ static void walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_result, ClosestPolygonPoint& poly2_result); /*! - * Find the nearest closest point on a polygon from a given index. - * - * \param from The point from which to get the smallest distance. - * \param polygon The polygon on which to find the point with the smallest distance. - * \param start_idx The index of the point in the polygon from which to start looking. - * \return The nearest point from \p start_idx going along the \p polygon (in both directions) with a locally minimal distance to \p from. - */ + * Find the nearest closest point on a polygon from a given index. + * + * \param from The point from which to get the smallest distance. + * \param polygon The polygon on which to find the point with the smallest distance. + * \param start_idx The index of the point in the polygon from which to start looking. + * \return The nearest point from \p start_idx going along the \p polygon (in both directions) with a locally minimal distance to \p from. + */ static ClosestPolygonPoint findNearestClosest(Point from, ConstPolygonRef polygon, int start_idx); /*! - * Find the nearest closest point on a polygon from a given index walking in one direction along the polygon. - * - * \param from The point from which to get the smallest distance. - * \param polygon The polygon on which to find the point with the smallest distance. - * \param start_idx The index of the point in the polygon from which to start looking. - * \param direction The direction to walk: 1 for walking along the \p polygon, -1 for walking in opposite direction - * \return The nearest point from \p start_idx going along the \p polygon with a locally minimal distance to \p from. - */ + * Find the nearest closest point on a polygon from a given index walking in one direction along the polygon. + * + * \param from The point from which to get the smallest distance. + * \param polygon The polygon on which to find the point with the smallest distance. + * \param start_idx The index of the point in the polygon from which to start looking. + * \param direction The direction to walk: 1 for walking along the \p polygon, -1 for walking in opposite direction + * \return The nearest point from \p start_idx going along the \p polygon with a locally minimal distance to \p from. + */ static ClosestPolygonPoint findNearestClosest(const Point from, ConstPolygonRef polygon, int start_idx, int direction); /*! * Find the point closest to \p from in all polygons in \p polygons. - * + * * \note The penalty term is applied to the *squared* distance score - * + * * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. */ static ClosestPolygonPoint findClosest(Point from, const Polygons& polygons, const std::function& penalty_function = no_penalty_function); - + /*! * Find the point closest to \p from in the polygon \p polygon. - * + * * \note The penalty term is applied to the *squared* distance score - * + * * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. */ static ClosestPolygonPoint findClosest(Point from, ConstPolygonRef polygon, const std::function& penalty_function = no_penalty_function); @@ -387,9 +434,9 @@ class PolygonUtils /*! * Create a SparsePointGridInclusive mapping from locations to line segments occurring in the \p polygons - * + * * \warning The caller of this function is responsible for deleting the returned object - * + * * \param polygons The polygons for which to create the mapping * \param square_size The cell size used to bundle line segments (also used to chop up lines so that multiple cells contain the same long line) * \return A bucket grid mapping spatial locations to poly-point indices into \p polygons @@ -398,57 +445,63 @@ class PolygonUtils /*! * Find the line segment closest to a given point \p from within a cell-block of a size defined in the SparsePointGridInclusive \p loc_to_line - * + * * \note The penalty term is applied to the *squared* distance score. * Note also that almost only nearby points are considered even when the penalty function would favour points farther away. - * + * * \param from The location to find a polygon edge close to * \param polygons The polygons for which the \p loc_to_line has been built up - * \param loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons + * \param loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. * \return The nearest point on the polygon if the polygon was within a distance equal to the cell_size of the SparsePointGridInclusive */ - static std::optional findClose(Point from, const Polygons& polygons, const LocToLineGrid& loc_to_line, const std::function& penalty_function = no_penalty_function); + static std::optional + findClose(Point from, const Polygons& polygons, const LocToLineGrid& loc_to_line, const std::function& penalty_function = no_penalty_function); /*! * Find the line segment closest to any point on \p from within cell-blocks of a size defined in the SparsePointGridInclusive \p destination_loc_to_line - * + * * \note The penalty term is applied to the *squared* distance score. * Note also that almost only nearby points are considered even when the penalty function would favour points farther away. - * + * * \param from The polygon for which to find a polygon edge close to * \param destination The polygons for which the \p destination_loc_to_line has been built up - * \param destination_loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segments of the \p destination + * \param destination_loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segments of the \p destination * \param penalty_function A function returning a penalty term on the squared distance score of a candidate point. - * \return A collection of near crossing from the \p from polygon to the \p destination polygon. Each element in the sollection is a pair with as first a cpp in the \p from polygon and as second a cpp in the \p destination polygon. + * \return A collection of near crossing from the \p from polygon to the \p destination polygon. Each element in the sollection is a pair with as first a cpp in the \p from + * polygon and as second a cpp in the \p destination polygon. */ - static std::vector> findClose(ConstPolygonRef from, const Polygons& destination, const LocToLineGrid& destination_loc_to_line, const std::function& penalty_function = no_penalty_function); + static std::vector> findClose( + ConstPolygonRef from, + const Polygons& destination, + const LocToLineGrid& destination_loc_to_line, + const std::function& penalty_function = no_penalty_function); /*! * Checks whether a given line segment collides with polygons as given in a loc_to_line grid. - * + * * If the line segment doesn't intersect with any edge of the polygon, but * merely touches it, a collision is also reported. For instance, a * collision is reported when the an endpoint of the line is exactly on the * polygon, and when the line coincides with an edge. - * + * * \param[in] from The start point * \param[in] to The end point - * \param[in] loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons + * \param[in] loc_to_line A SparsePointGridInclusive mapping locations to starting vertices of line segmetns of the \p polygons * \param[out] collision_result (optional) The polygons segment intersecting with the line segment * \return whether the line segment collides with the boundary of the polygons */ static bool polygonCollidesWithLineSegment(const Point from, const Point to, const LocToLineGrid& loc_to_line, PolygonsPointIndex* collision_result = nullptr); /*! - * Find the next point (going along the direction of the polygon) with a distance \p dist from the point \p from within the \p poly. - * Returns whether another point could be found within the \p poly which can be found before encountering the point at index \p start_idx. - * The point \p from and the polygon \p poly are assumed to lie on the same plane. - * - * \param from The point from whitch to find a point on the polygon satisfying the conditions - * \param start_idx the index of the prev poly point on the poly. - * \param poly_start_idx The index of the point in the polygon which is to be handled as the start of the polygon. No point further than this point will be the result. - */ + * Find the next point (going along the direction of the polygon) with a distance \p dist from the point \p from within the \p poly. + * Returns whether another point could be found within the \p poly which can be found before encountering the point at index \p start_idx. + * The point \p from and the polygon \p poly are assumed to lie on the same plane. + * + * \param from The point from whitch to find a point on the polygon satisfying the conditions + * \param start_idx the index of the prev poly point on the poly. + * \param poly_start_idx The index of the point in the polygon which is to be handled as the start of the polygon. No point further than this point will be the result. + */ static bool getNextPointWithDistance(Point from, int64_t dist, ConstPolygonRef poly, int start_idx, int poly_start_idx, GivenDistPoint& result); /*! @@ -458,10 +511,10 @@ class PolygonUtils /*! * Get the point on a polygon which intersects a line parallel to a line going through the starting point and through another point. - * + * * Note that the looking direction \p forward doesn't neccesarily determine on which side of the line we cross a parallel line. * Depending on the geometry of the polygon the next intersection may be left or right of the input line. - * + * * \param start The starting point of the search and the starting point of the line * \param line_to The end point of the line * \param dist The distance from the parallel line to the line defined by the previous two parameters @@ -474,12 +527,12 @@ class PolygonUtils * Checks whether a given line segment collides with a given polygon(s). * The transformed_startPoint and transformed_endPoint should have the same * Y coordinate. - * + * * If the line segment doesn't intersect with any edge of the polygon, but * merely touches it, a collision is also reported. For instance, a * collision is reported when the an endpoint of the line is exactly on the * polygon, and when the line coincides with an edge. - * + * * \param poly The polygon * \param transformed_startPoint The start point transformed such that it is * on the same horizontal line as the end point @@ -494,12 +547,12 @@ class PolygonUtils /*! * Checks whether a given line segment collides with a given polygon(s). - * + * * If the line segment doesn't intersect with any edge of the polygon, but * merely touches it, a collision is also reported. For instance, a * collision is reported when the an endpoint of the line is exactly on the * polygon, and when the line coincides with an edge. - * + * * \param poly The polygon * \param startPoint The start point * \param endPoint The end point @@ -512,12 +565,12 @@ class PolygonUtils * Checks whether a given line segment collides with a given polygon(s). * The transformed_startPoint and transformed_endPoint should have the same * Y coordinate. - * + * * If the line segment doesn't intersect with any edge of the polygon, but * merely touches it, a collision is also reported. For instance, a * collision is reported when the an endpoint of the line is exactly on the * polygon, and when the line coincides with an edge. - * + * * \param poly The polygon * \param transformed_startPoint The start point transformed such that it is * on the same horizontal line as the end point @@ -532,12 +585,12 @@ class PolygonUtils /*! * Checks whether a given line segment collides with a given polygon(s). - * + * * If the line segment doesn't intersect with any edge of the polygon, but * merely touches it, a collision is also reported. For instance, a * collision is reported when the an endpoint of the line is exactly on the * polygon, and when the line coincides with an edge. - * + * * \param poly The polygon * \param startPoint The start point * \param endPoint The end point @@ -573,7 +626,11 @@ class PolygonUtils * \param[in] possible_adjacent_polys The vector of polygons we are testing. * \param[in] max_gap Polygons must be closer together than this distance to be considered adjacent. */ - static void findAdjacentPolygons(std::vector& adjacent_poly_indices, const ConstPolygonRef& poly, const std::vector& possible_adjacent_polys, const coord_t max_gap); + static void findAdjacentPolygons( + std::vector& adjacent_poly_indices, + const ConstPolygonRef& poly, + const std::vector& possible_adjacent_polys, + const coord_t max_gap); /*! * Calculate the Hamming Distance between two polygons relative to their own @@ -619,10 +676,31 @@ class PolygonUtils */ static Polygons clipPolygonWithAABB(const Polygons& src, const AABB& aabb); + /*! + * Generate a few outset polygons around the given base, according to the given line width + * + * \param inner_poly The inner polygon to start generating the outset from + * \param count The number of outer polygons to add + * \param line_width The actual line width to distance the polygons from each other (and from the base) + * \return The generated outset polygons + */ + static Polygons generateOutset(const Polygons& inner_poly, size_t count, coord_t line_width); + + /*! + * Generate inset polygons inside the given base, until there is no space left, according to the given line width + * + * \param outer_poly The outer polygon to start generating the inset from + * \param line_width The actual line width to distance the polygons from each other (and from the base) + * \param initial_inset The inset distance to be added to the first generated polygon + * \return The generated inset polygons + */ + static Polygons generateInset(const Polygons& outer_poly, coord_t line_width, coord_t initial_inset = 0); + private: /*! - * Helper function for PolygonUtils::moveInside2: moves a point \p from which was moved onto \p closest_polygon_point towards inside/outside when it's not already inside/outside by enough distance. - * + * Helper function for PolygonUtils::moveInside2: moves a point \p from which was moved onto \p closest_polygon_point towards inside/outside when it's not already + * inside/outside by enough distance. + * * \param closest_polygon_point The ClosestPolygonPoint we have to move inside * \param distance The distance by which to move the point. * \param from[in,out] The point to move. @@ -633,6 +711,6 @@ class PolygonUtils }; -}//namespace cura +} // namespace cura -#endif//POLYGON_OPTIMIZER_H +#endif // POLYGON_OPTIMIZER_H diff --git a/include/utils/resources/certificate.pem.h b/include/utils/resources/certificate.pem.h new file mode 100644 index 0000000000..534492366c --- /dev/null +++ b/include/utils/resources/certificate.pem.h @@ -0,0 +1,49 @@ +#ifndef CERTIFICATE_H +#define CERTIFICATE_H + +// FIXME: Move out of the source code even though this is the public key + +#include + +namespace cura::utils::resources +{ +constexpr std::string_view certificate = + R"#(-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIUd6fjXFbNBMZOxfBnv4FOokHlOC4wDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAk5MMRMwEQYDVQQIDApHZWxkZXJsYW5kMRUwEwYDVQQH +DAxHZWxkZXJtYWxzZW4xEjAQBgNVBAoMCVVsdGlNYWtlcjEbMBkGA1UECwwSQ29t +bXVuaXR5IFNvZnR3YXJlMRowGAYDVQQDDBFQbHVnaW5zIFZhbGlkYXRvcjAgFw0y +MzA1MTcxMzQ3NTFaGA8yMTEzMDIwMTEzNDc1MVowgYYxCzAJBgNVBAYTAk5MMRMw +EQYDVQQIDApHZWxkZXJsYW5kMRUwEwYDVQQHDAxHZWxkZXJtYWxzZW4xEjAQBgNV +BAoMCVVsdGlNYWtlcjEbMBkGA1UECwwSQ29tbXVuaXR5IFNvZnR3YXJlMRowGAYD +VQQDDBFQbHVnaW5zIFZhbGlkYXRvcjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBALNcLyGzwSpkGzsiM9pT821AdXJqCM7CBwsy9E9Tpf/D+iBdwQsukIBc +HrkHf9g+q9BYDIKu0/eqeE7coQg0HAJ/CxowWAAo5MCYJHqDGp73IHUVxvGetVVr +y6r13QEpj/Mzvz4nnyZMlwdXjWLyz/AtDRfT0i+TbyqK3BYBTs0YW3/fG4g4Dd+v +8OsQOjfDDTv5iQSaNQuiSfCbQA8bL1aXqijFcE2imRnLFQ3IxrcLVkkXEA4xAps5 +34aVKVBXFAS7UII0MmTvhmHSZ6Vl21fAhmGwJGPrQSlSQ8JhVuIs4q7Tfp1SHeoH ++lmid9X92iWlrM/gDZLiKMk8VHlt9Y1kaYPUUKq1mNoHGJ8Ix4J8umCVCaIezYY5 +QqmCLKniP4VuCGoYaXuiWf/EOa+YGMGYaKB9yJLsQpuoulWVTrKyriv5YW68CT0u +jokCDmf33rQc4axxnu0ot+Up6nmV7XcGApLQWxJfbx9hhFOYfipWzgtX1FlXbx62 +5gG8WWw0NjCKpTN39Qq7y8bJIJoCMXlKrZuZ5yDIJsgTsVYX3RLOw+CuOiagjN0f +9btL4X7uO0b34vFjckd1GDrDgNtWjEJiFBQqM82e3BzwEcG1Gkh6LqaFzJpqo8Ql +tAwr+0uWTN5jCe5+COiSKf3r7YBhCdfsPe2U95+g6GGq2+J3IPlbAgMBAAGjUzBR +MB0GA1UdDgQWBBTTEqyXIy/N6+MkniTCLk3bRMIcTzAfBgNVHSMEGDAWgBTTEqyX +Iy/N6+MkniTCLk3bRMIcTzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4ICAQAM8YYARPRmAYVJTrDL1D7I8w7vRHkRak57Y3CpvgcCQLZSjsc94FVeUUbd +zE7vLM/Fh59hu4PJQfrBfCpoixRqZ5Wqmh0R/Ws0mxaqBnkRcKR/Su76iFGiEAQ1 +tzHjGTUw+Qg8/rR9QoJjhwjrjofBYifAyNikvJ0AeHkAWLdT4BFJjZydi2zGoeZw +Er5uROlGntso2BEoW7sGsFVXjCN8R+oezihowjqluRbQ6yuKj6Bpb6AMZVAj7bx0 +SnoE1Nqo6b0qyAd8HldJK5NinXdwbOzhSs1JtISLB/5r9ZJYmgBIEqom03/lJ49s +Y2ceQP+xK0UApsHifDlOo8/3NoUiaZnCfGF+feEE7b6+hDQ7PdoY6wve3sUrpjKf +C2qmE5LEOR0fHxCgj8KiRm+AapomJdGHjlbaaf2q+9cnG7WV6K/OFz+f38MVS9Cz +XbOyLHSJGZNMjdLy722DT8saVrR85nStG4KrnlDDsr0kLp31QpObHWgw6NU4pU5n +/ZIUW53dq0QUy3sTpehM3U1EfxWLkqP5EwID5Vny27VEqrHohlXjjWOf/G0Q64MS +ZtGCFwzJwPHmTaVZnLO8zc0tbnzJjxkJbrEnhaQvHdz0BBHEFvZafUFCGJahMPJV +c3dQRszg9OTAyUwNKQEAuZ9II/cCpLyE+GbnqRdkErHgO7kiKQ== +-----END CERTIFICATE----- +)#"; + +} // namespace cura::utils::resources + +#endif // CERTIFICATE_H diff --git a/include/utils/types/arachne.h b/include/utils/types/arachne.h new file mode 100644 index 0000000000..ae96a13ca9 --- /dev/null +++ b/include/utils/types/arachne.h @@ -0,0 +1,146 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_TYPES_ARACHNE_H +#define UTILS_TYPES_ARACHNE_H + +#include "utils/types/generic.h" +#include "utils/types/geometry.h" + +#include + +#include +#include +#include + +namespace cura::utils +{ +// clang-format off +template +concept st_storable_data = requires(T val) +{ + val.data; +}; + +/*! + * @brief A node in a skeleton trapezoidal graph, defined as a 2D point with additional stored data. + * @details This concept is used to check if a type is a node in a straight skeleton graph. A node is a type that is a 2D point with additional stored data. + * @tparam T Type to check + */ +template +concept st_node = requires(T val) +{ + requires point2d; +}; + +/*! + * @brief A edge in a skeleton trapezoidal graph, defined as a 2D point with additional stored data. + * @details This concept is used to check if a type is a edge in a skeleton trapezoidal graph. defined as a pair of nodes with pointers to the next, previous, and twin edges, and additional stored data. + * @tparam T Type to check + */ +template +concept st_edge = requires(T val) +{ + requires std::is_pointer_v; + requires st_node; + requires std::is_pointer_v; + requires st_node; + requires std::is_pointer_v; + requires std::is_pointer_v; + requires std::is_pointer_v; +}; + +template +concept st_edges = requires(T edges) +{ + requires ranges::range; + requires st_edge; + requires st_storable_data; +}; + +template +concept st_nodes = requires(T nodes) +{ + requires ranges::range; + requires st_node; + requires st_storable_data; +}; + +/*! + * @brief A skeleton trapezoidal graph, defined as a collection of nodes and edges. + * @details This concept is used to check if a type is a skeleton trapezoidal graph. + * @tparam T Type to check + */ +template +concept st_graph = requires(T graph) +{ + requires st_edges; + requires st_nodes; +}; + +/*! + * @brief A 2D point with an associated weight value + * @details This concept is used to check if a type is a junction as used in wall toolpaths + * @tparam T Type to check + */ +template +concept junction = requires(T val) +{ + requires point2d; + requires utils::integral; +}; + +/*! + * @brief A collection of junctions + * @details This concept is used to check if a type is a collection of junctions + * @tparam T Type to check + */ +template +concept junctions = requires(T val) +{ + requires ranges::range; + requires junction; +}; + +/*! + * @brief an Extrusion line in a toolpath + * @details This concept is used to check if a type is a collection of junctions. A series of junctions defining a path for extrusion, + * with additional flags indicating whether the path is closed and whether it corresponds to an odd or even layer. + * @tparam T Type to check + */ +template +concept extrusion_line = requires(T val) +{ + std::is_same_v; + std::is_same_v; + std::is_same_v; + requires junctions; +}; + +/*! + * @brief A collection of extrusion lines + * @details This concept is used to check if a type is a collection of extrusion lines + * @tparam T Type to check + */ +template +concept toolpath = requires(T tp) +{ + requires ranges::range; + requires extrusion_line; +}; + +/*! + * @brief A collection of toolpaths + * @details This concept is used to check if a type is a collection of toolpaths + * @tparam T Type to check + */ +template +concept toolpaths = requires(T tp) +{ + requires ranges::range; + requires toolpath; +}; +// clang-format on +} // namespace cura::utils + +#endif // UTILS_TYPES_ARACHNE_H \ No newline at end of file diff --git a/include/utils/types/char_range_literal.h b/include/utils/types/char_range_literal.h new file mode 100644 index 0000000000..44a1d0b599 --- /dev/null +++ b/include/utils/types/char_range_literal.h @@ -0,0 +1,25 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_TYPES_CHAR_RANGE_LITERAL_H +#define UTILS_TYPES_CHAR_RANGE_LITERAL_H + +#include + + +namespace cura::utils +{ +template +struct CharRangeLiteral +{ + constexpr CharRangeLiteral(const char (&str)[N]) noexcept + { + std::copy_n(str, N, value); + } + + char value[N]; +}; + +} // namespace cura::utils + +#endif // UTILS_TYPES_CHAR_RANGE_LITERAL_H \ No newline at end of file diff --git a/include/utils/types/generic.h b/include/utils/types/generic.h new file mode 100644 index 0000000000..7d53dcfe86 --- /dev/null +++ b/include/utils/types/generic.h @@ -0,0 +1,68 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_GENERIC_H +#define CURAENGINE_GENERIC_H + +#include + +#include +#include +#include + +namespace cura::utils +{ +// clang-format off +template +concept hashable = requires(T value) +{ + { std::hash{}(value) } -> concepts::convertible_to; +}; + +template +concept grpc_convertable = requires(T value) +{ + requires ranges::semiregular; + requires ranges::semiregular; + requires ranges::semiregular; +}; + +#ifdef OLDER_APPLE_CLANG + +// std::integral and std::floating_point are not implemented in older Apple Clang versions < 13 +// https://stackoverflow.com/questions/71818683/stdintegral-not-found-in-clang13-c20-error +template +concept integral = + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + +template +concept floating_point = std::is_same_v || std::is_same_v || std::is_same_v; +#else +template +concept integral = std::integral; + +template +concept floating_point = std::floating_point; +#endif +// clang-format on + +template +concept numeric = std::is_arithmetic_v>; +} // namespace cura::utils + +#endif // CURAENGINE_GENERIC_H diff --git a/include/utils/types/geometry.h b/include/utils/types/geometry.h new file mode 100644 index 0000000000..76620bc38c --- /dev/null +++ b/include/utils/types/geometry.h @@ -0,0 +1,156 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +/*! @file geometry.h + * @brief Defines geometric concepts like Point2D, Point3D, Segment etc. + */ + +#ifndef UTILS_TYPES_GEOMETRY_H +#define UTILS_TYPES_GEOMETRY_H + +#include "utils/types/generic.h" + +#include +#include + +#include +#include +#include +#include + +namespace cura::utils +{ +// clang-format off + +/*! + * @concept point2d_tuple + * @brief Checks whether T is a 2D point represented as a tuple with two integral elements + * @tparam T The type to check + */ +template +concept point2d_tuple = requires(T t) +{ + requires std::is_same_v::type, typename std::tuple_element<0, T>::type>>; + requires utils::numeric>; +}; + +/*! + * @concept point2d_ranged + * @brief Checks whether T is a 2D point represented as a range with two integral elements + * @tparam T The type to check + */ +template +concept point2d_ranged = ranges::range && requires(T point) +{ + requires ranges::size(point) == 2; + requires utils::numeric>; +}; + + +/*! + * @concept point2d_named + * @brief Checks whether T is a 2D point represented as an object with X and Y integral fields + * @tparam T The type to check + */ +template +concept point2d_named = requires(T point) +{ + requires utils::numeric; + requires utils::numeric; +}; + +/*! + * @concept point2d + * @brief Checks whether T is a 2D point. A type satisfying any of point2d_tuple, point2d_ranged, or point2d_named + * @tparam T The type to check + */ +template +concept point2d = point2d_named || point2d_ranged || point2d_tuple; + +/*! + * @concept point3d_tuple + * @brief Checks whether T is a 3D point represented as a tuple with three integral elements + * @tparam T The type to check + */ +template +concept point3d_tuple = requires(T t) +{ + requires std::is_same_v::type, typename std::tuple_element<0, T>::type, typename std::tuple_element<0, T>::type>>; + requires utils::numeric>; +}; + +/*! + * @concept point3d_ranged + * @brief Checks whether T is a 3D point represented as a range with three integral elements + * @tparam T The type to check + */ +template +concept point3d_ranged = ranges::range && requires(T point) +{ + requires ranges::size(point) == 3; + requires utils::numeric>; +}; + +/*! + * @concept point3d_named + * @brief Checks whether T is a 3D point represented as an object with X, Y and Z integral fields + * @tparam T The type to check + */ +template +concept point3d_named = requires(T point) +{ + requires utils::numeric; + requires utils::numeric; + requires utils::numeric; +}; + +/*! + * @concept point3d + * @brief Checks whether T is a 3D point. A type satisfying any of point3d_tuple, point3d_ranged, or point3d_named + * @tparam T The type to check + */ +template +concept point3d = point3d_named || point3d_ranged || point3d_tuple; + +/*! + * @concept point_named + * @brief Checks whether T is a point represented as an object with named fields + * @tparam T The type to check + */ +template +concept point_named = point2d_named || point3d_named; + +/*! + * @concept point + * @brief Checks whether T is a 2D or 3D point + * @tparam T The type to check + */ +template +concept point = point2d || point3d; + +/*! + * @concept segment + * @brief Checks whether T represents a segment as a pair of 2D or 3D points. + * @tparam T The type to check + */ +template +concept segment = requires(T segment) +{ + requires point(segment))>; + requires point(segment))>; +}; + +/*! + * @concept segment_range + * @brief Checks whether T is a range of valid segments + * @tparam T The type to check + */ +template +concept segment_range = ranges::range && requires(T segment_range) +{ + requires segment; +}; +// clang-format on +} // namespace cura::utils + +#endif // UTILS_TYPES_GEOMETRY_H \ No newline at end of file diff --git a/include/utils/types/get.h b/include/utils/types/get.h new file mode 100644 index 0000000000..4df9c0f494 --- /dev/null +++ b/include/utils/types/get.h @@ -0,0 +1,147 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_TYPES_GET_H +#define UTILS_TYPES_GET_H + +#include "utils/types/arachne.h" +#include "utils/types/char_range_literal.h" +#include "utils/types/geometry.h" + +namespace std +{ + +/** + * @brief Get a coordinate of a 2d point by index. + * + * @tparam N The index of the coordinate to get (0 for X, 1 for Y). + * @param point The point to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::point2d_named auto& point) noexcept +{ + static_assert(N < 2, "Index out of bounds"); + if constexpr (N == 0) + { + return point.X; + } + return point.Y; +} + +/** + * @brief Get a coordinate of a 2d point by character. + * + * @tparam C The character of the coordinate to get ('x' or 'X' for X-coordinate, 'y' or 'Y' for Y-coordinate). + * @param point The point to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::point2d auto& point) noexcept +{ + constexpr std::string_view idx = C.value; + static_assert(idx.size() == 1, "Only one character allowed"); + static_assert(idx.starts_with("X") || idx.starts_with("x") || idx.starts_with("Y") || idx.starts_with("y"), "Index out of bounds"); + if constexpr (idx.starts_with("X") || idx.starts_with("x")) + { + return std::get<0>(point); + } + return std::get<1>(point); +} + +/** + * @brief Get a coordinate of a 2d point by character. + * + * @tparam C The character of the coordinate to get ('x' or 'X' for X-coordinate, 'y' or 'Y' for Y-coordinate). + * @param point The point to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr const auto& get(const cura::utils::point2d auto& point) noexcept +{ + constexpr std::string_view idx = C.value; + static_assert(idx.size() == 1, "Only one character allowed"); + static_assert(idx.starts_with("X") || idx.starts_with("x") || idx.starts_with("Y") || idx.starts_with("y"), "Index out of bounds"); + if constexpr (idx.starts_with("X") || idx.starts_with("x")) + { + return std::get<0>(point); + } + return std::get<1>(point); +} + +/** + * @brief Get a coordinate of a 3d point by index. + * + * @tparam N The index of the coordinate to get (0 for X, 1 for Y, 2 for Z). + * @param point The point to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::point3d_named auto& point) noexcept +{ + static_assert(N < 3, "Index out of bounds"); + if constexpr (N == 0) + { + return point.x; + } + else if constexpr (N == 1) + { + return point.y; + } + return point.z; +} + +/** + * @brief Get a coordinate of a 3d point by character. + * + * @tparam C The character of the coordinate to get ('x' or 'X' for X-coordinate, 'y' or 'Y' for Y-coordinate, 'z' or 'Z' for Z-coordinate). + * @param point The point to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::point3d auto& point) noexcept +{ + constexpr std::string_view idx = C.value; + static_assert(idx.size() == 1, "Only one character allowed"); + static_assert(idx.starts_with("X") || idx.starts_with("x") || idx.starts_with("Y") || idx.starts_with("y") || idx.starts_with("Z") || idx.starts_with("z"), "Index out of bounds"); + if constexpr (idx.starts_with("X") || idx.starts_with("x")) + { + return std::get<0>(point); + } + if constexpr (idx.starts_with("Y") || idx.starts_with("y")) + { + return std::get<1>(point); + } + return std::get<2>(point); +} + +/** + * @brief Get a coordinate of a junction by index. + * + * @tparam N The index of the coordinate to get. + * @param junction The junction to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::junction auto& junction) noexcept +{ + return get(junction.p); +} + +/** + * @brief Get a coordinate of a junction by character. + * + * @tparam C The character of the coordinate to get. + * @param junction The junction to get the coordinate from. + * @return auto& A reference to the requested coordinate. + */ +template +constexpr auto& get(cura::utils::junction auto& junction) noexcept +{ + return get(junction.p); +} + +} // namespace std + + +#endif // UTILS_TYPES_GET_H \ No newline at end of file diff --git a/include/utils/concepts/graph.h b/include/utils/types/graph.h similarity index 92% rename from include/utils/concepts/graph.h rename to include/utils/types/graph.h index 0a5c1e42a6..ea74ab65f6 100644 --- a/include/utils/concepts/graph.h +++ b/include/utils/types/graph.h @@ -4,13 +4,14 @@ #ifndef UTILS_CONCEPTS_GRAPH_H #define UTILS_CONCEPTS_GRAPH_H +#include "utils/types/generic.h" + #include #include -#include "utils/concepts/generic.h" - -namespace cura +namespace cura::utils { +// clang-format off /* # nodable * Describing the basic requirement for a node in a graph. @@ -32,6 +33,8 @@ concept graphable = template concept setable = nodeable && (std::is_same>::value || std::is_same>::value || std::is_same>::value); + +// clang-format off } // namespace cura #endif // UTILS_CONCEPTS_GRAPH_H diff --git a/include/utils/types/numeric_facade.h b/include/utils/types/numeric_facade.h new file mode 100644 index 0000000000..ce986fcf25 --- /dev/null +++ b/include/utils/types/numeric_facade.h @@ -0,0 +1,169 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_TYPES_ARITHMITIC_FACADE_H +#define UTILS_TYPES_ARITHMITIC_FACADE_H + +#include "utils/types/generic.h" + +namespace cura::utils +{ + +template +struct NumericFacade +{ + using value_type = T; + + value_type value{}; + + constexpr NumericFacade() noexcept = default; + + constexpr NumericFacade(const NumericFacade& other) noexcept = default; + constexpr NumericFacade(NumericFacade&& other) noexcept = default; + + constexpr NumericFacade(const floating_point auto val) noexcept + : value{ static_cast(val) } {}; + constexpr explicit NumericFacade(const integral auto val) noexcept + : value{ static_cast(val) } {}; + + constexpr NumericFacade& operator=(const NumericFacade& other) noexcept = default; + + constexpr NumericFacade& operator=(const floating_point auto& other) noexcept + { + this->value = static_cast(other); + return *this; + } + + constexpr NumericFacade& operator=(NumericFacade&& other) noexcept = default; + + constexpr NumericFacade& operator=(const floating_point auto&& other) noexcept + { + this->value = static_cast(other); + return *this; + } + + ~NumericFacade() noexcept = default; + + constexpr operator value_type() const noexcept + { + return value; + } + + constexpr bool operator==(const NumericFacade& other) const noexcept + { + return value == other.value; + } + + constexpr bool operator==(const floating_point auto& other) const noexcept + { + return value == static_cast(other); + } + + constexpr auto operator<=>(const NumericFacade& other) const noexcept = default; + constexpr auto operator<=>(const floating_point auto& other) const noexcept + { + return value <=> static_cast(other); + }; + + constexpr NumericFacade& operator+=(const NumericFacade& other) noexcept + { + value += other.value; + return *this; + } + + constexpr NumericFacade& operator+=(const floating_point auto& other) noexcept + { + value += static_cast(other); + return *this; + } + + constexpr NumericFacade& operator-=(const NumericFacade& other) noexcept + { + value -= other.value; + return *this; + } + + constexpr NumericFacade& operator-=(const floating_point auto& other) noexcept + { + value -= static_cast(other); + return *this; + } + + constexpr NumericFacade& operator*=(const NumericFacade& other) noexcept + { + value *= other.value; + return *this; + } + + constexpr NumericFacade& operator*=(const floating_point auto& other) noexcept + { + value *= static_cast(other); + return *this; + } + + constexpr NumericFacade& operator/=(const NumericFacade& other) + { + value /= other.value; + return *this; + } + + constexpr NumericFacade& operator/=(const floating_point auto& other) + { + value /= static_cast(other); + return *this; + } + + constexpr NumericFacade operator+(const NumericFacade& other) const noexcept + { + return { value + other.value }; + } + + constexpr NumericFacade operator+(NumericFacade&& other) const noexcept + { + return { value + other.value }; + } + + constexpr NumericFacade operator+(const floating_point auto& other) const noexcept + { + return { value + static_cast(other) }; + } + + constexpr NumericFacade operator-(const NumericFacade& other) const noexcept + { + return { value - other.value }; + } + + constexpr NumericFacade operator-(const floating_point auto& other) const noexcept + { + return { value - static_cast(other) }; + } + + constexpr NumericFacade operator*(const NumericFacade& other) const noexcept + { + return { value * other.value }; + } + + constexpr NumericFacade operator*(const floating_point auto& other) const noexcept + { + return { value * static_cast(other) }; + } + + constexpr NumericFacade operator/(const NumericFacade& other) const + { + return { value / other.value }; + } + + constexpr NumericFacade operator/(const floating_point auto& other) const + { + return { value / static_cast(other) }; + } + + constexpr NumericFacade operator-() const noexcept + { + return { -value }; + } +}; + +} // namespace cura::utils + +#endif // UTILS_TYPES_ARITHMITIC_FACADE_H diff --git a/include/utils/types/string_switch.h b/include/utils/types/string_switch.h new file mode 100644 index 0000000000..7b520858ae --- /dev/null +++ b/include/utils/types/string_switch.h @@ -0,0 +1,41 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef UTILS_TYPES_STRING_SWITCH_H +#define UTILS_TYPES_STRING_SWITCH_H + +#include + +namespace cura::utils +{ + +// Source: https://learnmoderncpp.com/2020/06/01/strings-as-switch-case-labels/ +constexpr inline uint64_t hash_djb2a(const std::string_view value) +{ + uint64_t hash{ 5381 }; + for (unsigned char c : value) + { + hash = ((hash << 5) + hash) ^ c; + } + return hash; +} + +constexpr inline uint64_t hash_enum(const std::string_view value) +{ + constexpr uint64_t plugin_namespace_sep_location{ 6 }; + if (value.size() > plugin_namespace_sep_location && value.at(plugin_namespace_sep_location) == ':') + { + return hash_djb2a("plugin"); + } + return hash_djb2a(value); +} + +constexpr inline auto operator""_sw(const char* str, size_t len) +{ + return hash_enum(std::string_view{ str, len }); +} + + +} // namespace cura::utils + +#endif // UTILS_TYPES_STRING_SWITCH_H diff --git a/include/utils/views/dfs.h b/include/utils/views/dfs.h index c5799ee018..0d509b8004 100644 --- a/include/utils/views/dfs.h +++ b/include/utils/views/dfs.h @@ -7,7 +7,7 @@ #include #include -#include "utils/concepts/graph.h" +#include "utils/types/graph.h" namespace cura::actions { @@ -24,7 +24,7 @@ namespace cura::actions namespace details { -template +template std::function(const Node, const Graph&)> get_neighbours = [](const Node current_node, const Graph& graph) { const auto& [neighbour_begin, neighbour_end] = graph.equal_range(current_node); @@ -38,7 +38,7 @@ std::function(const Node, const Graph&)> get_neighbours = [](c }; }; -template +template constexpr void dfs( const Node& current_node, const Graph& graph, @@ -61,7 +61,7 @@ constexpr void dfs( } } -template +template constexpr void dfs_parent_state(const Node& current_node, const Graph& graph, std::function handle_node) { const std::function parent_view = [handle_node](auto current_node, auto parent_node) @@ -74,7 +74,7 @@ constexpr void dfs_parent_state(const Node& current_node, const Graph& graph, st dfs(current_node, graph, parent_view, visited); } -template +template constexpr void dfs_depth_state(const Node& current_node, const Graph& graph, std::function handle_node) { const std::function depth_view = [handle_node](auto current_node, auto depth) diff --git a/src/Application.cpp b/src/Application.cpp index c5a3a6f102..0074940150 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -3,41 +3,46 @@ #include "Application.h" -#include -#include -#include +#include "FffProcessor.h" +#include "communication/ArcusCommunication.h" //To connect via Arcus to the front-end. +#include "communication/CommandLine.h" //To use the command line to slice stuff. +#include "plugins/slots.h" +#include "progress/Progress.h" +#include "utils/ThreadPool.h" +#include "utils/string.h" //For stringcasecompare. +#include //For generating a UUID. +#include //For generating a UUID. #include #include +#include +#include +#include #include #include #include -#include -#include -#include -#include "FffProcessor.h" -#include "communication/ArcusCommunication.h" //To connect via Arcus to the front-end. -#include "communication/CommandLine.h" //To use the command line to slice stuff. -#include "progress/Progress.h" -#include "utils/ThreadPool.h" -#include "utils/string.h" //For stringcasecompare. +#include +#include +#include namespace cura { Application::Application() + : instance_uuid(boost::uuids::to_string(boost::uuids::random_generator()())) { auto dup_sink = std::make_shared(std::chrono::seconds{ 10 }); auto base_sink = std::make_shared(); dup_sink->add_sink(base_sink); - spdlog::default_logger()->sinks() = std::vector>{ dup_sink }; // replace default_logger sinks with the duplicating filtering sink to avoid spamming + spdlog::default_logger()->sinks() + = std::vector>{ dup_sink }; // replace default_logger sinks with the duplicating filtering sink to avoid spamming if (auto spdlog_val = spdlog::details::os::getenv("CURAENGINE_LOG_LEVEL"); ! spdlog_val.empty()) { spdlog::cfg::helpers::load_levels(spdlog_val); - } + }; } Application::~Application() @@ -135,9 +140,11 @@ void Application::printHelp() const fmt::print(" -o \n\tSpecify a file to which to write the generated gcode.\n"); fmt::print("\n"); fmt::print("The settings are appended to the last supplied object:\n"); - fmt::print("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e0 [extruder train 0 settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object settings] \n\t--next [next group settings]\n\t... etc.\n"); + fmt::print("CuraEngine slice [general settings] \n\t-g [current group settings] \n\t-e0 [extruder train 0 settings] \n\t-l obj_inheriting_from_last_extruder_train.stl [object " + "settings] \n\t--next [next group settings]\n\t... etc.\n"); fmt::print("\n"); - fmt::print("In order to load machine definitions from custom locations, you need to create the environment variable CURA_ENGINE_SEARCH_PATH, which should contain all search paths delimited by a (semi-)colon.\n"); + fmt::print("In order to load machine definitions from custom locations, you need to create the environment variable CURA_ENGINE_SEARCH_PATH, which should contain all search " + "paths delimited by a (semi-)colon.\n"); fmt::print("\n"); } @@ -145,7 +152,7 @@ void Application::printLicense() const { fmt::print("\n"); fmt::print("Cura_SteamEngine version {}\n", CURA_ENGINE_VERSION); - fmt::print("Copyright (C) 2022 Ultimaker\n"); + fmt::print("Copyright (C) 2023 Ultimaker\n"); fmt::print("\n"); fmt::print("This program is free software: you can redistribute it and/or modify\n"); fmt::print("it under the terms of the GNU Affero General Public License as published by\n"); @@ -247,4 +254,4 @@ void Application::startThreadPool(int nworkers) thread_pool = new ThreadPool(nthreads); } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/src/ConicalOverhang.cpp b/src/ConicalOverhang.cpp index d1fdf7dd0d..03b3b80222 100644 --- a/src/ConicalOverhang.cpp +++ b/src/ConicalOverhang.cpp @@ -1,12 +1,14 @@ -//Copyright (c) 2016 Tim Kuipers -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2016 Tim Kuipers +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "ConicalOverhang.h" + #include "mesh.h" -#include "utils/Simplify.h" //Simplifying at every step to prevent getting lots of vertices from all the insets. -#include "slicer.h" #include "settings/types/Angle.h" //To process the overhang angle. +#include "settings/types/LayerIndex.h" +#include "slicer.h" +#include "utils/Simplify.h" //Simplifying at every step to prevent getting lots of vertices from all the insets. namespace cura { @@ -15,11 +17,11 @@ void ConicalOverhang::apply(Slicer* slicer, const Mesh& mesh) { const AngleRadians angle = mesh.settings.get("conical_overhang_angle"); const double maxHoleArea = mesh.settings.get("conical_overhang_hole_size"); - const double tan_angle = tan(angle); // the XY-component of the angle + const double tan_angle = tan(angle); // the XY-component of the angle const coord_t layer_thickness = mesh.settings.get("layer_height"); coord_t max_dist_from_lower_layer = tan_angle * layer_thickness; // max dist which can be bridged - for(unsigned int layer_nr = slicer->layers.size() - 2; static_cast(layer_nr) >= 0; layer_nr--) + for (LayerIndex layer_nr = slicer->layers.size() - 2; static_cast(layer_nr) >= 0; layer_nr--) { SlicerLayer& layer = slicer->layers[layer_nr]; SlicerLayer& layer_above = slicer->layers[layer_nr + 1]; @@ -30,7 +32,7 @@ void ConicalOverhang::apply(Slicer* slicer, const Mesh& mesh) Polygons diff = layer_above.polygons.difference(layer.polygons.offset(-safe_dist)); layer.polygons = layer.polygons.unionPolygons(diff); layer.polygons = layer.polygons.smooth(safe_dist); - layer.polygons = Simplify(safe_dist, safe_dist / 2, 0).polygon(layer.polygons); + layer.polygons = Simplify(safe_dist, safe_dist / 2, 0).polygon(layer.polygons); // somehow layer.polygons get really jagged lines with a lot of vertices // without the above steps slicing goes really slow } @@ -43,22 +45,22 @@ void ConicalOverhang::apply(Slicer* slicer, const Mesh& mesh) // Now go through all the holes in the current layer and check if they intersect anything in the layer above // If not, then they're the top of a hole and should be cut from the layer above before the union - for(unsigned int part = 0; part < layerParts.size(); part++) + for (unsigned int part = 0; part < layerParts.size(); part++) { - if(layerParts[part].size() > 1) // first poly is the outer contour, 1..n are the holes + if (layerParts[part].size() > 1) // first poly is the outer contour, 1..n are the holes { - for(unsigned int hole_nr = 1; hole_nr < layerParts[part].size(); ++hole_nr) + for (unsigned int hole_nr = 1; hole_nr < layerParts[part].size(); ++hole_nr) { Polygons holePoly; holePoly.add(layerParts[part][hole_nr]); if (maxHoleArea > 0.0 && INT2MM2(std::abs(holePoly.area())) < maxHoleArea) { Polygons holeWithAbove = holePoly.intersection(above); - if(!holeWithAbove.empty()) + if (! holeWithAbove.empty()) { // The hole had some intersection with the above layer, check if it's a complete overlap Polygons holeDifference = holePoly.xorPolygons(holeWithAbove); - if(holeDifference.empty()) + if (holeDifference.empty()) { // The hole was returned unchanged, so the layer above must completely cover it. Remove the hole from the layer above. above = above.difference(holePoly); @@ -68,10 +70,10 @@ void ConicalOverhang::apply(Slicer* slicer, const Mesh& mesh) } } } - // And now union with offset of the resulting above layer + // And now union with offset of the resulting above layer layer.polygons = layer.polygons.unionPolygons(above.offset(-max_dist_from_lower_layer)); } } } -}//namespace cura +} // namespace cura diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp new file mode 100644 index 0000000000..1b59ff7af4 --- /dev/null +++ b/src/ExtruderPlan.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "ExtruderPlan.h" + +namespace cura +{ +ExtruderPlan::ExtruderPlan( + const size_t extruder, + const LayerIndex layer_nr, + const bool is_initial_layer, + const bool is_raft_layer, + const coord_t layer_thickness, + const FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, + const RetractionConfig& retraction_config) + : extruder_nr(extruder) + , layer_nr(layer_nr) + , is_initial_layer(is_initial_layer) + , is_raft_layer(is_raft_layer) + , layer_thickness(layer_thickness) + , fan_speed_layer_time_settings(fan_speed_layer_time_settings) + , retraction_config(retraction_config) +{ +} + +void ExtruderPlan::insertCommand(NozzleTempInsert&& insert) +{ + inserts.emplace_back(insert); +} + +void ExtruderPlan::handleInserts(const size_t path_idx, GCodeExport& gcode, const double cumulative_path_time) +{ + while (! inserts.empty() && path_idx >= inserts.front().path_idx && inserts.front().time_after_path_start < cumulative_path_time) + { // handle the Insert to be inserted before this path_idx (and all inserts not handled yet) + inserts.front().write(gcode); + inserts.pop_front(); + } +} + +void ExtruderPlan::handleAllRemainingInserts(GCodeExport& gcode) +{ + while (! inserts.empty()) + { // handle the Insert to be inserted before this path_idx (and all inserts not handled yet) + NozzleTempInsert& insert = inserts.front(); + insert.write(gcode); + inserts.pop_front(); + } +} + +void ExtruderPlan::setFanSpeed(double _fan_speed) +{ + fan_speed = _fan_speed; +} +double ExtruderPlan::getFanSpeed() +{ + return fan_speed; +} + +void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compensation) +{ + constexpr double epsilon_speed_factor = 0.001; // Don't put on actual 'limit double minimum', because we don't want printers to stall. + for (auto& path : paths) + { + const double nominal_width_for_path = static_cast(path.config.getLineWidth()); + if (path.width_factor <= 0.0 || nominal_width_for_path <= 0.0 || path.config.isTravelPath() || path.config.isBridgePath()) + { + continue; + } + const double line_width_for_path = path.width_factor * nominal_width_for_path; + path.speed_back_pressure_factor = std::max(epsilon_speed_factor, 1.0 + (nominal_width_for_path / line_width_for_path - 1.0) * back_pressure_compensation); + } +} +} // namespace cura \ No newline at end of file diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 542d7e3c33..a42d12889d 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1,20 +1,24 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "FffGcodeWriter.h" + #include #include // numeric_limits #include +#include +#include #include #include -#include //For generating a UUID. -#include //For generating a UUID. -#include +#include +#include +#include #include +#include #include "Application.h" #include "ExtruderTrain.h" -#include "FffGcodeWriter.h" #include "FffProcessor.h" #include "InsetOrderOptimizer.h" #include "LayerPlan.h" @@ -34,7 +38,10 @@ namespace cura { -FffGcodeWriter::FffGcodeWriter() : max_object_height(0), layer_plan_buffer(gcode), slice_uuid(boost::uuids::to_string(boost::uuids::random_generator()())) +FffGcodeWriter::FffGcodeWriter() + : max_object_height(0) + , layer_plan_buffer(gcode) + , slice_uuid(Application::getInstance().instance_uuid) { for (unsigned int extruder_nr = 0; extruder_nr < MAX_EXTRUDERS; extruder_nr++) { // initialize all as max layer_nr, so that they get updated to the lowest layer on which they are used. @@ -91,7 +98,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep { auto should_prime_extruder = gcode.initializeExtruderTrains(storage, start_extruder_nr); - if (!should_prime_extruder) + if (! should_prime_extruder) { // set to most negative number so that layer processing never primes this extruder anymore. extruder_prime_layer_nr[start_extruder_nr] = std::numeric_limits::min(); @@ -103,17 +110,19 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep } size_t total_layers = 0; - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh_ptr : storage.meshes) { - if (mesh.isPrinted()) // No need to process higher layers if the non-printed meshes are higher than the normal meshes. - { - size_t mesh_layer_num = mesh.layers.size(); - for (; mesh_layer_num > 0 && mesh.layers[mesh_layer_num - 1].getOutlines().empty(); --mesh_layer_num); - // No body, calculation of _actual_ number of layers in loop. + auto& mesh = *mesh_ptr; + size_t mesh_layer_num = mesh.layers.size(); - total_layers = std::max(total_layers, mesh_layer_num); + // calculation of _actual_ number of layers in loop. + while (mesh_layer_num > 0 && mesh.layers[mesh_layer_num - 1].getOutlines().empty()) + { + mesh_layer_num--; } + total_layers = std::max(total_layers, mesh_layer_num); + setInfillAndSkinAngles(mesh); } @@ -158,14 +167,17 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep run_multiple_producers_ordered_consumer( process_layer_starting_layer_nr, total_layers, - [&storage, total_layers, this](int layer_nr) { return &processLayer(storage, layer_nr, total_layers); }, - [this, total_layers](LayerPlan* gcode_layer) + [&storage, total_layers, this](int layer_nr) + { + return std::make_optional(processLayer(storage, layer_nr, total_layers)); + }, + [this, total_layers](std::optional result_opt) { - Progress::messageProgress(Progress::Stage::EXPORT, std::max(0, gcode_layer->getLayerNr()) + 1, total_layers); - layer_plan_buffer.handle(*gcode_layer, gcode); + const ProcessLayerResult& result = result_opt.value(); + Progress::messageProgressLayer(result.layer_plan->getLayerNr(), total_layers, result.total_elapsed_time, result.stages_times); + layer_plan_buffer.handle(*result.layer_plan, gcode); }); - layer_plan_buffer.flush(); Progress::messageProgressStage(Progress::Stage::FINISH, &time_keeper); @@ -262,7 +274,7 @@ void FffGcodeWriter::findLayerSeamsForSpiralize(SliceDataStorage& storage, size_ const std::vector& mesh_order = mesh_order_per_extruder[extruder_nr]; for (unsigned int mesh_idx : mesh_order) { - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; // if this mesh has layer data for this layer process it if (! done_this_layer && mesh.layers.size() > layer_nr) { @@ -327,7 +339,8 @@ static void retractionAndWipeConfigFromSettings(const Settings& settings, Retrac switch_retraction_config.primeSpeed = settings.get("switch_extruder_prime_speed"); switch_retraction_config.zHop = settings.get("retraction_hop_after_extruder_switch_height"); switch_retraction_config.retraction_min_travel_distance = 0; // No limitation on travel distance for an extruder switch retract. - switch_retraction_config.retraction_extrusion_window = 99999.9; // So that extruder switch retractions won't affect the retraction buffer (extruded_volume_at_previous_n_retractions). + switch_retraction_config.retraction_extrusion_window + = 99999.9; // So that extruder switch retractions won't affect the retraction buffer (extruded_volume_at_previous_n_retractions). switch_retraction_config.retraction_count_max = 9999999; // Extruder switch retraction is never limited. WipeScriptConfig& wipe_config = config->wipe_config; @@ -363,9 +376,9 @@ void FffGcodeWriter::setConfigRetractionAndWipe(SliceDataStorage& storage) ExtruderTrain& train = scene.extruders[extruder_index]; retractionAndWipeConfigFromSettings(train.settings, &storage.retraction_wipe_config_per_extruder[extruder_index]); } - for(SliceMeshStorage& mesh: storage.meshes) + for (std::shared_ptr& mesh : storage.meshes) { - retractionAndWipeConfigFromSettings(mesh.settings, &mesh.retraction_wipe_config); + retractionAndWipeConfigFromSettings(mesh->settings, &mesh->retraction_wipe_config); } } @@ -374,24 +387,22 @@ size_t FffGcodeWriter::getStartExtruder(const SliceDataStorage& storage) const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const EPlatformAdhesion adhesion_type = mesh_group_settings.get("adhesion_type"); const int skirt_brim_extruder_nr = mesh_group_settings.get("skirt_brim_extruder_nr"); - const ExtruderTrain* skirt_brim_extruder = (skirt_brim_extruder_nr < 0)? nullptr : &mesh_group_settings.get("skirt_brim_extruder_nr"); + const ExtruderTrain* skirt_brim_extruder = (skirt_brim_extruder_nr < 0) ? nullptr : &mesh_group_settings.get("skirt_brim_extruder_nr"); size_t start_extruder_nr; - if (adhesion_type == EPlatformAdhesion::SKIRT - && skirt_brim_extruder + if (adhesion_type == EPlatformAdhesion::SKIRT && skirt_brim_extruder && (skirt_brim_extruder->settings.get("skirt_line_count") > 0 || skirt_brim_extruder->settings.get("skirt_brim_minimal_length") > 0)) { start_extruder_nr = skirt_brim_extruder->extruder_nr; } - else if ((adhesion_type == EPlatformAdhesion::BRIM || mesh_group_settings.get("prime_tower_brim_enable")) - && skirt_brim_extruder + + else if ( + (adhesion_type == EPlatformAdhesion::BRIM || mesh_group_settings.get("prime_tower_brim_enable")) && skirt_brim_extruder && (skirt_brim_extruder->settings.get("brim_line_count") > 0 || skirt_brim_extruder->settings.get("skirt_brim_minimal_length") > 0)) { start_extruder_nr = skirt_brim_extruder->extruder_nr; } - else if (adhesion_type == EPlatformAdhesion::RAFT - && skirt_brim_extruder - ) + else if (adhesion_type == EPlatformAdhesion::RAFT && skirt_brim_extruder) { start_extruder_nr = mesh_group_settings.get("raft_base_extruder_nr").extruder_nr; } @@ -483,7 +494,8 @@ void FffGcodeWriter::setSupportAngles(SliceDataStorage& storage) storage.support.support_infill_angles_layer_0.push_back(0); } - auto getInterfaceAngles = [&storage](const ExtruderTrain& extruder, const std::string& interface_angles_setting, const EFillMethod pattern, const std::string& interface_height_setting) + auto getInterfaceAngles + = [&storage](const ExtruderTrain& extruder, const std::string& interface_angles_setting, const EFillMethod pattern, const std::string& interface_height_setting) { std::vector angles = extruder.settings.get>(interface_angles_setting); if (angles.empty()) @@ -498,9 +510,10 @@ void FffGcodeWriter::setSupportAngles(SliceDataStorage& storage) } else { - for (const SliceMeshStorage& mesh : storage.meshes) + for (const auto& mesh : storage.meshes) { - if (mesh.settings.get(interface_height_setting) >= 2 * Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height")) + if (mesh->settings.get(interface_height_setting) + >= 2 * Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height")) { // Some roofs are quite thick. // Alternate between the two kinds of diagonal: / and \ . @@ -518,10 +531,12 @@ void FffGcodeWriter::setSupportAngles(SliceDataStorage& storage) }; const ExtruderTrain& roof_extruder = mesh_group_settings.get("support_roof_extruder_nr"); - storage.support.support_roof_angles = getInterfaceAngles(roof_extruder, "support_roof_angles", roof_extruder.settings.get("support_roof_pattern"), "support_roof_height"); + storage.support.support_roof_angles + = getInterfaceAngles(roof_extruder, "support_roof_angles", roof_extruder.settings.get("support_roof_pattern"), "support_roof_height"); const ExtruderTrain& bottom_extruder = mesh_group_settings.get("support_bottom_extruder_nr"); - storage.support.support_bottom_angles = getInterfaceAngles(bottom_extruder, "support_bottom_angles", bottom_extruder.settings.get("support_bottom_pattern"), "support_bottom_height"); + storage.support.support_bottom_angles + = getInterfaceAngles(bottom_extruder, "support_bottom_angles", bottom_extruder.settings.get("support_bottom_pattern"), "support_bottom_height"); } void FffGcodeWriter::processNextMeshGroupCode(const SliceDataStorage& storage) @@ -543,6 +558,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const size_t base_extruder_nr = mesh_group_settings.get("raft_base_extruder_nr").extruder_nr; const size_t interface_extruder_nr = mesh_group_settings.get("raft_interface_extruder_nr").extruder_nr; const size_t surface_extruder_nr = mesh_group_settings.get("raft_surface_extruder_nr").extruder_nr; + const size_t prime_tower_extruder_nr = storage.primeTower.extruder_order.front(); coord_t z = 0; const LayerIndex initial_raft_layer_nr = -Raft::getTotalExtraLayers(); @@ -569,7 +585,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) z += layer_height; const coord_t comb_offset = base_settings.get("raft_base_line_spacing"); - std::vector fan_speed_layer_time_settings_per_extruder_raft_base = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy + std::vector fan_speed_layer_time_settings_per_extruder_raft_base + = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_base) { double regular_fan_speed = base_settings.get("raft_base_fan_speed") * 100.0; @@ -579,11 +596,10 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const coord_t line_width = base_settings.get("raft_base_line_width"); const coord_t avoid_distance = base_settings.get("travel_avoid_distance"); - LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_height, base_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_base, comb_offset, line_width, avoid_distance); + LayerPlan& gcode_layer + = *new LayerPlan(storage, layer_nr, z, layer_height, base_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_base, comb_offset, line_width, avoid_distance); gcode_layer.setIsInside(true); - gcode_layer.setExtruder(base_extruder_nr); - Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_height); Polygons raftLines; @@ -596,6 +612,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const size_t wall_line_count = base_settings.get("raft_base_wall_count"); const coord_t small_area_width = 0; // A raft never has a small region due to the large horizontal expansion. const coord_t line_spacing = base_settings.get("raft_base_line_spacing"); + const coord_t line_spacing_prime_tower = base_settings.get("prime_tower_raft_base_line_spacing"); const Point& infill_origin = Point(); constexpr bool skip_stitching = false; constexpr bool connected_zigzags = false; @@ -606,38 +623,56 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const coord_t max_resolution = base_settings.get("meshfix_maximum_resolution"); const coord_t max_deviation = base_settings.get("meshfix_maximum_deviation"); - std::vector raft_outline_paths; - if (storage.primeRaftOutline.area() > 0) - { - raft_outline_paths.emplace_back(storage.primeRaftOutline); - } - raft_outline_paths.emplace_back(storage.raftOutline); - - for (const Polygons& raft_outline_path : raft_outline_paths) - { - Infill infill_comp(EFillMethod::LINES, - zig_zaggify_infill, - connect_polygons, - raft_outline_path, - gcode_layer.configs_storage.raft_base_config.getLineWidth(), - line_spacing, - fill_overlap, - infill_multiplier, - fill_angle, - z, - extra_infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); + struct ParameterizedRaftPath + { + coord_t line_spacing; + Polygons outline; + }; + + std::vector raft_outline_paths; + raft_outline_paths.emplace_back(ParameterizedRaftPath{ line_spacing, storage.raftOutline }); + if (storage.primeTower.enabled) + { + const Polygons& raft_outline_prime_tower = storage.primeTower.getOuterPoly(layer_nr); + if (line_spacing_prime_tower == line_spacing) + { + // Base layer is shared with prime tower base + raft_outline_paths.front().outline = raft_outline_paths.front().outline.unionPolygons(raft_outline_prime_tower); + } + else + { + // Prime tower has a different line spacing, print them separately + raft_outline_paths.front().outline = raft_outline_paths.front().outline.difference(raft_outline_prime_tower); + raft_outline_paths.emplace_back(ParameterizedRaftPath{ line_spacing_prime_tower, raft_outline_prime_tower }); + } + } + + for (const ParameterizedRaftPath& raft_outline_path : raft_outline_paths) + { + Infill infill_comp( + EFillMethod::LINES, + zig_zaggify_infill, + connect_polygons, + raft_outline_path.outline, + gcode_layer.configs_storage.raft_base_config.getLineWidth(), + raft_outline_path.line_spacing, + fill_overlap, + infill_multiplier, + fill_angle, + z, + extra_infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); std::vector raft_paths; infill_comp.generate(raft_paths, raft_polygons, raftLines, base_settings, layer_nr, SectionType::ADHESION); if (! raft_paths.empty()) @@ -645,7 +680,22 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const GCodePathConfig& config = gcode_layer.configs_storage.raft_base_config; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); InsetOrderOptimizer wall_orderer( - *this, storage, gcode_layer, base_settings, base_extruder_nr, config, config, config, config, retract_before_outer_wall, wipe_dist, wipe_dist, base_extruder_nr, base_extruder_nr, z_seam_config, raft_paths); + *this, + storage, + gcode_layer, + base_settings, + base_extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + base_extruder_nr, + base_extruder_nr, + z_seam_config, + raft_paths); wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer(raftLines, gcode_layer.configs_storage.raft_base_config, SpaceFillType::Lines); @@ -668,10 +718,12 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) for (LayerIndex raft_interface_layer = 1; static_cast(raft_interface_layer) <= num_interface_layers; ++raft_interface_layer) { // raft interface layer + bool prime_tower_added_on_this_layer = ! storage.primeTower.enabled; const LayerIndex layer_nr = initial_raft_layer_nr + raft_interface_layer; z += interface_layer_height; - std::vector fan_speed_layer_time_settings_per_extruder_raft_interface = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy + std::vector fan_speed_layer_time_settings_per_extruder_raft_interface + = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_interface) { const double regular_fan_speed = interface_fan_speed * 100.0; @@ -680,22 +732,37 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) } const coord_t comb_offset = interface_line_spacing; - LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, interface_layer_height, current_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_interface, comb_offset, interface_line_width, interface_avoid_distance); - gcode_layer.setIsInside(true); + LayerPlan& gcode_layer = *new LayerPlan( + storage, + layer_nr, + z, + interface_layer_height, + current_extruder_nr, + fan_speed_layer_time_settings_per_extruder_raft_interface, + comb_offset, + interface_line_width, + interface_avoid_distance); - current_extruder_nr = interface_extruder_nr; - - Application::getInstance().communication->sendLayerComplete(layer_nr, z, interface_layer_height); + if (! prime_tower_added_on_this_layer && current_extruder_nr == prime_tower_extruder_nr) + { + addPrimeTower(storage, gcode_layer, current_extruder_nr); + prime_tower_added_on_this_layer = true; + } - std::vector raft_outline_paths; - const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. - if (storage.primeRaftOutline.area() > 0) + gcode_layer.setIsInside(true); + if (interface_extruder_nr != current_extruder_nr) { - raft_outline_paths.emplace_back(storage.primeRaftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + setExtruder_addPrime(storage, gcode_layer, interface_extruder_nr); + current_extruder_nr = interface_extruder_nr; } - raft_outline_paths.emplace_back(storage.raftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + + Application::getInstance().communication->sendLayerComplete(layer_nr, z, interface_layer_height); + + Polygons raft_outline_path; + const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() + / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. + raft_outline_path = storage.raftOutline.offset(-small_offset); + raft_outline_path = Simplify(interface_settings).polygon(raft_outline_path); // Remove those micron-movements. const coord_t infill_outline_width = gcode_layer.configs_storage.raft_interface_config.getLineWidth(); Polygons raft_lines; AngleDegrees fill_angle = (num_surface_layers + num_interface_layers - raft_interface_layer) % 2 ? 45 : 135; // 90 degrees rotated from the first top layer. @@ -712,37 +779,47 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr int zag_skip_count = 0; constexpr coord_t pocket_size = 0; - for (const Polygons& raft_outline_path : raft_outline_paths) - { - Infill infill_comp(EFillMethod::ZIG_ZAG, - zig_zaggify_infill, - connect_polygons, - raft_outline_path, - infill_outline_width, - interface_line_spacing, - fill_overlap, - infill_multiplier, - fill_angle, - z, - extra_infill_shift, - interface_max_resolution, - interface_max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - std::vector raft_paths; // Should remain empty, since we have no walls. - infill_comp.generate(raft_paths, raft_polygons, raft_lines, interface_settings, layer_nr, SectionType::ADHESION); - gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); - - raft_polygons.clear(); - raft_lines.clear(); + if (storage.primeTower.enabled) + { + // Interface layer excludes prime tower base + raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + } + + Infill infill_comp( + EFillMethod::ZIG_ZAG, + zig_zaggify_infill, + connect_polygons, + raft_outline_path, + infill_outline_width, + interface_line_spacing, + fill_overlap, + infill_multiplier, + fill_angle, + z, + extra_infill_shift, + interface_max_resolution, + interface_max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + std::vector raft_paths; // Should remain empty, since we have no walls. + infill_comp.generate(raft_paths, raft_polygons, raft_lines, interface_settings, layer_nr, SectionType::ADHESION); + gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + + raft_polygons.clear(); + raft_lines.clear(); + + if (! prime_tower_added_on_this_layer) + { + setExtruder_addPrime(storage, gcode_layer, prime_tower_extruder_nr); + current_extruder_nr = prime_tower_extruder_nr; } layer_plan_buffer.handle(gcode_layer, gcode); @@ -759,10 +836,12 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) for (LayerIndex raft_surface_layer = 1; static_cast(raft_surface_layer) <= num_surface_layers; raft_surface_layer++) { // raft surface layers + bool prime_tower_added_on_this_layer = ! storage.primeTower.enabled; const LayerIndex layer_nr = initial_raft_layer_nr + 1 + num_interface_layers + raft_surface_layer - 1; // +1: 1 base layer z += surface_layer_height; - std::vector fan_speed_layer_time_settings_per_extruder_raft_surface = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy + std::vector fan_speed_layer_time_settings_per_extruder_raft_surface + = fan_speed_layer_time_settings_per_extruder; // copy so that we change only the local copy for (FanSpeedLayerTimeSettings& fan_speed_layer_time_settings : fan_speed_layer_time_settings_per_extruder_raft_surface) { const double regular_fan_speed = surface_fan_speed * 100.0; @@ -771,26 +850,42 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) } const coord_t comb_offset = surface_line_spacing; - LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, surface_layer_height, current_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_surface, comb_offset, surface_line_width, surface_avoid_distance); - gcode_layer.setIsInside(true); + LayerPlan& gcode_layer = *new LayerPlan( + storage, + layer_nr, + z, + surface_layer_height, + current_extruder_nr, + fan_speed_layer_time_settings_per_extruder_raft_surface, + comb_offset, + surface_line_width, + surface_avoid_distance); - // make sure that we are using the correct extruder to print raft - current_extruder_nr = surface_extruder_nr; + if (! prime_tower_added_on_this_layer && current_extruder_nr == prime_tower_extruder_nr) + { + addPrimeTower(storage, gcode_layer, current_extruder_nr); + prime_tower_added_on_this_layer = true; + } - Application::getInstance().communication->sendLayerComplete(layer_nr, z, surface_layer_height); + gcode_layer.setIsInside(true); - std::vector raft_outline_paths; - const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. - if (storage.primeRaftOutline.area() > 0) + // make sure that we are using the correct extruder to print raft + if (current_extruder_nr != surface_extruder_nr) { - raft_outline_paths.emplace_back(storage.primeRaftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + setExtruder_addPrime(storage, gcode_layer, surface_extruder_nr); + current_extruder_nr = surface_extruder_nr; } - raft_outline_paths.emplace_back(storage.raftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + Application::getInstance().communication->sendLayerComplete(layer_nr, z, surface_layer_height); + + Polygons raft_outline_path; + const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() + / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. + raft_outline_path = storage.raftOutline.offset(-small_offset); + raft_outline_path = Simplify(interface_settings).polygon(raft_outline_path); // Remove those micron-movements. const coord_t infill_outline_width = gcode_layer.configs_storage.raft_interface_config.getLineWidth(); Polygons raft_lines; - AngleDegrees fill_angle = (num_surface_layers - raft_surface_layer) % 2 ? 45 : 135; // Alternate between -45 and +45 degrees, ending up 90 degrees rotated from the default skin angle. + AngleDegrees fill_angle + = (num_surface_layers - raft_surface_layer) % 2 ? 45 : 135; // Alternate between -45 and +45 degrees, ending up 90 degrees rotated from the default skin angle. constexpr bool zig_zaggify_infill = true; constexpr size_t wall_line_count = 0; @@ -804,46 +899,58 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr size_t zag_skip_count = 0; constexpr coord_t pocket_size = 0; - for (const Polygons& raft_outline_path : raft_outline_paths) - { - Infill infill_comp(EFillMethod::ZIG_ZAG, - zig_zaggify_infill, - connect_polygons, - raft_outline_path, - infill_outline_width, - surface_line_spacing, - fill_overlap, - infill_multiplier, - fill_angle, - z, - extra_infill_shift, - surface_max_resolution, - surface_max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - std::vector raft_paths; // Should remain empty, since we have no walls. - infill_comp.generate(raft_paths, raft_polygons, raft_lines, surface_settings, layer_nr, SectionType::ADHESION); - gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); - - raft_polygons.clear(); - raft_lines.clear(); + if (storage.primeTower.enabled) + { + // Surface layers exclude prime tower base + raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + } + + Infill infill_comp( + EFillMethod::ZIG_ZAG, + zig_zaggify_infill, + connect_polygons, + raft_outline_path, + infill_outline_width, + surface_line_spacing, + fill_overlap, + infill_multiplier, + fill_angle, + z, + extra_infill_shift, + surface_max_resolution, + surface_max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + std::vector raft_paths; // Should remain empty, since we have no walls. + infill_comp.generate(raft_paths, raft_polygons, raft_lines, surface_settings, layer_nr, SectionType::ADHESION); + gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + + raft_polygons.clear(); + raft_lines.clear(); + + if (! prime_tower_added_on_this_layer) + { + setExtruder_addPrime(storage, gcode_layer, prime_tower_extruder_nr); + current_extruder_nr = prime_tower_extruder_nr; } layer_plan_buffer.handle(gcode_layer, gcode); } } -LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const +FffGcodeWriter::ProcessLayerResult FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIndex layer_nr, const size_t total_layers) const { spdlog::debug("GcodeWriter processing layer {} of {}", layer_nr, total_layers); + TimeKeeper time_keeper; + spdlog::stopwatch timer_total; const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; coord_t layer_thickness = mesh_group_settings.get("layer_height"); @@ -860,12 +967,13 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn } else { - z = storage.meshes[0].layers[layer_nr].printZ; // stub default + z = storage.meshes[0]->layers[layer_nr].printZ; // stub default // find printZ of first actual printed mesh - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { - if (layer_nr >= static_cast(mesh.layers.size()) || mesh.settings.get("support_mesh") || mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("cutting_mesh") - || mesh.settings.get("infill_mesh")) + const auto& mesh = *mesh_ptr; + if (layer_nr >= static_cast(mesh.layers.size()) || mesh.settings.get("support_mesh") || mesh.settings.get("anti_overhang_mesh") + || mesh.settings.get("cutting_mesh") || mesh.settings.get("infill_mesh")) { continue; } @@ -898,8 +1006,9 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn } coord_t max_inner_wall_width = 0; - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { + const auto& mesh = *mesh_ptr; coord_t mesh_inner_wall_width = mesh.settings.get((mesh.settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); if (layer_nr == 0) { @@ -910,11 +1019,23 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn } const coord_t comb_offset_from_outlines = max_inner_wall_width * 2; - assert(static_cast(extruder_order_per_layer_negative_layers.size()) + layer_nr >= 0 && "Layer numbers shouldn't get more negative than there are raft/filler layers"); - const std::vector& extruder_order = (layer_nr < 0) ? extruder_order_per_layer_negative_layers[extruder_order_per_layer_negative_layers.size() + layer_nr] : extruder_order_per_layer[layer_nr]; + assert( + static_cast(extruder_order_per_layer_negative_layers.size()) + layer_nr >= 0 && "Layer numbers shouldn't get more negative than there are raft/filler layers"); + const std::vector& extruder_order + = (layer_nr < 0) ? extruder_order_per_layer_negative_layers[extruder_order_per_layer_negative_layers.size() + layer_nr] : extruder_order_per_layer[layer_nr]; const coord_t first_outer_wall_line_width = scene.extruders[extruder_order.front()].settings.get("wall_line_width_0"); - LayerPlan& gcode_layer = *new LayerPlan(storage, layer_nr, z, layer_thickness, extruder_order.front(), fan_speed_layer_time_settings_per_extruder, comb_offset_from_outlines, first_outer_wall_line_width, avoid_distance); + LayerPlan& gcode_layer = *new LayerPlan( + storage, + layer_nr, + z, + layer_thickness, + extruder_order.front(), + fan_speed_layer_time_settings_per_extruder, + comb_offset_from_outlines, + first_outer_wall_line_width, + avoid_distance); + time_keeper.registerTime("Init"); if (include_helper_parts) { @@ -923,16 +1044,21 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn if (storage.skirt_brim[extruder_nr].size() > 0) { processSkirtBrim(storage, gcode_layer, extruder_nr, layer_nr); + time_keeper.registerTime("Skirt/brim"); } // handle shield(s) first in a layer so that chances are higher that the other nozzle is wiped (for the ooze shield) processOozeShield(storage, gcode_layer); + time_keeper.registerTime("Ooze shield"); + processDraftShield(storage, gcode_layer); + time_keeper.registerTime("Draft shield"); } const size_t support_roof_extruder_nr = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; const size_t support_bottom_extruder_nr = mesh_group_settings.get("support_bottom_extruder_nr").extruder_nr; - const size_t support_infill_extruder_nr = (layer_nr <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; + const size_t support_infill_extruder_nr = (layer_nr <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr + : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; for (const size_t& extruder_nr : extruder_order) { @@ -944,44 +1070,54 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn // later in the print a prime tower is needed. // - prime tower is already printed this layer (only applicable for more than 2 extruders). // The setExtruder_addPrime takes care of this. - if (extruder_nr != extruder_order.front() || extruder_order.size() == 1) + if (extruder_nr != extruder_order.front() || (extruder_order.size() == 1 && layer_nr >= 0) || extruder_nr == 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Prime tower pre"); } if (include_helper_parts && (extruder_nr == support_infill_extruder_nr || extruder_nr == support_roof_extruder_nr || extruder_nr == support_bottom_extruder_nr)) { addSupportToGCode(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Supports"); } if (layer_nr >= 0) { const std::vector& mesh_order = mesh_order_per_extruder[extruder_nr]; for (size_t mesh_idx : mesh_order) { - const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; - const PathConfigStorage::MeshPathConfigs& mesh_config = gcode_layer.configs_storage.mesh_configs[mesh_idx]; - if (mesh.settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE - && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr // mesh surface mode should always only be printed with the outer wall extruder! + const std::shared_ptr& mesh = storage.meshes[mesh_idx]; + const MeshPathConfigs& mesh_config = gcode_layer.configs_storage.mesh_configs[mesh_idx]; + if (mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE + && extruder_nr + == mesh->settings.get("wall_0_extruder_nr").extruder_nr // mesh surface mode should always only be printed with the outer wall extruder! ) { - addMeshLayerToGCode_meshSurfaceMode(storage, mesh, mesh_config, gcode_layer); + addMeshLayerToGCode_meshSurfaceMode(storage, *mesh, mesh_config, gcode_layer); } else { addMeshLayerToGCode(storage, mesh, extruder_nr, mesh_config, gcode_layer); } + time_keeper.registerTime(fmt::format("Mesh {}", mesh_idx)); } } // Always print a prime tower before switching extruder. Unless: // - The prime tower is already printed this layer (setExtruder_addPrime takes care of this). // - this is the last extruder of the layer, since the next layer will start with the same extruder. - if (extruder_nr != extruder_order.back()) + if (extruder_nr != extruder_order.back() && layer_nr >= 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); + time_keeper.registerTime("Prime tower post"); } } + gcode_layer.applyModifyPlugin(); + time_keeper.registerTime("Modify plugin"); + gcode_layer.applyBackPressureCompensation(); - return gcode_layer; + time_keeper.registerTime("Back pressure comp."); + + return { &gcode_layer, timer_total.elapsed().count(), time_keeper.getRegisteredTimes() }; } bool FffGcodeWriter::getExtruderNeedPrimeBlobDuringFirstLayer(const SliceDataStorage& storage, const size_t extruder_nr) const @@ -1006,7 +1142,7 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan const int skirt_height = train.settings.get("skirt_height"); const bool is_skirt = train.settings.get("adhesion_type") == EPlatformAdhesion::SKIRT; // only create a multilayer SkirtBrim for a skirt for the height of skirt_height - if (layer_nr != 0 && (layer_nr >= skirt_height || !is_skirt)) + if (layer_nr != 0 && (layer_nr >= skirt_height || ! is_skirt)) { return; } @@ -1015,7 +1151,7 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan return; } gcode_layer.setSkirtBrimIsPlanned(extruder_nr); - + const auto& original_skirt_brim = storage.skirt_brim[extruder_nr]; if (original_skirt_brim.size() == 0) { @@ -1041,7 +1177,7 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan const size_t inset_idx; ConstPolygonPointer poly; }; - + size_t total_line_count = 0; for (const SkirtBrimLine& line : storage.skirt_brim[extruder_nr]) { @@ -1049,18 +1185,8 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan total_line_count += line.open_polylines.size(); } Polygons all_brim_lines; - - // Add the support brim before the below algorithm which takes order requirements into account - // For support brim we don't care about the order, because support doesn't need to be accurate. - const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - if (extruder_nr == mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr) - { - total_line_count += storage.support_brim.size(); - Polygons support_brim_lines = storage.support_brim; - support_brim_lines.toPolylines(); - all_brim_lines = support_brim_lines; - } - + + all_brim_lines.reserve(total_line_count); const coord_t line_w = train.settings.get("skirt_brim_line_width") * train.settings.get("initial_layer_line_width_factor"); @@ -1090,13 +1216,13 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan ConstPolygonPointer pp(all_brim_lines.back()); for (Point p : line) { - grid.insert(p, BrimLineReference{inset_idx, pp}); + grid.insert(p, BrimLineReference{ inset_idx, pp }); } } } } - const auto smart_brim_ordering = train.settings.get("brim_smart_ordering"); + const auto smart_brim_ordering = train.settings.get("brim_smart_ordering") && train.settings.get("adhesion_type") == EPlatformAdhesion::BRIM; std::unordered_multimap order_requirements; for (const std::pair>& p : grid) { @@ -1144,35 +1270,59 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan } } assert(all_brim_lines.size() == total_line_count); // Otherwise pointers would have gotten invalidated - + const bool enable_travel_optimization = true; // Use the combing outline while deciding in which order to print the lines. Can't hurt for only one layer. const coord_t wipe_dist = 0u; const Ratio flow_ratio = 1.0; const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; const bool reverse_print_direction = false; - // For layer_nr != 0 add only the innermost brim line (which is only the case if skirt_height > 1) - Polygons inner_brim_line; - inner_brim_line.add(all_brim_lines[0]); - - gcode_layer.addLinesByOptimizer - ( - layer_nr == 0 ? all_brim_lines : inner_brim_line, - gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], - SpaceFillType::PolyLines, - enable_travel_optimization, - wipe_dist, - flow_ratio, - start_close_to, - fan_speed, - reverse_print_direction, - order_requirements - ); + if (! all_brim_lines.empty()) + { + // For layer_nr != 0 add only the innermost brim line (which is only the case if skirt_height > 1) + Polygons inner_brim_line; + inner_brim_line.add(all_brim_lines[0]); + + gcode_layer.addLinesByOptimizer( + layer_nr == 0 ? all_brim_lines : inner_brim_line, + gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], + SpaceFillType::PolyLines, + enable_travel_optimization, + wipe_dist, + flow_ratio, + start_close_to, + fan_speed, + reverse_print_direction, + order_requirements); + } + + + // Add the support brim after the skirt_brim to gcode_layer + // Support brim is only added in layer 0 + // For support brim we don't care about the order, because support doesn't need to be accurate. + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + if ((layer_nr == 0) && (extruder_nr == mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr)) + { + total_line_count += storage.support_brim.size(); + Polygons support_brim_lines = storage.support_brim; + support_brim_lines.toPolylines(); + gcode_layer.addLinesByOptimizer( + support_brim_lines, + gcode_layer.configs_storage.skirt_brim_config_per_extruder[extruder_nr], + SpaceFillType::PolyLines, + enable_travel_optimization, + wipe_dist, + flow_ratio, + start_close_to, + fan_speed, + reverse_print_direction, + order_requirements = {}); + } } void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { - unsigned int layer_nr = std::max(0, gcode_layer.getLayerNr()); + LayerIndex layer_nr = std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr()); if (layer_nr == 0 && Application::getInstance().current_slice->scene.current_mesh_group->settings.get("adhesion_type") == EPlatformAdhesion::BRIM) { return; // ooze shield already generated by brim @@ -1186,7 +1336,7 @@ void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPla void FffGcodeWriter::processDraftShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const LayerIndex layer_nr = std::max(0, gcode_layer.getLayerNr()); + const LayerIndex layer_nr = std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr()); if (storage.draft_protection_shield.size() == 0) { return; @@ -1261,7 +1411,7 @@ std::vector FffGcodeWriter::getUsedExtrudersOnLayerExcludingStartingExtr std::vector extruder_is_used_on_this_layer = storage.getExtrudersUsed(layer_nr); // The outermost prime tower extruder is always used if there is a prime tower, apart on layers with negative index (e.g. for the raft) - if (mesh_group_settings.get("prime_tower_enable") && layer_nr >= 0 && layer_nr <= storage.max_print_height_second_to_last_extruder) + if (mesh_group_settings.get("prime_tower_enable") && /*layer_nr >= 0 &&*/ layer_nr <= storage.max_print_height_second_to_last_extruder) { extruder_is_used_on_this_layer[storage.primeTower.extruder_order[0]] = true; } @@ -1303,7 +1453,7 @@ std::vector FffGcodeWriter::calculateMeshOrder(const SliceDataStorage& s std::vector::iterator mesh_group = Application::getInstance().current_slice->scene.current_mesh_group; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; if (mesh.getExtruderIsUsed(extruder_nr)) { const Mesh& mesh_data = mesh_group->meshes[mesh_idx]; @@ -1326,7 +1476,8 @@ std::vector FffGcodeWriter::calculateMeshOrder(const SliceDataStorage& s return ret; } -void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const +void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) + const { if (gcode_layer.getLayerNr() > mesh.layer_nr_max_filled_layer) { @@ -1349,22 +1500,32 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceDataStorage& polygons = Simplify(mesh.settings).polygon(polygons); - ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); + ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + mesh.settings.get("wall_line_width_0") * 2); const bool spiralize = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("magic_spiralize"); gcode_layer.addPolygonsByOptimizer(polygons, mesh_config.inset0_config, z_seam_config, mesh.settings.get("wall_0_wipe_dist"), spiralize); addMeshOpenPolyLinesToGCode(mesh, mesh_config, gcode_layer); } -void FffGcodeWriter::addMeshOpenPolyLinesToGCode(const SliceMeshStorage& mesh, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const +void FffGcodeWriter::addMeshOpenPolyLinesToGCode(const SliceMeshStorage& mesh, const MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const { const SliceLayer* layer = &mesh.layers[gcode_layer.getLayerNr()]; gcode_layer.addLinesByOptimizer(layer->openPolyLines, mesh_config.inset0_config, SpaceFillType::PolyLines); } -void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const +void FffGcodeWriter::addMeshLayerToGCode( + const SliceDataStorage& storage, + const std::shared_ptr& mesh_ptr, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + LayerPlan& gcode_layer) const { + const auto& mesh = *mesh_ptr; if (gcode_layer.getLayerNr() > mesh.layer_nr_max_filled_layer) { return; @@ -1382,19 +1543,23 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const return; } - gcode_layer.setMesh(&mesh); + gcode_layer.setMesh(mesh_ptr); ZSeamConfig z_seam_config; if (mesh.isPrinted()) //"normal" meshes with walls, skin, infill, etc. get the traditional part ordering based on the z-seam settings. { - z_seam_config = ZSeamConfig(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); + z_seam_config = ZSeamConfig( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + mesh.settings.get("wall_line_width_0") * 2); } PathOrderOptimizer part_order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config); for (const SliceLayerPart& part : layer.parts) { part_order_optimizer.addPolygon(&part); } - part_order_optimizer.optimize(); + part_order_optimizer.optimize(false); for (const PathOrdering& path : part_order_optimizer.paths) { addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer); @@ -1412,8 +1577,13 @@ void FffGcodeWriter::addMeshLayerToGCode(const SliceDataStorage& storage, const gcode_layer.setMesh(nullptr); } -void FffGcodeWriter::addMeshPartToGCode(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part, LayerPlan& gcode_layer) - const +void FffGcodeWriter::addMeshPartToGCode( + const SliceDataStorage& storage, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part, + LayerPlan& gcode_layer) const { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; @@ -1434,7 +1604,8 @@ void FffGcodeWriter::addMeshPartToGCode(const SliceDataStorage& storage, const S added_something = added_something | processSkin(storage, gcode_layer, mesh, extruder_nr, mesh_config, part); // After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. - if (added_something && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < static_cast(mesh.settings.get("initial_bottom_layers")))) + if (added_something + && (! mesh_group_settings.get("magic_spiralize") || gcode_layer.getLayerNr() < static_cast(mesh.settings.get("initial_bottom_layers")))) { coord_t innermost_wall_line_width = mesh.settings.get((mesh.settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); if (gcode_layer.getLayerNr() == 0) @@ -1447,7 +1618,13 @@ void FffGcodeWriter::addMeshPartToGCode(const SliceDataStorage& storage, const S gcode_layer.setIsInside(false); } -bool FffGcodeWriter::processInfill(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const +bool FffGcodeWriter::processInfill( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) { @@ -1458,8 +1635,13 @@ bool FffGcodeWriter::processInfill(const SliceDataStorage& storage, LayerPlan& g return added_something; } -bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) - const +bool FffGcodeWriter::processMultiLayerInfill( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) { @@ -1475,7 +1657,8 @@ bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, La AngleDegrees infill_angle = 45; // Original default. This will get updated to an element from mesh->infill_angles. if (! mesh.infill_angles.empty()) { - const size_t combined_infill_layers = std::max(uint64_t(1), round_divide(mesh.settings.get("infill_sparse_thickness"), std::max(mesh.settings.get("layer_height"), coord_t(1)))); + const size_t combined_infill_layers + = std::max(uint64_t(1), round_divide(mesh.settings.get("infill_sparse_thickness"), std::max(mesh.settings.get("layer_height"), coord_t(1)))); infill_angle = mesh.infill_angles.at((gcode_layer.getLayerNr() / combined_infill_layers) % mesh.infill_angles.size()); } const Point3 mesh_middle = mesh.bounding_box.getMiddle(); @@ -1504,7 +1687,7 @@ bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, La } constexpr size_t wall_line_count = 0; // wall toolpaths are when gradual infill areas are determined - const coord_t small_area_width = mesh.settings.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const coord_t small_area_width = 0; constexpr coord_t infill_overlap = 0; // Overlap is handled when the wall toolpaths are generated constexpr bool skip_stitching = false; constexpr bool connected_zigzags = false; @@ -1513,35 +1696,45 @@ bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, La constexpr size_t zag_skip_count = 0; const bool fill_gaps = density_idx == 0; // Only fill gaps for the lowest density. - const LightningLayer* lightning_layer = nullptr; + std::shared_ptr lightning_layer = nullptr; if (mesh.lightning_generator) { - lightning_layer = &mesh.lightning_generator->getTreesForLayer(gcode_layer.getLayerNr()); + lightning_layer = std::make_shared(mesh.lightning_generator->getTreesForLayer(gcode_layer.getLayerNr())); } - Infill infill_comp(infill_pattern, - zig_zaggify_infill, - connect_polygons, - part.infill_area_per_combine_per_density[density_idx][combine_idx], - infill_line_width, - infill_line_distance_here, - infill_overlap, - infill_multiplier, - infill_angle, - gcode_layer.z, - infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - mesh.settings.get("cross_infill_pocket_size")); - infill_comp.generate(infill_paths, infill_polygons, infill_lines, mesh.settings, gcode_layer.getLayerNr(), SectionType::INFILL, mesh.cross_fill_provider, lightning_layer, &mesh); + Infill infill_comp( + infill_pattern, + zig_zaggify_infill, + connect_polygons, + part.infill_area_per_combine_per_density[density_idx][combine_idx], + infill_line_width, + infill_line_distance_here, + infill_overlap, + infill_multiplier, + infill_angle, + gcode_layer.z, + infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + mesh.settings.get("cross_infill_pocket_size")); + infill_comp.generate( + infill_paths, + infill_polygons, + infill_lines, + mesh.settings, + gcode_layer.getLayerNr(), + SectionType::INFILL, + mesh.cross_fill_provider, + lightning_layer, + &mesh); } if (! infill_lines.empty() || ! infill_polygons.empty()) { @@ -1565,25 +1758,27 @@ bool FffGcodeWriter::processMultiLayerInfill(const SliceDataStorage& storage, La } const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); - gcode_layer.addLinesByOptimizer(infill_lines, - mesh_config.infill_config[combine_idx], - zig_zaggify_infill ? SpaceFillType::PolyLines : SpaceFillType::Lines, - enable_travel_optimization, - /*wipe_dist = */ 0, - /* flow = */ 1.0, - near_start_location); + gcode_layer.addLinesByOptimizer( + infill_lines, + mesh_config.infill_config[combine_idx], + zig_zaggify_infill ? SpaceFillType::PolyLines : SpaceFillType::Lines, + enable_travel_optimization, + /*wipe_dist = */ 0, + /* flow = */ 1.0, + near_start_location); } } } return added_something; } -bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const SliceMeshStorage& mesh, - const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, - const SliceLayerPart& part) const +bool FffGcodeWriter::processSingleLayerInfill( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) { @@ -1614,7 +1809,8 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, AngleDegrees infill_angle = 45; // Original default. This will get updated to an element from mesh->infill_angles. if (! mesh.infill_angles.empty()) { - const size_t combined_infill_layers = std::max(uint64_t(1), round_divide(mesh.settings.get("infill_sparse_thickness"), std::max(mesh.settings.get("layer_height"), coord_t(1)))); + const size_t combined_infill_layers + = std::max(uint64_t(1), round_divide(mesh.settings.get("infill_sparse_thickness"), std::max(mesh.settings.get("layer_height"), coord_t(1)))); infill_angle = mesh.infill_angles.at((static_cast(gcode_layer.getLayerNr()) / combined_infill_layers) % mesh.infill_angles.size()); } const Point3 mesh_middle = mesh.bounding_box.getMiddle(); @@ -1696,10 +1892,10 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, Polygons in_outline = part.infill_area_per_combine_per_density[density_idx][0]; - const LightningLayer* lightning_layer = nullptr; + std::shared_ptr lightning_layer; if (mesh.lightning_generator) { - lightning_layer = &mesh.lightning_generator->getTreesForLayer(gcode_layer.getLayerNr()); + lightning_layer = std::make_shared(mesh.lightning_generator->getTreesForLayer(gcode_layer.getLayerNr())); } const bool fill_gaps = density_idx == 0; // Only fill gaps in the lowest infill density pattern. @@ -1708,33 +1904,43 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, // infill region with skin above has to have at least one infill wall line const size_t min_skin_below_wall_count = wall_line_count > 0 ? wall_line_count : 1; const size_t skin_below_wall_count = density_idx == last_idx ? min_skin_below_wall_count : 0; - const coord_t small_area_width = mesh.settings.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const coord_t small_area_width = 0; wall_tool_paths.emplace_back(std::vector()); const coord_t overlap = infill_overlap - (density_idx == last_idx ? 0 : wall_line_count * infill_line_width); - Infill infill_comp(pattern, - zig_zaggify_infill, - connect_polygons, - infill_below_skin, - infill_line_width, - infill_line_distance_here, - overlap, - infill_multiplier, - infill_angle, - gcode_layer.z, - infill_shift, - max_resolution, - max_deviation, - skin_below_wall_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - infill_comp.generate(wall_tool_paths.back(), infill_polygons, infill_lines, mesh.settings, gcode_layer.getLayerNr(), SectionType::INFILL, mesh.cross_fill_provider, lightning_layer, &mesh); + Infill infill_comp( + pattern, + zig_zaggify_infill, + connect_polygons, + infill_below_skin, + infill_line_width, + infill_line_distance_here, + overlap, + infill_multiplier, + infill_angle, + gcode_layer.z, + infill_shift, + max_resolution, + max_deviation, + skin_below_wall_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + wall_tool_paths.back(), + infill_polygons, + infill_lines, + mesh.settings, + gcode_layer.getLayerNr(), + SectionType::INFILL, + mesh.cross_fill_provider, + lightning_layer, + &mesh); if (density_idx < last_idx) { const coord_t cut_offset = get_cut_offset(zig_zaggify_infill, infill_line_width, min_skin_below_wall_count); @@ -1762,34 +1968,44 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, in_outline.removeSmallAreas(minimum_small_area); constexpr size_t wall_line_count_here = 0; // Wall toolpaths were generated in generateGradualInfill for the sparsest density, denser parts don't have walls by default - const coord_t small_area_width = mesh.settings.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const coord_t small_area_width = 0; constexpr coord_t overlap = 0; // overlap is already applied for the sparsest density in the generateGradualInfill wall_tool_paths.emplace_back(); - Infill infill_comp(pattern, - zig_zaggify_infill, - connect_polygons, - in_outline, - infill_line_width, - infill_line_distance_here, - overlap, - infill_multiplier, - infill_angle, - gcode_layer.z, - infill_shift, - max_resolution, - max_deviation, - wall_line_count_here, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - infill_comp.generate(wall_tool_paths.back(), infill_polygons, infill_lines, mesh.settings, gcode_layer.getLayerNr(), SectionType::INFILL, mesh.cross_fill_provider, lightning_layer, &mesh); + Infill infill_comp( + pattern, + zig_zaggify_infill, + connect_polygons, + in_outline, + infill_line_width, + infill_line_distance_here, + overlap, + infill_multiplier, + infill_angle, + gcode_layer.z, + infill_shift, + max_resolution, + max_deviation, + wall_line_count_here, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + wall_tool_paths.back(), + infill_polygons, + infill_lines, + mesh.settings, + gcode_layer.getLayerNr(), + SectionType::INFILL, + mesh.cross_fill_provider, + lightning_layer, + &mesh); if (density_idx < last_idx) { const coord_t cut_offset = get_cut_offset(zig_zaggify_infill, infill_line_width, wall_line_count); @@ -1801,8 +2017,21 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, } wall_tool_paths.emplace_back(part.infill_wall_toolpaths); // The extra infill walls were generated separately. Add these too. - const bool walls_generated = - std::any_of(wall_tool_paths.cbegin(), wall_tool_paths.cend(), [](const std::vector& tp) { return ! (tp.empty() || std::all_of(tp.begin(), tp.end(), [](const VariableWidthLines& vwl) { return vwl.empty(); })); }); + const bool walls_generated = std::any_of( + wall_tool_paths.cbegin(), + wall_tool_paths.cend(), + [](const std::vector& tp) + { + return ! ( + tp.empty() + || std::all_of( + tp.begin(), + tp.end(), + [](const VariableWidthLines& vwl) + { + return vwl.empty(); + })); + }); if (! infill_lines.empty() || ! infill_polygons.empty() || walls_generated) { added_something = true; @@ -1823,7 +2052,8 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, else // So walls_generated must be true. { std::vector* start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()]; - while (start_paths->empty() || (*start_paths)[0].empty()) // We know for sure (because walls_generated) that one of them is not empty. So randomise until we hit it. Should almost always be very quick. + while (start_paths->empty() || (*start_paths)[0].empty()) // We know for sure (because walls_generated) that one of them is not empty. So randomise until we hit + // it. Should almost always be very quick. { start_paths = &wall_tool_paths[rand() % wall_tool_paths.size()]; } @@ -1836,23 +2066,28 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, { constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; - const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh_config.infill_config[0].getLineWidth() * 2); - InsetOrderOptimizer wall_orderer(*this, - storage, - gcode_layer, - mesh.settings, - extruder_nr, - mesh_config.infill_config[0], - mesh_config.infill_config[0], - mesh_config.infill_config[0], - mesh_config.infill_config[0], - retract_before_outer_wall, - wipe_dist, - wipe_dist, - extruder_nr, - extruder_nr, - z_seam_config, - tool_paths); + const ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + mesh_config.infill_config[0].getLineWidth() * 2); + InsetOrderOptimizer wall_orderer( + *this, + storage, + gcode_layer, + mesh.settings, + extruder_nr, + mesh_config.infill_config[0], + mesh_config.infill_config[0], + mesh_config.infill_config[0], + mesh_config.infill_config[0], + retract_before_outer_wall, + wipe_dist, + wipe_dist, + extruder_nr, + extruder_nr, + z_seam_config, + tool_paths); added_something |= wall_orderer.addToLayer(); } } @@ -1864,21 +2099,40 @@ bool FffGcodeWriter::processSingleLayerInfill(const SliceDataStorage& storage, gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); } const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); - if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC - || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) + if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC + || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) { - gcode_layer.addLinesByOptimizer(infill_lines, mesh_config.infill_config[0], SpaceFillType::Lines, enable_travel_optimization, mesh.settings.get("infill_wipe_dist"), /*float_ratio = */ 1.0, near_start_location); + gcode_layer.addLinesByOptimizer( + infill_lines, + mesh_config.infill_config[0], + SpaceFillType::Lines, + enable_travel_optimization, + mesh.settings.get("infill_wipe_dist"), + /*float_ratio = */ 1.0, + near_start_location); } else { gcode_layer.addLinesByOptimizer( - infill_lines, mesh_config.infill_config[0], (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, enable_travel_optimization, /* wipe_dist = */ 0, /*float_ratio = */ 1.0, near_start_location); + infill_lines, + mesh_config.infill_config[0], + (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, + enable_travel_optimization, + /* wipe_dist = */ 0, + /*float_ratio = */ 1.0, + near_start_location); } } return added_something; } -bool FffGcodeWriter::partitionInfillBySkinAbove(Polygons& infill_below_skin, Polygons& infill_not_below_skin, const LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const SliceLayerPart& part, coord_t infill_line_width) +bool FffGcodeWriter::partitionInfillBySkinAbove( + Polygons& infill_below_skin, + Polygons& infill_not_below_skin, + const LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const SliceLayerPart& part, + coord_t infill_line_width) { constexpr coord_t tiny_infill_offset = 20; const auto skin_edge_support_layers = mesh.settings.get("skin_edge_support_layers"); @@ -1931,8 +2185,8 @@ bool FffGcodeWriter::partitionInfillBySkinAbove(Polygons& infill_below_skin, Pol } else // this layer is the 1st layer above the layer whose infill we're printing { - // add this layer's skin region without subtracting the overlap but still make a gap between this skin region and what has been accumulated so far - // we do this so that these skin region edges will definitely have infill walls below them + // add this layer's skin region without subtracting the overlap but still make a gap between this skin region and what has been accumulated so + // far we do this so that these skin region edges will definitely have infill walls below them // looking from the side, if the combined regions so far look like this... // @@ -1963,7 +2217,8 @@ bool FffGcodeWriter::partitionInfillBySkinAbove(Polygons& infill_below_skin, Pol } } - // the shrink/expand here is to remove regions of infill below skin that are narrower than the width of the infill walls otherwise the infill walls could merge and form a bump + // the shrink/expand here is to remove regions of infill below skin that are narrower than the width of the infill walls otherwise the infill walls could merge and form + // a bump infill_below_skin = skin_above_combined.intersection(part.infill_area_per_combine_per_density.back().front()).offset(-infill_line_width).offset(infill_line_width); constexpr bool remove_small_holes_from_infill_below_skin = true; @@ -1983,7 +2238,12 @@ bool FffGcodeWriter::partitionInfillBySkinAbove(Polygons& infill_below_skin, Pol return ! infill_below_skin_overlap.empty() && ! infill_not_below_skin.empty(); } -void FffGcodeWriter::processSpiralizedWall(const SliceDataStorage& storage, LayerPlan& gcode_layer, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part, const SliceMeshStorage& mesh) const +void FffGcodeWriter::processSpiralizedWall( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part, + const SliceMeshStorage& mesh) const { if (part.spiral_wall.empty()) { @@ -2009,11 +2269,18 @@ void FffGcodeWriter::processSpiralizedWall(const SliceDataStorage& storage, Laye // output a wall slice that is interpolated between the last and current walls for (const ConstPolygonRef& wall_outline : part.spiral_wall) { - gcode_layer.spiralizeWallSlice(mesh_config.inset0_config, wall_outline, ConstPolygonRef(*last_wall_outline), seam_vertex_idx, last_seam_vertex_idx, is_top_layer, is_bottom_layer); + gcode_layer + .spiralizeWallSlice(mesh_config.inset0_config, wall_outline, ConstPolygonRef(*last_wall_outline), seam_vertex_idx, last_seam_vertex_idx, is_top_layer, is_bottom_layer); } } -bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const +bool FffGcodeWriter::processInsets( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const { bool added_something = false; if (extruder_nr != mesh.settings.get("wall_0_extruder_nr").extruder_nr && extruder_nr != mesh.settings.get("wall_x_extruder_nr").extruder_nr) @@ -2030,7 +2297,8 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g { const size_t initial_bottom_layers = mesh.settings.get("initial_bottom_layers"); const int layer_nr = gcode_layer.getLayerNr(); - if ((layer_nr < static_cast(initial_bottom_layers) && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths + if ((layer_nr < static_cast(initial_bottom_layers) + && part.wall_toolpaths.empty()) // The bottom layers in spiralize mode are generated using the variable width paths || (layer_nr >= static_cast(initial_bottom_layers) && part.spiral_wall.empty())) // The rest of the layers in spiralize mode are using the spiral wall { // nothing to do @@ -2040,7 +2308,8 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g { spiralize = true; } - if (spiralize && gcode_layer.getLayerNr() == static_cast(initial_bottom_layers) && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr) + if (spiralize && gcode_layer.getLayerNr() == static_cast(initial_bottom_layers) + && extruder_nr == mesh.settings.get("wall_0_extruder_nr").extruder_nr) { // on the last normal layer first make the outer wall normally and then start a second outer wall from the same hight, but gradually moving upward added_something = true; gcode_layer.setIsInside(true); // going to print stuff inside print object @@ -2062,8 +2331,9 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g Polygons outlines_below; AABB boundaryBox(part.outline); - for (const SliceMeshStorage& m : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { + const auto& m = *mesh_ptr; if (m.isPrinted()) { for (const SliceLayerPart& prevLayerPart : m.layers[gcode_layer.getLayerNr() - 1].parts) @@ -2084,7 +2354,7 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g if (mesh_group_settings.get("support_enable")) { const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; const int support_layer_nr = gcode_layer.getLayerNr() - z_distance_top_layers; if (support_layer_nr > 0) @@ -2188,25 +2458,125 @@ bool FffGcodeWriter::processInsets(const SliceDataStorage& storage, LayerPlan& g } else { + // for layers that (partially) do not have any layers above we apply the roofing configuration + auto use_roofing_config = [&part, &mesh, &gcode_layer]() + { + const auto getOutlineOnLayer = [mesh](const SliceLayerPart& part_here, const LayerIndex layer2_nr) -> Polygons + { + Polygons result; + if (layer2_nr >= static_cast(mesh.layers.size())) + { + return result; + } + const SliceLayer& layer2 = mesh.layers[layer2_nr]; + for (const SliceLayerPart& part2 : layer2.parts) + { + if (part_here.boundaryBox.hit(part2.boundaryBox)) + { + result.add(part2.outline); + } + } + return result; + }; + + const auto filled_area_above = [&getOutlineOnLayer, &part, &mesh, &gcode_layer]() -> Polygons + { + const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); + const bool no_small_gaps_heuristic = mesh.settings.get("skin_no_small_gaps_heuristic"); + const int layer_nr = gcode_layer.getLayerNr(); + auto filled_area_above = getOutlineOnLayer(part, layer_nr + roofing_layer_count); + if (! no_small_gaps_heuristic) + { + for (int layer_nr_above = layer_nr + 1; layer_nr_above < layer_nr + roofing_layer_count; layer_nr_above++) + { + Polygons outlines_above = getOutlineOnLayer(part, layer_nr_above); + filled_area_above = filled_area_above.intersection(outlines_above); + } + } + if (layer_nr > 0) + { + // if the skin has air below it then cutting it into regions could cause a region + // to be wholely or partly above air and it may not be printable so restrict + // the regions that have air above (the visible regions) to not include any area that + // has air below (fixes https://github.com/Ultimaker/Cura/issues/2656) + + // set air_below to the skin area for the current layer that has air below it + Polygons air_below = getOutlineOnLayer(part, layer_nr).difference(getOutlineOnLayer(part, layer_nr - 1)); + + if (! air_below.empty()) + { + // add the polygons that have air below to the no air above polygons + filled_area_above = filled_area_above.unionPolygons(air_below); + } + } + + return filled_area_above; + }(); + + if (filled_area_above.empty()) + { + return true; + } + + const auto point_view = ranges::views::transform( + [](auto extrusion_junction) + { + return extrusion_junction.p; + }); + + for (const auto& path : part.wall_toolpaths) + { + for (const auto& wall : path) + { + for (const auto& p : wall | point_view) + { + if (! filled_area_above.inside(p)) + { + return true; + } + } + + for (const auto& window : wall | point_view | ranges::views::sliding(2)) + { + auto p0 = window[0]; + auto p1 = window[1]; + if (PolygonUtils::polygonCollidesWithLineSegment(filled_area_above, p0, p1)) + { + return true; + } + } + } + } + return false; + }(); + + const GCodePathConfig& inset0_config = use_roofing_config ? mesh_config.inset0_roofing_config : mesh_config.inset0_config; + const GCodePathConfig& insetX_config = use_roofing_config ? mesh_config.insetX_roofing_config : mesh_config.insetX_config; + // Main case: Optimize the insets with the InsetOrderOptimizer. const coord_t wall_x_wipe_dist = 0; - const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); - InsetOrderOptimizer wall_orderer(*this, - storage, - gcode_layer, - mesh.settings, - extruder_nr, - mesh_config.inset0_config, - mesh_config.insetX_config, - mesh_config.bridge_inset0_config, - mesh_config.bridge_insetX_config, - mesh.settings.get("travel_retract_before_outer_wall"), - mesh.settings.get("wall_0_wipe_dist"), - wall_x_wipe_dist, - mesh.settings.get("wall_0_extruder_nr").extruder_nr, - mesh.settings.get("wall_x_extruder_nr").extruder_nr, - z_seam_config, - part.wall_toolpaths); + const ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + mesh.settings.get("wall_line_width_0") * 2); + InsetOrderOptimizer wall_orderer( + *this, + storage, + gcode_layer, + mesh.settings, + extruder_nr, + inset0_config, + insetX_config, + mesh_config.bridge_inset0_config, + mesh_config.bridge_insetX_config, + mesh.settings.get("travel_retract_before_outer_wall"), + mesh.settings.get("wall_0_wipe_dist"), + wall_x_wipe_dist, + mesh.settings.get("wall_0_extruder_nr").extruder_nr, + mesh.settings.get("wall_x_extruder_nr").extruder_nr, + z_seam_config, + part.wall_toolpaths); added_something |= wall_orderer.addToLayer(); } return added_something; @@ -2245,7 +2615,13 @@ std::optional FffGcodeWriter::getSeamAvoidingLocation(const Polygons& fil } } -bool FffGcodeWriter::processSkin(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SliceLayerPart& part) const +bool FffGcodeWriter::processSkin( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SliceLayerPart& part) const { const size_t top_bottom_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; const size_t roofing_extruder_nr = mesh.settings.get("roofing_extruder_nr").extruder_nr; @@ -2274,7 +2650,13 @@ bool FffGcodeWriter::processSkin(const SliceDataStorage& storage, LayerPlan& gco return added_something; } -bool FffGcodeWriter::processSkinPart(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SkinPart& skin_part) const +bool FffGcodeWriter::processSkinPart( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part) const { bool added_something = false; @@ -2287,13 +2669,14 @@ bool FffGcodeWriter::processSkinPart(const SliceDataStorage& storage, LayerPlan& return added_something; } -void FffGcodeWriter::processRoofing(const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const SliceMeshStorage& mesh, - const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, - const SkinPart& skin_part, - bool& added_something) const +void FffGcodeWriter::processRoofing( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part, + bool& added_something) const { const size_t roofing_extruder_nr = mesh.settings.get("roofing_extruder_nr").extruder_nr; if (extruder_nr != roofing_extruder_nr) @@ -2311,16 +2694,30 @@ void FffGcodeWriter::processRoofing(const SliceDataStorage& storage, const Ratio skin_density = 1.0; const coord_t skin_overlap = 0; // skinfill already expanded over the roofing areas; don't overlap with perimeters const bool monotonic = mesh.settings.get("roofing_monotonic"); - processSkinPrintFeature(storage, gcode_layer, mesh, mesh_config, extruder_nr, skin_part.roofing_fill, mesh_config.roofing_config, pattern, roofing_angle, skin_overlap, skin_density, monotonic, added_something); + processSkinPrintFeature( + storage, + gcode_layer, + mesh, + mesh_config, + extruder_nr, + skin_part.roofing_fill, + mesh_config.roofing_config, + pattern, + roofing_angle, + skin_overlap, + skin_density, + monotonic, + added_something); } -void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const SliceMeshStorage& mesh, - const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, - const SkinPart& skin_part, - bool& added_something) const +void FffGcodeWriter::processTopBottom( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const size_t extruder_nr, + const MeshPathConfigs& mesh_config, + const SkinPart& skin_part, + bool& added_something) const { if (skin_part.skin_fill.empty()) { @@ -2346,7 +2743,7 @@ void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, // generate skin_polygons and skin_lines const GCodePathConfig* skin_config = &mesh_config.skin_config; Ratio skin_density = 1.0; - const coord_t skin_overlap = 0; // Skin overlap offset is applied in skin.cpp more overlap might be beneficial for curved bridges, but makes it worse in general. + const coord_t skin_overlap = 0; // Skin overlap offset is applied in skin.cpp more overlap might be beneficial for curved bridges, but makes it worse in general. const bool bridge_settings_enabled = mesh.settings.get("bridge_settings_enabled"); const bool bridge_enable_more_layers = bridge_settings_enabled && mesh.settings.get("bridge_enable_more_layers"); const Ratio support_threshold = bridge_settings_enabled ? mesh.settings.get("bridge_skin_support_threshold") : 0.0_r; @@ -2361,7 +2758,7 @@ void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, { const coord_t layer_height = mesh_config.inset0_config.getLayerThickness(); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; support_layer_nr = layer_nr - z_distance_top_layers; } @@ -2478,23 +2875,38 @@ void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, } } const bool monotonic = mesh.settings.get("skin_monotonic"); - processSkinPrintFeature(storage, gcode_layer, mesh, mesh_config, extruder_nr, skin_part.skin_fill, *skin_config, pattern, skin_angle, skin_overlap, skin_density, monotonic, added_something, fan_speed); + processSkinPrintFeature( + storage, + gcode_layer, + mesh, + mesh_config, + extruder_nr, + skin_part.skin_fill, + *skin_config, + pattern, + skin_angle, + skin_overlap, + skin_density, + monotonic, + added_something, + fan_speed); } -void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const SliceMeshStorage& mesh, - const PathConfigStorage::MeshPathConfigs& mesh_config, - const size_t extruder_nr, - const Polygons& area, - const GCodePathConfig& config, - EFillMethod pattern, - const AngleDegrees skin_angle, - const coord_t skin_overlap, - const Ratio skin_density, - const bool monotonic, - bool& added_something, - double fan_speed) const +void FffGcodeWriter::processSkinPrintFeature( + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const SliceMeshStorage& mesh, + const MeshPathConfigs& mesh_config, + const size_t extruder_nr, + const Polygons& area, + const GCodePathConfig& config, + EFillMethod pattern, + const AngleDegrees skin_angle, + const coord_t skin_overlap, + const Ratio skin_density, + const bool monotonic, + bool& added_something, + double fan_speed) const { Polygons skin_polygons; Polygons skin_lines; @@ -2516,31 +2928,45 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, constexpr bool skip_some_zags = false; constexpr int zag_skip_count = 0; constexpr coord_t pocket_size = 0; - - Infill infill_comp(pattern, - zig_zaggify_infill, - connect_polygons, - area, - config.getLineWidth(), - config.getLineWidth() / skin_density, - skin_overlap, - infill_multiplier, - skin_angle, - gcode_layer.z, - extra_infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_line_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - infill_comp.generate(skin_paths, skin_polygons, skin_lines, mesh.settings, gcode_layer.getLayerNr(), SectionType::SKIN); + const bool small_areas_on_surface = mesh.settings.get("small_skin_on_surface"); + const auto& current_layer = mesh.layers[gcode_layer.getLayerNr()]; + const auto& exposed_to_air = current_layer.top_surface.areas.unionPolygons(current_layer.bottom_surface); + + Infill infill_comp( + pattern, + zig_zaggify_infill, + connect_polygons, + area, + config.getLineWidth(), + config.getLineWidth() / skin_density, + skin_overlap, + infill_multiplier, + skin_angle, + gcode_layer.z, + extra_infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_line_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + skin_paths, + skin_polygons, + skin_lines, + mesh.settings, + gcode_layer.getLayerNr(), + SectionType::SKIN, + nullptr, + nullptr, + nullptr, + small_areas_on_surface ? Polygons() : exposed_to_air); // add paths if (! skin_polygons.empty() || ! skin_lines.empty() || ! skin_paths.empty()) @@ -2555,23 +2981,28 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, { constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; - const ZSeamConfig z_seam_config(mesh.settings.get("z_seam_type"), mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), config.getLineWidth() * 2); - InsetOrderOptimizer wall_orderer(*this, - storage, - gcode_layer, - mesh.settings, - extruder_nr, - mesh_config.skin_config, - mesh_config.skin_config, - mesh_config.skin_config, - mesh_config.skin_config, - retract_before_outer_wall, - wipe_dist, - wipe_dist, - skin_extruder_nr, - skin_extruder_nr, - z_seam_config, - skin_paths); + const ZSeamConfig z_seam_config( + mesh.settings.get("z_seam_type"), + mesh.getZSeamHint(), + mesh.settings.get("z_seam_corner"), + config.getLineWidth() * 2); + InsetOrderOptimizer wall_orderer( + *this, + storage, + gcode_layer, + mesh.settings, + extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + skin_extruder_nr, + skin_extruder_nr, + z_seam_config, + skin_paths); added_something |= wall_orderer.addToLayer(); } } @@ -2588,11 +3019,23 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, const AngleRadians monotonic_direction = AngleRadians(skin_angle); constexpr Ratio flow = 1.0_r; - const coord_t max_adjacent_distance = config.getLineWidth() * 1.1; // Lines are considered adjacent if they are 1 line width apart, with 10% extra play. The monotonic order is enforced if they are adjacent. - if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC - || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) + const coord_t max_adjacent_distance + = config.getLineWidth() + * 1.1; // Lines are considered adjacent if they are 1 line width apart, with 10% extra play. The monotonic order is enforced if they are adjacent. + if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC + || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) { - gcode_layer.addLinesMonotonic(area, skin_lines, config, SpaceFillType::Lines, monotonic_direction, max_adjacent_distance, exclude_distance, mesh.settings.get("infill_wipe_dist"), flow, fan_speed); + gcode_layer.addLinesMonotonic( + area, + skin_lines, + config, + SpaceFillType::Lines, + monotonic_direction, + max_adjacent_distance, + exclude_distance, + mesh.settings.get("infill_wipe_dist"), + flow, + fan_speed); } else { @@ -2604,7 +3047,8 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, else { std::optional near_start_location; - const EFillMethod pattern = (gcode_layer.getLayerNr() == 0) ? mesh.settings.get("top_bottom_pattern_0") : mesh.settings.get("top_bottom_pattern"); + const EFillMethod pattern + = (gcode_layer.getLayerNr() == 0) ? mesh.settings.get("top_bottom_pattern_0") : mesh.settings.get("top_bottom_pattern"); if (pattern == EFillMethod::LINES || pattern == EFillMethod::ZIG_ZAG) { // update near_start_location to a location which tries to avoid seams in skin near_start_location = getSeamAvoidingLocation(area, skin_angle, gcode_layer.getLastPlannedPositionOrStartingPosition()); @@ -2612,10 +3056,18 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, constexpr bool enable_travel_optimization = false; constexpr float flow = 1.0; - if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC - || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) + if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC + || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::LIGHTNING) { - gcode_layer.addLinesByOptimizer(skin_lines, config, SpaceFillType::Lines, enable_travel_optimization, mesh.settings.get("infill_wipe_dist"), flow, near_start_location, fan_speed); + gcode_layer.addLinesByOptimizer( + skin_lines, + config, + SpaceFillType::Lines, + enable_travel_optimization, + mesh.settings.get("infill_wipe_dist"), + flow, + near_start_location, + fan_speed); } else { @@ -2627,7 +3079,12 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, } } -bool FffGcodeWriter::processIroning(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const SliceLayer& layer, const GCodePathConfig& line_config, LayerPlan& gcode_layer) const +bool FffGcodeWriter::processIroning( + const SliceDataStorage& storage, + const SliceMeshStorage& mesh, + const SliceLayer& layer, + const GCodePathConfig& line_config, + LayerPlan& gcode_layer) const { bool added_something = false; const bool ironing_enabled = mesh.settings.get("ironing_enabled"); @@ -2656,22 +3113,30 @@ bool FffGcodeWriter::addSupportToGCode(const SliceDataStorage& storage, LayerPla const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const size_t support_roof_extruder_nr = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; const size_t support_bottom_extruder_nr = mesh_group_settings.get("support_bottom_extruder_nr").extruder_nr; - size_t support_infill_extruder_nr = - (gcode_layer.getLayerNr() <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; + size_t support_infill_extruder_nr = (gcode_layer.getLayerNr() <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr + : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; - const SupportLayer& support_layer = storage.support.supportLayers[std::max(0, gcode_layer.getLayerNr())]; + const SupportLayer& support_layer = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; if (support_layer.support_bottom.empty() && support_layer.support_roof.empty() && support_layer.support_infill_parts.empty()) { return support_added; } + if (extruder_nr == support_roof_extruder_nr) + { + support_added |= addSupportRoofsToGCode(storage, support_layer.support_fractional_roof, gcode_layer.configs_storage.support_fractional_roof_config, gcode_layer); + } if (extruder_nr == support_infill_extruder_nr) { support_added |= processSupportInfill(storage, gcode_layer); } if (extruder_nr == support_roof_extruder_nr) { - support_added |= addSupportRoofsToGCode(storage, gcode_layer); + support_added |= addSupportRoofsToGCode( + storage, + support_layer.support_roof.difference(support_layer.support_fractional_roof), + gcode_layer.configs_storage.support_roof_config, + gcode_layer); } if (extruder_nr == support_bottom_extruder_nr) { @@ -2684,7 +3149,8 @@ bool FffGcodeWriter::addSupportToGCode(const SliceDataStorage& storage, LayerPla bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { bool added_something = false; - const SupportLayer& support_layer = storage.support.supportLayers[std::max(0, gcode_layer.getLayerNr())]; // account for negative layer numbers for raft filler layers + const SupportLayer& support_layer + = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; // account for negative layer numbers for raft filler layers if (gcode_layer.getLayerNr() > storage.support.layer_nr_max_filled_layer || support_layer.support_infill_parts.empty()) { @@ -2692,7 +3158,8 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const size_t extruder_nr = (gcode_layer.getLayerNr() <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; + const size_t extruder_nr = (gcode_layer.getLayerNr() <= 0) ? mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr + : mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; const ExtruderTrain& infill_extruder = Application::getInstance().current_slice->scene.extruders[extruder_nr]; coord_t default_support_line_distance = infill_extruder.settings.get("support_line_distance"); @@ -2745,14 +3212,15 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const auto zag_skip_count = infill_extruder.settings.get("support_zag_skip_count"); // create a list of outlines and use PathOrderOptimizer to optimize the travel move + PathOrderOptimizer island_order_optimizer_initial(gcode_layer.getLastPlannedPositionOrStartingPosition()); PathOrderOptimizer island_order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition()); for (const SupportInfillPart& part : support_layer.support_infill_parts) { - island_order_optimizer.addPolygon(&part); + (part.use_fractional_config ? island_order_optimizer_initial : island_order_optimizer).addPolygon(&part); } + island_order_optimizer_initial.optimize(); island_order_optimizer.optimize(); - const auto support_brim_line_count = infill_extruder.settings.get("support_brim_line_count"); const auto support_connect_zigzags = infill_extruder.settings.get("support_connect_zigzags"); const auto support_structure = infill_extruder.settings.get("support_structure"); const Point infill_origin; @@ -2763,9 +3231,10 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer bool need_travel_to_end_of_last_spiral = true; // Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers) - for (const PathOrdering& path : island_order_optimizer.paths) + for (const PathOrdering& path : ranges::views::concat(island_order_optimizer_initial.paths, island_order_optimizer.paths)) { const SupportInfillPart& part = *path.vertices; + const auto& configs = part.use_fractional_config ? gcode_layer.configs_storage.support_fractional_infill_config : gcode_layer.configs_storage.support_infill_config; // always process the wall overlap if walls are generated const int current_support_infill_overlap = (part.inset_count_to_generate > 0) ? default_support_infill_overlap : 0; @@ -2775,12 +3244,27 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer if (! wall_toolpaths.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; + const GCodePathConfig& config = configs[0]; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); InsetOrderOptimizer wall_orderer( - *this, storage, gcode_layer, infill_extruder.settings, extruder_nr, config, config, config, config, retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, wall_toolpaths); + *this, + storage, + gcode_layer, + infill_extruder.settings, + extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + extruder_nr, + extruder_nr, + z_seam_config, + wall_toolpaths); added_something |= wall_orderer.addToLayer(); } @@ -2805,42 +3289,53 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } const unsigned int density_factor = 2 << density_idx; // == pow(2, density_idx + 1) - int support_line_distance_here = (part.custom_line_distance > 0 ? part.custom_line_distance : default_support_line_distance * density_factor); // the highest density infill combines with the next to create a grid with density_factor 1 + int support_line_distance_here + = (part.custom_line_distance > 0 + ? part.custom_line_distance + : default_support_line_distance * density_factor); // the highest density infill combines with the next to create a grid with density_factor 1 const int support_shift = support_line_distance_here / 2; if (part.custom_line_distance == 0 && (density_idx == max_density_idx || support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D)) { support_line_distance_here /= 2; } - const Polygons& area = part.infill_area_per_combine_per_density[density_idx][combine_idx]; + const Polygons& area = Simplify(infill_extruder.settings).polygon(part.infill_area_per_combine_per_density[density_idx][combine_idx]); constexpr size_t wall_count = 0; // Walls are generated somewhere else, so their layers aren't vertically combined. - const coord_t small_area_width = mesh_group_settings.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const coord_t small_area_width = 0; constexpr bool skip_stitching = false; const bool fill_gaps = density_idx == 0; // Only fill gaps for one of the densities. - Infill infill_comp(support_pattern, - zig_zaggify_infill, - connect_polygons, - area, - support_line_width, - support_line_distance_here, - current_support_infill_overlap - (density_idx == max_density_idx ? 0 : wall_line_count * support_line_width), - infill_multiplier, - support_infill_angle, - gcode_layer.z, - support_shift, - max_resolution, - max_deviation, - wall_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - support_connect_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - infill_comp.generate(wall_toolpaths_here, support_polygons, support_lines, infill_extruder.settings, gcode_layer.getLayerNr(), SectionType::SUPPORT, storage.support.cross_fill_provider); + Infill infill_comp( + support_pattern, + zig_zaggify_infill, + connect_polygons, + area, + support_line_width, + support_line_distance_here, + current_support_infill_overlap - (density_idx == max_density_idx ? 0 : wall_line_count * support_line_width), + infill_multiplier, + support_infill_angle, + gcode_layer.z + configs[combine_idx].z_offset, + support_shift, + max_resolution, + max_deviation, + wall_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + support_connect_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + infill_comp.generate( + wall_toolpaths_here, + support_polygons, + support_lines, + infill_extruder.settings, + gcode_layer.getLayerNr(), + SectionType::SUPPORT, + storage.support.cross_fill_provider); } if (need_travel_to_end_of_last_spiral && infill_extruder.settings.get("magic_spiralize")) @@ -2882,7 +3377,15 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const std::optional start_near_location = std::optional(); gcode_layer.addPolygonsByOptimizer( - support_polygons, gcode_layer.configs_storage.support_infill_config[combine_idx], z_seam_config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract, alternate_layer_print_direction, start_near_location); + support_polygons, + configs[combine_idx], + z_seam_config, + wall_0_wipe_dist, + spiralize, + flow_ratio, + always_retract, + alternate_layer_print_direction, + start_near_location); added_something = true; } @@ -2894,15 +3397,16 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const std::optional near_start_location = std::optional(); constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; - gcode_layer.addLinesByOptimizer(support_lines, - gcode_layer.configs_storage.support_infill_config[combine_idx], - (support_pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, - enable_travel_optimization, - wipe_dist, - flow_ratio, - near_start_location, - fan_speed, - alternate_layer_print_direction); + gcode_layer.addLinesByOptimizer( + support_lines, + configs[combine_idx], + (support_pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, + enable_travel_optimization, + wipe_dist, + flow_ratio, + near_start_location, + fan_speed, + alternate_layer_print_direction); added_something = true; } @@ -2911,13 +3415,32 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer // If not, the pattern may still generate gap filling (if it's connected infill or zigzag). We still want to print those. if (wall_line_count == 0 || ! wall_toolpaths_here.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; + const GCodePathConfig& config = configs[0]; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; constexpr coord_t simplify_curvature = 0; - const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, simplify_curvature); + const ZSeamConfig z_seam_config( + EZSeamType::SHORTEST, + gcode_layer.getLastPlannedPositionOrStartingPosition(), + EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, + simplify_curvature); InsetOrderOptimizer wall_orderer( - *this, storage, gcode_layer, infill_extruder.settings, extruder_nr, config, config, config, config, retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, wall_toolpaths_here); + *this, + storage, + gcode_layer, + infill_extruder.settings, + extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + extruder_nr, + extruder_nr, + z_seam_config, + wall_toolpaths_here); added_something |= wall_orderer.addToLayer(); } } @@ -2927,9 +3450,13 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } -bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, LayerPlan& gcode_layer) const +bool FffGcodeWriter::addSupportRoofsToGCode( + const SliceDataStorage& storage, + const Polygons& support_roof_outlines, + const GCodePathConfig& current_roof_config, + LayerPlan& gcode_layer) const { - const SupportLayer& support_layer = storage.support.supportLayers[std::max(0, gcode_layer.getLayerNr())]; + const SupportLayer& support_layer = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; if (! storage.support.generated || gcode_layer.getLayerNr() > storage.support.layer_nr_max_filled_layer || support_layer.support_roof.empty()) { @@ -2973,7 +3500,7 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay support_roof_line_distance *= roof_extruder.settings.get("initial_layer_line_width_factor"); } - Polygons infill_outline = support_layer.support_roof; + Polygons infill_outline = support_roof_outlines; Polygons wall; // make sure there is a wall if this is on the first layer if (gcode_layer.getLayerNr() == 0) @@ -2981,30 +3508,32 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay wall = support_layer.support_roof.offset(-support_roof_line_width / 2); infill_outline = wall.offset(-support_roof_line_width / 2); } - - Infill roof_computation(pattern, - zig_zaggify_infill, - connect_polygons, - infill_outline, - gcode_layer.configs_storage.support_roof_config.getLineWidth(), - support_roof_line_distance, - support_roof_overlap, - infill_multiplier, - fill_angle, - gcode_layer.z, - extra_infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); + infill_outline = Simplify(roof_extruder.settings).polygon(infill_outline); + + Infill roof_computation( + pattern, + zig_zaggify_infill, + connect_polygons, + infill_outline, + current_roof_config.getLineWidth(), + support_roof_line_distance, + support_roof_overlap, + infill_multiplier, + fill_angle, + gcode_layer.z + current_roof_config.z_offset, + extra_infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); Polygons roof_polygons; std::vector roof_paths; Polygons roof_lines; @@ -3016,32 +3545,47 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support if (gcode_layer.getLayerNr() == 0) { - gcode_layer.addPolygonsByOptimizer(wall, gcode_layer.configs_storage.support_roof_config); + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); } if (! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, gcode_layer.configs_storage.support_roof_config); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); } if (! roof_paths.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_roof_config; + const GCodePathConfig& config = current_roof_config; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); InsetOrderOptimizer wall_orderer( - *this, storage, gcode_layer, roof_extruder.settings, roof_extruder_nr, config, config, config, config, retract_before_outer_wall, wipe_dist, wipe_dist, roof_extruder_nr, roof_extruder_nr, z_seam_config, roof_paths); + *this, + storage, + gcode_layer, + roof_extruder.settings, + roof_extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + roof_extruder_nr, + roof_extruder_nr, + z_seam_config, + roof_paths); wall_orderer.addToLayer(); } - gcode_layer.addLinesByOptimizer(roof_lines, gcode_layer.configs_storage.support_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); + gcode_layer.addLinesByOptimizer(roof_lines, current_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); return true; } bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { - const SupportLayer& support_layer = storage.support.supportLayers[std::max(0, gcode_layer.getLayerNr())]; + const SupportLayer& support_layer = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; if (! storage.support.generated || gcode_layer.getLayerNr() > storage.support.layer_nr_max_filled_layer || support_layer.support_bottom.empty()) { @@ -3079,30 +3623,32 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L const coord_t max_resolution = bottom_extruder.settings.get("meshfix_maximum_resolution"); const coord_t max_deviation = bottom_extruder.settings.get("meshfix_maximum_deviation"); - const coord_t support_bottom_line_distance = bottom_extruder.settings.get("support_bottom_line_distance"); // note: no need to apply initial line width factor; support bottoms cannot exist on the first layer - Infill bottom_computation(pattern, - zig_zaggify_infill, - connect_polygons, - support_layer.support_bottom, - gcode_layer.configs_storage.support_bottom_config.getLineWidth(), - support_bottom_line_distance, - support_bottom_overlap, - infill_multiplier, - fill_angle, - gcode_layer.z, - extra_infill_shift, - max_resolution, - max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); + const coord_t support_bottom_line_distance = bottom_extruder.settings.get( + "support_bottom_line_distance"); // note: no need to apply initial line width factor; support bottoms cannot exist on the first layer + Infill bottom_computation( + pattern, + zig_zaggify_infill, + connect_polygons, + support_layer.support_bottom, + gcode_layer.configs_storage.support_bottom_config.getLineWidth(), + support_bottom_line_distance, + support_bottom_overlap, + infill_multiplier, + fill_angle, + gcode_layer.z, + extra_infill_shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); Polygons bottom_polygons; std::vector bottom_paths; Polygons bottom_lines; @@ -3126,17 +3672,33 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); InsetOrderOptimizer wall_orderer( - *this, storage, gcode_layer, bottom_extruder.settings, bottom_extruder_nr, config, config, config, config, retract_before_outer_wall, wipe_dist, wipe_dist, bottom_extruder_nr, bottom_extruder_nr, z_seam_config, bottom_paths); + *this, + storage, + gcode_layer, + bottom_extruder.settings, + bottom_extruder_nr, + config, + config, + config, + config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + bottom_extruder_nr, + bottom_extruder_nr, + z_seam_config, + bottom_paths); wall_orderer.addToLayer(); } - gcode_layer.addLinesByOptimizer(bottom_lines, gcode_layer.configs_storage.support_bottom_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); + gcode_layer.addLinesByOptimizer( + bottom_lines, + gcode_layer.configs_storage.support_bottom_config, + (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); return true; } void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr) const { - const size_t outermost_prime_tower_extruder = storage.primeTower.extruder_order[0]; - const size_t previous_extruder = gcode_layer.getExtruder(); const bool extruder_changed = gcode_layer.setExtruder(extruder_nr); @@ -3236,7 +3798,7 @@ void FffGcodeWriter::finalize() // set extrusion mode back to "normal" gcode.resetExtrusionMode(); - for (size_t e = 0; e < Application::getInstance().current_slice->scene.extruders.size(); e ++) + for (size_t e = 0; e < Application::getInstance().current_slice->scene.extruders.size(); e++) { gcode.writeTemperatureCommand(e, 0, false); } diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 817e91ecde..3b1a5e4c38 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -80,7 +80,9 @@ size_t FffPolygonGenerator::getDraftShieldLayerCount(const size_t total_layers) case DraftShieldHeightLimitation::FULL: return total_layers; case DraftShieldHeightLimitation::LIMITED: - return std::max((coord_t)0, (mesh_group_settings.get("draft_shield_height") - mesh_group_settings.get("layer_height_0")) / mesh_group_settings.get("layer_height") + 1); + return std::max( + (coord_t)0, + (mesh_group_settings.get("draft_shield_height") - mesh_group_settings.get("layer_height_0")) / mesh_group_settings.get("layer_height") + 1); default: spdlog::warn("A draft shield height limitation option was added without implementing the new option in getDraftShieldLayerCount."); return total_layers; @@ -128,9 +130,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe const auto variable_layer_height_max_variation = mesh_group_settings.get("adaptive_layer_height_variation"); const auto variable_layer_height_variation_step = mesh_group_settings.get("adaptive_layer_height_variation_step"); const auto adaptive_threshold = mesh_group_settings.get("adaptive_layer_height_threshold"); - adaptive_layer_heights = new AdaptiveLayerHeights(layer_thickness, variable_layer_height_max_variation, - variable_layer_height_variation_step, adaptive_threshold, - meshgroup); + adaptive_layer_heights + = new AdaptiveLayerHeights(layer_thickness, variable_layer_height_max_variation, variable_layer_height_variation_step, adaptive_threshold, meshgroup); // Get the amount of layers slice_layer_count = adaptive_layer_heights->getLayerCount(); @@ -174,7 +175,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe case SlicingTolerance::INCLUSIVE: if (mesh_height < initial_layer_thickness) { - slice_layer_count = std::max(slice_layer_count, (mesh_height > 0) ? 1 : 0); // If less than the initial layer height, it always has 1 layer unless the height is truly zero. + slice_layer_count + = std::max(slice_layer_count, (mesh_height > 0) ? 1 : 0); // If less than the initial layer height, it always has 1 layer unless the height is truly zero. } else { @@ -265,15 +267,16 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe } storage.support.supportLayers.resize(storage.print_layer_count); - storage.meshes.reserve(slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated. + storage.meshes.reserve( + slicerList.size()); // causes there to be no resize in meshes so that the pointers in sliceMeshStorage._config to retraction_config don't get invalidated. for (unsigned int meshIdx = 0; meshIdx < slicerList.size(); meshIdx++) { Slicer* slicer = slicerList[meshIdx]; Mesh& mesh = scene.current_mesh_group->meshes[meshIdx]; // always make a new SliceMeshStorage, so that they have the same ordering / indexing as meshgroup.meshes - storage.meshes.emplace_back(&meshgroup->meshes[meshIdx], slicer->layers.size()); // new mesh in storage had settings from the Mesh - SliceMeshStorage& meshStorage = storage.meshes.back(); + storage.meshes.push_back(std::make_shared(&meshgroup->meshes[meshIdx], slicer->layers.size())); // new mesh in storage had settings from the Mesh + SliceMeshStorage& meshStorage = *storage.meshes.back(); // only create layer parts for normal meshes const bool is_support_modifier = AreaSupport::handleSupportModifierMesh(storage, mesh.settings, slicer); @@ -295,7 +298,7 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe const bool has_raft = mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT; // calculate the height at which each layer is actually printed (printZ) - for (unsigned int layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < meshStorage.layers.size(); layer_nr++) { SliceLayer& layer = meshStorage.layers[layer_nr]; @@ -322,7 +325,8 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe if (has_raft) { const ExtruderTrain& train = mesh_group_settings.get("raft_surface_extruder_nr"); - layer.printZ += Raft::getTotalThickness() + train.settings.get("raft_airgap") - train.settings.get("layer_0_z_overlap"); // shift all layers (except 0) down + layer.printZ += Raft::getTotalThickness() + train.settings.get("raft_airgap") + - train.settings.get("layer_0_z_overlap"); // shift all layers (except 0) down if (layer_nr == 0) { @@ -343,8 +347,9 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& // compute layer count and remove first empty layers // there is no separate progress stage for removeEmptyFisrtLayer (TODO) unsigned int slice_layer_count = 0; - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh_ptr : storage.meshes) { + auto& mesh = *mesh_ptr; if (! mesh.settings.get("infill_mesh") && ! mesh.settings.get("anti_overhang_mesh")) { slice_layer_count = std::max(slice_layer_count, mesh.layers.size()); @@ -365,7 +370,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& std::multimap order_to_mesh_indices; for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - order_to_mesh_indices.emplace(storage.meshes[mesh_idx].settings.get("infill_mesh_order"), mesh_idx); + order_to_mesh_indices.emplace(storage.meshes[mesh_idx]->settings.get("infill_mesh_order"), mesh_idx); } for (std::pair& order_and_mesh_idx : order_to_mesh_indices) { @@ -386,7 +391,7 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& // only remove empty layers if we haven't generate support, because then support was added underneath the model. // for some materials it's better to print on support than on the build plate. const auto has_support = mesh_group_settings.get("support_enable") || mesh_group_settings.get("support_mesh"); - const auto remove_empty_first_layers = mesh_group_settings.get("remove_empty_first_layers") && !has_support; + const auto remove_empty_first_layers = mesh_group_settings.get("remove_empty_first_layers") && ! has_support; if (remove_empty_first_layers) { removeEmptyFirstLayers(storage, storage.print_layer_count); // changes storage.print_layer_count! @@ -428,9 +433,9 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& spdlog::debug("Meshes post-processing"); // meshes post processing - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh : storage.meshes) { - processDerivedWallsSkinInfill(mesh); + processDerivedWallsSkinInfill(*mesh); } spdlog::debug("Processing gradual support"); @@ -438,10 +443,14 @@ void FffPolygonGenerator::slices2polygons(SliceDataStorage& storage, TimeKeeper& AreaSupport::generateSupportInfillFeatures(storage); } -void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, const size_t mesh_order_idx, const std::vector& mesh_order, ProgressStageEstimator& inset_skin_progress_estimate) +void FffPolygonGenerator::processBasicWallsSkinInfill( + SliceDataStorage& storage, + const size_t mesh_order_idx, + const std::vector& mesh_order, + ProgressStageEstimator& inset_skin_progress_estimate) { size_t mesh_idx = mesh_order[mesh_order_idx]; - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; size_t mesh_layer_count = mesh.layers.size(); if (mesh.settings.get("infill_mesh")) { @@ -485,14 +494,15 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, } guarded_progress = { inset_skin_progress_estimate }; // walls - cura::parallel_for(0, - mesh_layer_count, - [&](size_t layer_number) - { - spdlog::debug("Processing insets for layer {} of {}", layer_number, mesh.layers.size()); - processWalls(mesh, layer_number); - guarded_progress++; - }); + cura::parallel_for( + 0, + mesh_layer_count, + [&](size_t layer_number) + { + spdlog::debug("Processing insets for layer {} of {}", layer_number, mesh.layers.size()); + processWalls(mesh, layer_number); + guarded_progress++; + }); ProgressEstimatorLinear* skin_estimator = new ProgressEstimatorLinear(mesh_layer_count); mesh_inset_skin_progress_estimator->nextStage(skin_estimator); @@ -504,7 +514,7 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, for (size_t other_mesh_order_idx = mesh_order_idx + 1; other_mesh_order_idx < mesh_order.size(); ++other_mesh_order_idx) { const size_t other_mesh_idx = mesh_order[other_mesh_order_idx]; - SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx]; + SliceMeshStorage& other_mesh = *storage.meshes[other_mesh_idx]; if (other_mesh.settings.get("infill_mesh")) { AABB3D aabb = scene.current_mesh_group->meshes[mesh_idx].getAABB(); @@ -527,23 +537,24 @@ void FffPolygonGenerator::processBasicWallsSkinInfill(SliceDataStorage& storage, } guarded_progress.reset(); - cura::parallel_for(0, - mesh_layer_count, - [&](size_t layer_number) - { - spdlog::debug("Processing skins and infill layer {} of {}", layer_number, mesh.layers.size()); - if (! magic_spiralize || layer_number < mesh_max_initial_bottom_layer_count) // Only generate up/downskin and infill for the first X layers when spiralize is choosen. - { - processSkinsAndInfill(mesh, layer_number, process_infill); - } - guarded_progress++; - }); + cura::parallel_for( + 0, + mesh_layer_count, + [&](size_t layer_number) + { + spdlog::debug("Processing skins and infill layer {} of {}", layer_number, mesh.layers.size()); + if (! magic_spiralize || layer_number < mesh_max_initial_bottom_layer_count) // Only generate up/downskin and infill for the first X layers when spiralize is choosen. + { + processSkinsAndInfill(mesh, layer_number, process_infill); + } + guarded_progress++; + }); } void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, const size_t mesh_order_idx, const std::vector& mesh_order) { size_t mesh_idx = mesh_order[mesh_order_idx]; - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; coord_t surface_line_width = mesh.settings.get("wall_line_width_0"); mesh.layer_nr_max_filled_layer = -1; @@ -575,7 +586,7 @@ void FffPolygonGenerator::processInfillMesh(SliceDataStorage& storage, const siz { break; // all previous meshes have been processed } - SliceMeshStorage& other_mesh = storage.meshes[other_mesh_idx]; + SliceMeshStorage& other_mesh = *storage.meshes[other_mesh_idx]; if (layer_idx >= static_cast(other_mesh.layers.size())) { // there can be no interaction between the infill mesh and this other non-infill mesh continue; @@ -667,13 +678,18 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh) } // Pre-compute Cross Fractal - if (mesh.settings.get("infill_line_distance") > 0 && (mesh.settings.get("infill_pattern") == EFillMethod::CROSS || mesh.settings.get("infill_pattern") == EFillMethod::CROSS_3D)) + if (mesh.settings.get("infill_line_distance") > 0 + && (mesh.settings.get("infill_pattern") == EFillMethod::CROSS || mesh.settings.get("infill_pattern") == EFillMethod::CROSS_3D)) { const std::string cross_subdivision_spec_image_file = mesh.settings.get("cross_infill_density_image"); std::ifstream cross_fs(cross_subdivision_spec_image_file.c_str()); if (! cross_subdivision_spec_image_file.empty() && cross_fs.good()) { - mesh.cross_fill_provider = new SierpinskiFillProvider(mesh.bounding_box, mesh.settings.get("infill_line_distance"), mesh.settings.get("infill_line_width"), cross_subdivision_spec_image_file); + mesh.cross_fill_provider = std::make_shared( + mesh.bounding_box, + mesh.settings.get("infill_line_distance"), + mesh.settings.get("infill_line_width"), + cross_subdivision_spec_image_file); } else { @@ -681,7 +697,8 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh) { spdlog::error("Cannot find density image: {}.", cross_subdivision_spec_image_file); } - mesh.cross_fill_provider = new SierpinskiFillProvider(mesh.bounding_box, mesh.settings.get("infill_line_distance"), mesh.settings.get("infill_line_width")); + mesh.cross_fill_provider + = std::make_shared(mesh.bounding_box, mesh.settings.get("infill_line_distance"), mesh.settings.get("infill_line_width")); } } @@ -689,14 +706,14 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh) if (mesh.settings.get("infill_line_distance") > 0 && mesh.settings.get("infill_pattern") == EFillMethod::LIGHTNING) { // TODO: Make all of these into new type pointers (but the cross fill things need to happen too then, otherwise it'd just look weird). - mesh.lightning_generator = new LightningGenerator(mesh); + mesh.lightning_generator = std::make_shared(mesh); } // combine infill SkinInfillAreaComputation::combineInfillLayers(mesh); // Fuzzy skin. Disabled when using interlocking structures, the internal interlocking walls become fuzzy. - if (mesh.settings.get("magic_fuzzy_skin_enabled") && !mesh.settings.get("interlocking_enable")) + if (mesh.settings.get("magic_fuzzy_skin_enabled") && ! mesh.settings.get("interlocking_enable")) { processFuzzyWalls(mesh); } @@ -715,7 +732,7 @@ void FffPolygonGenerator::processWalls(SliceMeshStorage& mesh, size_t layer_nr) walls_computation.generateWalls(layer, SectionType::WALL); } -bool FffPolygonGenerator::isEmptyLayer(SliceDataStorage& storage, const unsigned int layer_idx) +bool FffPolygonGenerator::isEmptyLayer(SliceDataStorage& storage, const LayerIndex& layer_idx) { if (storage.support.generated && layer_idx < storage.support.supportLayers.size()) { @@ -725,8 +742,9 @@ bool FffPolygonGenerator::isEmptyLayer(SliceDataStorage& storage, const unsigned return false; } } - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh_ptr : storage.meshes) { + auto& mesh = *mesh_ptr; if (layer_idx >= mesh.layers.size()) { continue; @@ -766,8 +784,9 @@ void FffPolygonGenerator::removeEmptyFirstLayers(SliceDataStorage& storage, size { spdlog::info("Removing {} layers because they are empty", n_empty_first_layers); const coord_t layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height"); - for (SliceMeshStorage& mesh : storage.meshes) + for (auto& mesh_ptr : storage.meshes) { + auto& mesh = *mesh_ptr; std::vector& layers = mesh.layers; if (layers.size() > n_empty_first_layers) { @@ -808,11 +827,22 @@ void FffPolygonGenerator::processSkinsAndInfill(SliceMeshStorage& mesh, const La SkinInfillAreaComputation skin_infill_area_computation(layer_nr, mesh, process_infill); skin_infill_area_computation.generateSkinsAndInfill(); - if (mesh.settings.get("ironing_enabled") && (! mesh.settings.get("ironing_only_highest_layer") || mesh.layer_nr_max_filled_layer == layer_nr)) + if (mesh.settings.get("ironing_enabled") && (! mesh.settings.get("ironing_only_highest_layer") || mesh.layer_nr_max_filled_layer == layer_nr) + || ! mesh.settings.get("small_skin_on_surface")) { // Generate the top surface to iron over. mesh.layers[layer_nr].top_surface.setAreasFromMeshAndLayerNumber(mesh, layer_nr); } + + if (layer_nr >= 0 && ! mesh.settings.get("small_skin_on_surface")) + { + // Generate the bottom surface. + mesh.layers[layer_nr].bottom_surface = mesh.layers[layer_nr].getOutlines(); + if (layer_nr > 0) + { + mesh.layers[layer_nr].bottom_surface = mesh.layers[layer_nr].bottom_surface.difference(mesh.layers[layer_nr - 1].getOutlines()); + } + } } void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage) @@ -825,8 +855,9 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage max_print_height_per_extruder.resize(extruder_count, -(raft_layers + 1)); // Initialize all as -1 (or lower in case of raft). { // compute max_object_height_per_extruder // Height of the meshes themselves. - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh_ptr : storage.meshes) { + auto& mesh = *mesh_ptr; if (mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh")) { continue; // Special type of mesh that doesn't get printed. @@ -847,11 +878,14 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage // Height of where the support reaches. Scene& scene = Application::getInstance().current_slice->scene; const Settings& mesh_group_settings = scene.current_mesh_group->settings; - const size_t support_infill_extruder_nr = mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; // TODO: Support extruder should be configurable per object. + const size_t support_infill_extruder_nr + = mesh_group_settings.get("support_infill_extruder_nr").extruder_nr; // TODO: Support extruder should be configurable per object. max_print_height_per_extruder[support_infill_extruder_nr] = std::max(max_print_height_per_extruder[support_infill_extruder_nr], storage.support.layer_nr_max_filled_layer); - const size_t support_roof_extruder_nr = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; // TODO: Support roof extruder should be configurable per object. + const size_t support_roof_extruder_nr + = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; // TODO: Support roof extruder should be configurable per object. max_print_height_per_extruder[support_roof_extruder_nr] = std::max(max_print_height_per_extruder[support_roof_extruder_nr], storage.support.layer_nr_max_filled_layer); - const size_t support_bottom_extruder_nr = mesh_group_settings.get("support_bottom_extruder_nr").extruder_nr; // TODO: Support bottom extruder should be configurable per object. + const size_t support_bottom_extruder_nr + = mesh_group_settings.get("support_bottom_extruder_nr").extruder_nr; // TODO: Support bottom extruder should be configurable per object. max_print_height_per_extruder[support_bottom_extruder_nr] = std::max(max_print_height_per_extruder[support_bottom_extruder_nr], storage.support.layer_nr_max_filled_layer); // Height of where the platform adhesion reaches. @@ -861,12 +895,12 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage case EPlatformAdhesion::SKIRT: case EPlatformAdhesion::BRIM: { - const std::vector skirt_brim_extruder_trains = mesh_group_settings.get>("skirt_brim_extruder_nr"); - for (ExtruderTrain* train : skirt_brim_extruder_trains) - { - const size_t skirt_brim_extruder_nr = train->extruder_nr; - max_print_height_per_extruder[skirt_brim_extruder_nr] = std::max(0, max_print_height_per_extruder[skirt_brim_extruder_nr]); // Includes layer 0. - } + const std::vector skirt_brim_extruder_trains = mesh_group_settings.get>("skirt_brim_extruder_nr"); + for (ExtruderTrain* train : skirt_brim_extruder_trains) + { + const size_t skirt_brim_extruder_nr = train->extruder_nr; + max_print_height_per_extruder[skirt_brim_extruder_nr] = std::max(0, max_print_height_per_extruder[skirt_brim_extruder_nr]); // Includes layer 0. + } break; } case EPlatformAdhesion::RAFT: @@ -874,9 +908,11 @@ void FffPolygonGenerator::computePrintHeightStatistics(SliceDataStorage& storage const size_t base_extruder_nr = mesh_group_settings.get("raft_base_extruder_nr").extruder_nr; max_print_height_per_extruder[base_extruder_nr] = std::max(-raft_layers, max_print_height_per_extruder[base_extruder_nr]); // Includes the lowest raft layer. const size_t interface_extruder_nr = mesh_group_settings.get("raft_interface_extruder_nr").extruder_nr; - max_print_height_per_extruder[interface_extruder_nr] = std::max(-raft_layers + 1, max_print_height_per_extruder[interface_extruder_nr]); // Includes the second-lowest raft layer. + max_print_height_per_extruder[interface_extruder_nr] + = std::max(-raft_layers + 1, max_print_height_per_extruder[interface_extruder_nr]); // Includes the second-lowest raft layer. const size_t surface_extruder_nr = mesh_group_settings.get("raft_surface_extruder_nr").extruder_nr; - max_print_height_per_extruder[surface_extruder_nr] = std::max(-1, max_print_height_per_extruder[surface_extruder_nr]); // Includes up to the first layer below the model (so -1). + max_print_height_per_extruder[surface_extruder_nr] + = std::max(-1, max_print_height_per_extruder[surface_extruder_nr]); // Includes up to the first layer below the model (so -1). break; } default: @@ -917,7 +953,8 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) const AngleDegrees angle = mesh_group_settings.get("ooze_shield_angle"); if (angle <= 89) { - const coord_t allowed_angle_offset = tan(mesh_group_settings.get("ooze_shield_angle")) * mesh_group_settings.get("layer_height"); // Allow for a 60deg angle in the oozeShield. + const coord_t allowed_angle_offset + = tan(mesh_group_settings.get("ooze_shield_angle")) * mesh_group_settings.get("layer_height"); // Allow for a 60deg angle in the oozeShield. for (LayerIndex layer_nr = 1; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++) { storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].unionPolygons(storage.oozeShield[layer_nr - 1].offset(-allowed_angle_offset)); @@ -941,13 +978,14 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) const auto& extruders = Application::getInstance().current_slice->scene.extruders; for (int extruder_nr = 0; extruder_nr < int(extruders.size()); extruder_nr++) { - if ( ! extruder_is_used[extruder_nr]) continue; + if (! extruder_is_used[extruder_nr]) + continue; max_line_width = std::max(max_line_width, extruders[extruder_nr].settings.get("skirt_brim_line_width")); } } for (LayerIndex layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++) { - storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].difference(storage.primeTower.getOuterPoly(layer_nr).offset(max_line_width / 2)); } } } @@ -962,10 +1000,10 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const coord_t layer_height = mesh_group_settings.get("layer_height"); - const unsigned int layer_skip = 500 / layer_height + 1; + const LayerIndex layer_skip{ 500 / layer_height + 1 }; Polygons& draft_shield = storage.draft_protection_shield; - for (unsigned int layer_nr = 0; layer_nr < storage.print_layer_count && layer_nr < draft_shield_layers; layer_nr += layer_skip) + for (LayerIndex layer_nr = 0; layer_nr < storage.print_layer_count && layer_nr < draft_shield_layers; layer_nr += layer_skip) { constexpr bool around_support = true; constexpr bool around_prime_tower = false; @@ -992,11 +1030,12 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) const auto& extruders = Application::getInstance().current_slice->scene.extruders; for (int extruder_nr = 0; extruder_nr < int(extruders.size()); extruder_nr++) { - if ( ! extruder_is_used[extruder_nr]) continue; + if (! extruder_is_used[extruder_nr]) + continue; max_line_width = std::max(max_line_width, extruders[extruder_nr].settings.get("skirt_brim_line_width")); } } - storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.getGroundPoly().offset(max_line_width / 2)); } } @@ -1047,12 +1086,16 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) const coord_t avg_dist_between_points = mesh.settings.get("magic_fuzzy_skin_point_dist"); const coord_t min_dist_between_points = avg_dist_between_points * 3 / 4; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value const coord_t range_random_point_dist = avg_dist_between_points / 2; - unsigned int start_layer_nr = (mesh.settings.get("adhesion_type") == EPlatformAdhesion::BRIM) ? 1 : 0; // don't make fuzzy skin on first layer if there's a brim + unsigned int start_layer_nr + = (mesh.settings.get("adhesion_type") == EPlatformAdhesion::BRIM) ? 1 : 0; // don't make fuzzy skin on first layer if there's a brim auto hole_area = Polygons(); - std::function accumulate_is_in_hole = [](const bool& prev_result, const ExtrusionJunction& junction) { return false; }; + std::function accumulate_is_in_hole = [](const bool& prev_result, const ExtrusionJunction& junction) + { + return false; + }; - for (unsigned int layer_nr = start_layer_nr; layer_nr < mesh.layers.size(); layer_nr++) + for (LayerIndex layer_nr = start_layer_nr; layer_nr < mesh.layers.size(); layer_nr++) { SliceLayer& layer = mesh.layers[layer_nr]; for (SliceLayerPart& part : layer.parts) @@ -1071,7 +1114,10 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) if (apply_outside_only) { hole_area = part.print_outline.getOutsidePolygons().offset(-line_width); - accumulate_is_in_hole = [&hole_area](const bool& prev_result, const ExtrusionJunction& junction) { return prev_result || hole_area.inside(junction.p); }; + accumulate_is_in_hole = [&hole_area](const bool& prev_result, const ExtrusionJunction& junction) + { + return prev_result || hole_area.inside(junction.p); + }; } for (auto& line : toolpath) { @@ -1087,7 +1133,8 @@ void FffPolygonGenerator::processFuzzyWalls(SliceMeshStorage& mesh) result.is_closed = line.is_closed; // generate points in between p0 and p1 - int64_t dist_left_over = (min_dist_between_points / 4) + rand() % (min_dist_between_points / 4); // the distance to be traversed on the line before making the first new point + int64_t dist_left_over + = (min_dist_between_points / 4) + rand() % (min_dist_between_points / 4); // the distance to be traversed on the line before making the first new point auto* p0 = &line.front(); for (auto& p1 : line) { diff --git a/src/GCodePathConfig.cpp b/src/GCodePathConfig.cpp index aeb9ab3933..2aacd198ad 100644 --- a/src/GCodePathConfig.cpp +++ b/src/GCodePathConfig.cpp @@ -1,109 +1,72 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher -#include "settings/types/LayerIndex.h" -#include "utils/IntPoint.h" // INT2MM #include "GCodePathConfig.h" -namespace cura -{ - -GCodePathConfig::GCodePathConfig(const GCodePathConfig& other) -: type(other.type) -, speed_derivatives(other.speed_derivatives) -, line_width(other.line_width) -, layer_thickness(other.layer_thickness) -, flow(other.flow) -, extrusion_mm3_per_mm(other.extrusion_mm3_per_mm) -, is_bridge_path(other.is_bridge_path) -, fan_speed(other.fan_speed) -{ -} - - - -GCodePathConfig::GCodePathConfig(const PrintFeatureType& type, const coord_t line_width, const coord_t layer_height, const Ratio& flow, const GCodePathConfig::SpeedDerivatives speed_derivatives, const bool is_bridge_path, const double fan_speed) -: type(type) -, speed_derivatives(speed_derivatives) -, line_width(line_width) -, layer_thickness(layer_height) -, flow(flow) -, extrusion_mm3_per_mm(calculateExtrusion()) -, is_bridge_path(is_bridge_path) -, fan_speed(fan_speed) -{ -} +#include "utils/IntPoint.h" // INT2MM -void GCodePathConfig::smoothSpeed(GCodePathConfig::SpeedDerivatives first_layer_config, const LayerIndex& layer_nr, const LayerIndex& max_speed_layer_nr) +namespace cura { - double max_speed_layer = max_speed_layer_nr; - double first_layer_speed = std::min(speed_derivatives.speed, first_layer_config.speed); - double first_layer_acceleration = std::min(speed_derivatives.acceleration, first_layer_config.acceleration); - double first_layer_jerk = std::min(speed_derivatives.jerk, first_layer_config.jerk); - speed_derivatives.speed = (speed_derivatives.speed * layer_nr) / max_speed_layer + (first_layer_speed * (max_speed_layer - layer_nr) / max_speed_layer); - speed_derivatives.acceleration = (speed_derivatives.acceleration * layer_nr) / max_speed_layer + (first_layer_acceleration * (max_speed_layer - layer_nr) / max_speed_layer); - speed_derivatives.jerk = (speed_derivatives.jerk * layer_nr) / max_speed_layer + (first_layer_jerk * (max_speed_layer - layer_nr) / max_speed_layer); -} -double GCodePathConfig::getExtrusionMM3perMM() const +[[nodiscard]] double GCodePathConfig::getExtrusionMM3perMM() const noexcept { return extrusion_mm3_per_mm; } -Velocity GCodePathConfig::getSpeed() const +[[nodiscard]] Velocity GCodePathConfig::getSpeed() const noexcept { return speed_derivatives.speed; } -Acceleration GCodePathConfig::getAcceleration() const +[[nodiscard]] Acceleration GCodePathConfig::getAcceleration() const noexcept { return speed_derivatives.acceleration; } -Velocity GCodePathConfig::getJerk() const +[[nodiscard]] Velocity GCodePathConfig::getJerk() const noexcept { return speed_derivatives.jerk; } -coord_t GCodePathConfig::getLineWidth() const +[[nodiscard]] coord_t GCodePathConfig::getLineWidth() const noexcept { return line_width; } -coord_t GCodePathConfig::getLayerThickness() const +[[nodiscard]] coord_t GCodePathConfig::getLayerThickness() const noexcept { return layer_thickness; } -const PrintFeatureType& GCodePathConfig::getPrintFeatureType() const +[[nodiscard]] PrintFeatureType GCodePathConfig::getPrintFeatureType() const noexcept { return type; } -bool GCodePathConfig::isTravelPath() const +[[nodiscard]] bool GCodePathConfig::isTravelPath() const noexcept { return line_width == 0; } -bool GCodePathConfig::isBridgePath() const +[[nodiscard]] bool GCodePathConfig::isBridgePath() const noexcept { return is_bridge_path; } -double GCodePathConfig::getFanSpeed() const +[[nodiscard]] double GCodePathConfig::getFanSpeed() const noexcept { return fan_speed; } -Ratio GCodePathConfig::getFlowRatio() const +[[nodiscard]] Ratio GCodePathConfig::getFlowRatio() const noexcept { return flow; } -double GCodePathConfig::calculateExtrusion() const +[[nodiscard]] double GCodePathConfig::calculateExtrusion() const noexcept { return INT2MM(line_width) * INT2MM(layer_thickness) * double(flow); } -}//namespace cura +} // namespace cura diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index 7129ec2d52..05b1fa3f3f 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -1,21 +1,18 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "InsetOrderOptimizer.h" + #include "ExtruderTrain.h" #include "FffGcodeWriter.h" -#include "InsetOrderOptimizer.h" #include "LayerPlan.h" #include "utils/views/convert.h" #include "utils/views/dfs.h" -#include - -#include -#include #include #include +#include #include -#include #include #include #include @@ -26,6 +23,10 @@ #include #include #include +#include + +#include +#include namespace rg = ranges; namespace rv = ranges::views; @@ -33,22 +34,23 @@ namespace rv = ranges::views; namespace cura { -InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer, - const SliceDataStorage& storage, - LayerPlan& gcode_layer, - const Settings& settings, - const int extruder_nr, - const GCodePathConfig& inset_0_non_bridge_config, - const GCodePathConfig& inset_X_non_bridge_config, - const GCodePathConfig& inset_0_bridge_config, - const GCodePathConfig& inset_X_bridge_config, - const bool retract_before_outer_wall, - const coord_t wall_0_wipe_dist, - const coord_t wall_x_wipe_dist, - const size_t wall_0_extruder_nr, - const size_t wall_x_extruder_nr, - const ZSeamConfig& z_seam_config, - const std::vector& paths) +InsetOrderOptimizer::InsetOrderOptimizer( + const FffGcodeWriter& gcode_writer, + const SliceDataStorage& storage, + LayerPlan& gcode_layer, + const Settings& settings, + const int extruder_nr, + const GCodePathConfig& inset_0_non_bridge_config, + const GCodePathConfig& inset_X_non_bridge_config, + const GCodePathConfig& inset_0_bridge_config, + const GCodePathConfig& inset_X_bridge_config, + const bool retract_before_outer_wall, + const coord_t wall_0_wipe_dist, + const coord_t wall_x_wipe_dist, + const size_t wall_0_extruder_nr, + const size_t wall_x_extruder_nr, + const ZSeamConfig& z_seam_config, + const std::vector& paths) : gcode_writer(gcode_writer) , storage(storage) , gcode_layer(gcode_layer) @@ -91,10 +93,11 @@ bool InsetOrderOptimizer::addToLayer() constexpr bool detect_loops = false; constexpr Polygons* combing_boundary = nullptr; - constexpr bool group_outer_walls = true; + const auto group_outer_walls = settings.get("group_outer_walls"); // When we alternate walls, also alternate the direction at which the first wall starts in. // On even layers we start with normal direction, on odd layers with inverted direction. - PathOrderOptimizer order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse, order, group_outer_walls); + PathOrderOptimizer + order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse, order, group_outer_walls); for (const auto& line : walls_to_be_added) { @@ -157,7 +160,7 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& { const auto poly = std::get<1>(locator); const auto line = std::get<0>(locator); - return LineLoc { + return LineLoc{ .line = line, .poly = poly, .area = line->is_closed ? poly.area() : 0.0, @@ -166,7 +169,13 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& | rg::to_vector; // Sort polygons by increasing area, we are building the graph from the leaves (smallest area) upwards. - rg::sort( locator_view, [](const auto& lhs, const auto& rhs) { return std::abs(lhs) < std::abs(rhs); }, &LineLoc::area); + rg::sort( + locator_view, + [](const auto& lhs, const auto& rhs) + { + return std::abs(lhs) < std::abs(rhs); + }, + &LineLoc::area); // Create a bi-direction directed acyclic graph (Tree). Where polygon B is a child of A if B is inside A. The root of the graph is // the polygon that contains all other polygons. The leaves are polygons that contain no polygons. @@ -209,20 +218,19 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& // - mark all reachable nodes with their depth from the root // - find hole roots, these are the innermost polygons enclosing a hole { - const std::function initialize_nodes = - [graph, root, &hole_roots, &min_node, &min_depth] - (const auto current_node, const auto depth) - { - min_node[current_node] = root; - min_depth[current_node] = depth; + const std::function initialize_nodes + = [graph, root, &hole_roots, &min_node, &min_depth](const auto current_node, const auto depth) + { + min_node[current_node] = root; + min_depth[current_node] = depth; - // find hole roots (defined by a positive area in clipper1), these are leaves of the tree structure - // as odd walls are also leaves we filter them out by adding a non-zero area check - if (current_node != root && graph.count(current_node) == 1 && current_node->line->is_closed && current_node->area > 0) - { - hole_roots.push_back(current_node); - } - }; + // find hole roots (defined by a positive area in clipper1), these are leaves of the tree structure + // as odd walls are also leaves we filter them out by adding a non-zero area check + if (current_node != root && graph.count(current_node) == 1 && current_node->line->is_closed && current_node->area > 0) + { + hole_roots.push_back(current_node); + } + }; actions::dfs_depth_state(root, graph, initialize_nodes); }; @@ -233,16 +241,14 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& { for (auto& hole_root : hole_roots) { - const std::function update_nodes = - [hole_root, &min_depth, &min_node] - (const auto& current_node, auto depth) + const std::function update_nodes = [hole_root, &min_depth, &min_node](const auto& current_node, auto depth) + { + if (depth < min_depth[current_node]) { - if (depth < min_depth[current_node]) - { - min_depth[current_node] = depth; - min_node[current_node] = hole_root; - } - }; + min_depth[current_node] = depth; + min_node[current_node] = hole_root; + } + }; actions::dfs_depth_state(hole_root, graph, update_nodes); } @@ -252,22 +258,21 @@ InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const auto& // the depth is closest to root $r$ { const LineLoc* root_ = root; - const std::function set_order_constraints = - [&order, &min_node, &root_, graph, outer_to_inner] - (const auto& current_node, const auto& parent_node) + const std::function set_order_constraints + = [&order, &min_node, &root_, graph, outer_to_inner](const auto& current_node, const auto& parent_node) + { + if (min_node[current_node] == root_ && parent_node != nullptr) { - if (min_node[current_node] == root_ && parent_node != nullptr) - { - if (outer_to_inner) - { - order.insert(std::make_pair(parent_node->line, current_node->line)); - } - else - { - order.insert(std::make_pair(current_node->line, parent_node->line)); - } - } - }; + if (outer_to_inner) + { + order.insert(std::make_pair(parent_node->line, current_node->line)); + } + else + { + order.insert(std::make_pair(current_node->line, parent_node->line)); + } + } + }; actions::dfs_parent_state(root, graph, set_order_constraints); @@ -354,7 +359,7 @@ std::vector InsetOrderOptimizer::getWallsToBeAdded(const bool rev { if (paths.empty()) { - return { }; + return {}; } rg::any_view view; if (reverse) diff --git a/src/InterlockingGenerator.cpp b/src/InterlockingGenerator.cpp index c9e6cc8b89..6310a7ef16 100644 --- a/src/InterlockingGenerator.cpp +++ b/src/InterlockingGenerator.cpp @@ -1,21 +1,22 @@ -//Copyright (c) 2023 UltiMaker -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "InterlockingGenerator.h" -#include // max - #include "Application.h" #include "Slice.h" +#include "settings/types/LayerIndex.h" #include "slicer.h" -#include "utils/polygonUtils.h" #include "utils/VoxelUtils.h" +#include "utils/polygonUtils.h" #include #include #include #include +#include // max + namespace cura { @@ -43,7 +44,7 @@ void InterlockingGenerator::generateInterlockingStructure(std::vector& continue; } - if (extruder_nr_a == extruder_nr_b || !mesh_a.mesh->getAABB().expand(ignored_gap).hit(mesh_b.mesh->getAABB())) + if (extruder_nr_a == extruder_nr_b || ! mesh_a.mesh->getAABB().expand(ignored_gap).hit(mesh_b.mesh->getAABB())) { // early out for when meshes don't share any overlap in their bounding box continue; @@ -85,7 +86,7 @@ std::pair InterlockingGenerator::growBorderAreasPerpendicula from_border_b = temp_b.difference(temp_a); } - return { from_border_a, from_border_b}; + return { from_border_a, from_border_b }; } void InterlockingGenerator::handleThinAreas(const std::unordered_set& has_all_meshes) const @@ -132,8 +133,12 @@ void InterlockingGenerator::handleThinAreas(const std::unordered_set const Polygons large_b{ polys_b.offset(-detect).offset(detect) }; // Derive the area that the thin areas need to expand into (so the added areas to the thin strips) from the information we already have. - const Polygons thin_expansion_a{ large_b.intersection(polys_a.difference(large_a).offset(expand)).intersection(near_interlock_per_layer[layer_nr]).intersection(from_border_a).offset(rounding_errors) }; - const Polygons thin_expansion_b{ large_a.intersection(polys_b.difference(large_b).offset(expand)).intersection(near_interlock_per_layer[layer_nr]).intersection(from_border_b).offset(rounding_errors) }; + const Polygons thin_expansion_a{ + large_b.intersection(polys_a.difference(large_a).offset(expand)).intersection(near_interlock_per_layer[layer_nr]).intersection(from_border_a).offset(rounding_errors) + }; + const Polygons thin_expansion_b{ + large_a.intersection(polys_b.difference(large_b).offset(expand)).intersection(near_interlock_per_layer[layer_nr]).intersection(from_border_b).offset(rounding_errors) + }; // Expanded thin areas of the opposing polygon should 'eat into' the larger areas of the polygon, // and conversely, add the expansions to their own thin areas. @@ -175,9 +180,9 @@ std::vector> InterlockingGenerator::getShellVoxel // mark all cells which contain some boundary for (size_t mesh_idx = 0; mesh_idx < 2; mesh_idx++) { - Slicer* mesh = (mesh_idx == 0)? &mesh_a : &mesh_b; + Slicer* mesh = (mesh_idx == 0) ? &mesh_a : &mesh_b; std::unordered_set& mesh_voxels = voxels_per_mesh[mesh_idx]; - + std::vector rotated_polygons_per_layer(mesh->layers.size()); for (size_t layer_nr = 0; layer_nr < mesh->layers.size(); layer_nr++) { @@ -185,7 +190,7 @@ std::vector> InterlockingGenerator::getShellVoxel rotated_polygons_per_layer[layer_nr] = layer.polygons; rotated_polygons_per_layer[layer_nr].applyMatrix(rotation); } - + addBoundaryCells(rotated_polygons_per_layer, kernel, mesh_voxels); } @@ -194,7 +199,11 @@ std::vector> InterlockingGenerator::getShellVoxel void InterlockingGenerator::addBoundaryCells(const std::vector& layers, const DilationKernel& kernel, std::unordered_set& cells) const { - auto voxel_emplacer = [&cells](GridPoint3 p) { cells.emplace(p); return true; }; + auto voxel_emplacer = [&cells](GridPoint3 p) + { + cells.emplace(p); + return true; + }; for (size_t layer_nr = 0; layer_nr < layers.size(); layer_nr++) { @@ -215,10 +224,10 @@ std::vector InterlockingGenerator::computeUnionedVolumeRegions() const const size_t max_layer_count = std::max(mesh_a.layers.size(), mesh_b.layers.size()) + 1; // introduce ghost layer on top for correct skin computation of topmost layer. std::vector layer_regions(max_layer_count); - for (unsigned int layer_nr = 0; layer_nr < max_layer_count; layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < max_layer_count; layer_nr++) { Polygons& layer_region = layer_regions[layer_nr]; - for (Slicer* mesh : {&mesh_a, &mesh_b}) + for (Slicer* mesh : { &mesh_a, &mesh_b }) { if (layer_nr >= mesh->layers.size()) { @@ -241,9 +250,9 @@ std::vector> InterlockingGenerator::generateMicrostructure const coord_t beam_w_sum = beam_width_a + beam_width_b; const coord_t middle = cell_size.x * beam_width_a / beam_w_sum; const coord_t width[2] = { middle, cell_size.x - middle }; - for (size_t mesh_idx : {0, 1}) + for (size_t mesh_idx : { 0, 1 }) { - Point offset(mesh_idx? middle : 0, 0); + Point offset(mesh_idx ? middle : 0, 0); Point area_size(width[mesh_idx], cell_size.y); PolygonRef poly = cell_area_per_mesh_per_layer[0][mesh_idx].newPoly(); @@ -288,7 +297,7 @@ void InterlockingGenerator::applyMicrostructureToOutlines(const std::unordered_s Point3 bottom_corner = vu.toLowerCorner(grid_loc); for (size_t mesh_idx = 0; mesh_idx < 2; mesh_idx++) { - for (unsigned int layer_nr = bottom_corner.z; layer_nr < bottom_corner.z + cell_size.z && layer_nr < max_layer_count; layer_nr += beam_layer_count) + for (LayerIndex layer_nr = bottom_corner.z; layer_nr < bottom_corner.z + cell_size.z && layer_nr < max_layer_count; layer_nr += beam_layer_count) { Polygons areas_here = cell_area_per_mesh_per_layer[(layer_nr / beam_layer_count) % cell_area_per_mesh_per_layer.size()][mesh_idx]; areas_here.translate(Point(bottom_corner.x, bottom_corner.y)); @@ -309,7 +318,7 @@ void InterlockingGenerator::applyMicrostructureToOutlines(const std::unordered_s for (size_t mesh_idx = 0; mesh_idx < 2; mesh_idx++) { - Slicer* mesh = (mesh_idx == 0)? &mesh_a : &mesh_b; + Slicer* mesh = (mesh_idx == 0) ? &mesh_a : &mesh_b; for (size_t layer_nr = 0; layer_nr < max_layer_count; layer_nr++) { if (layer_nr >= mesh->layers.size()) @@ -321,13 +330,14 @@ void InterlockingGenerator::applyMicrostructureToOutlines(const std::unordered_s layer_outlines.applyMatrix(unapply_rotation); const Polygons areas_here = structure_per_layer[mesh_idx][layer_nr / beam_layer_count].intersection(layer_outlines); - const Polygons& areas_other = structure_per_layer[ ! mesh_idx][layer_nr / beam_layer_count]; + const Polygons& areas_other = structure_per_layer[! mesh_idx][layer_nr / beam_layer_count]; SlicerLayer& layer = mesh->layers[layer_nr]; - layer.polygons = layer.polygons.difference(areas_other) // reduce layer areas inward with beams from other mesh - .unionPolygons(areas_here); // extend layer areas outward with newly added beams + layer.polygons = layer.polygons + .difference(areas_other) // reduce layer areas inward with beams from other mesh + .unionPolygons(areas_here); // extend layer areas outward with newly added beams } } } -}//namespace cura +} // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 43def5f5db..52305a9521 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1,29 +1,33 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "LayerPlan.h" + #include #include #include #include #include +#include #include #include "Application.h" //To communicate layer view data. #include "ExtruderTrain.h" -#include "LayerPlan.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. #include "Slice.h" #include "WipeScriptConfig.h" #include "communication/Communication.h" #include "pathPlanning/Comb.h" #include "pathPlanning/CombPaths.h" +#include "plugins/slots.h" #include "raft.h" // getTotalExtraLayers #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "utils/Simplify.h" #include "utils/linearAlg2D.h" #include "utils/polygonUtils.h" +#include "utils/section_type.h" namespace cura { @@ -31,91 +35,32 @@ namespace cura constexpr int MINIMUM_LINE_LENGTH = 5; // in uM. Generated lines shorter than this may be discarded constexpr int MINIMUM_SQUARED_LINE_LENGTH = MINIMUM_LINE_LENGTH * MINIMUM_LINE_LENGTH; -ExtruderPlan::ExtruderPlan -( - const size_t extruder, - const LayerIndex layer_nr, - const bool is_initial_layer, - const bool is_raft_layer, - const coord_t layer_thickness, - const FanSpeedLayerTimeSettings& fan_speed_layer_time_settings, - const RetractionConfig& retraction_config -) : - heated_pre_travel_time(0), - required_start_temperature(-1), - extruder_nr(extruder), - layer_nr(layer_nr), - is_initial_layer(is_initial_layer), - is_raft_layer(is_raft_layer), - layer_thickness(layer_thickness), - fan_speed_layer_time_settings(fan_speed_layer_time_settings), - retraction_config(retraction_config), - extraTime(0.0), - temperatureFactor(0.0), - slowest_path_speed(0.0) -{ -} - -void ExtruderPlan::handleInserts(const size_t path_idx, GCodeExport& gcode, const double& cumulative_path_time) -{ - while (! inserts.empty() && path_idx >= inserts.front().path_idx && inserts.front().time_after_path_start < cumulative_path_time) - { // handle the Insert to be inserted before this path_idx (and all inserts not handled yet) - inserts.front().write(gcode); - inserts.pop_front(); - } -} - -void ExtruderPlan::handleAllRemainingInserts(GCodeExport& gcode) -{ - while (! inserts.empty()) - { // handle the Insert to be inserted before this path_idx (and all inserts not handled yet) - NozzleTempInsert& insert = inserts.front(); - insert.write(gcode); - inserts.pop_front(); - } -} - -void ExtruderPlan::setFanSpeed(double _fan_speed) -{ - fan_speed = _fan_speed; -} -double ExtruderPlan::getFanSpeed() -{ - return fan_speed; -} - -void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compensation) -{ - constexpr double epsilon_speed_factor = 0.001; // Don't put on actual 'limit double minimum', because we don't want printers to stall. - for (auto& path : paths) - { - const double nominal_width_for_path = static_cast(path.config->getLineWidth()); - if (path.width_factor <= 0.0 || nominal_width_for_path <= 0.0 || path.config->isTravelPath() || path.config->isBridgePath()) - { - continue; - } - const double line_width_for_path = path.width_factor * nominal_width_for_path; - path.speed_back_pressure_factor = std::max(epsilon_speed_factor, 1.0 + (nominal_width_for_path / line_width_for_path - 1.0) * back_pressure_compensation); - } -} -GCodePath* LayerPlan::getLatestPathWithConfig(const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, bool spiralize, const Ratio speed_factor) +GCodePath* LayerPlan::getLatestPathWithConfig( + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const coord_t z_offset, + const Ratio flow, + const Ratio width_factor, + const bool spiralize, + const Ratio speed_factor) { std::vector& paths = extruder_plans.back().paths; - if - ( - paths.size() > 0 && - paths.back().config == &config && - ! paths.back().done && - paths.back().flow == flow && - paths.back().width_factor == width_factor && - paths.back().speed_factor == speed_factor && - paths.back().mesh == current_mesh - ) // spiralize can only change when a travel path is in between + if (paths.size() > 0 && paths.back().config == config && ! paths.back().done && paths.back().flow == flow && paths.back().width_factor == width_factor + && paths.back().speed_factor == speed_factor && paths.back().z_offset == z_offset + && paths.back().mesh == current_mesh) // spiralize can only change when a travel path is in between { return &paths.back(); } - paths.emplace_back(config, current_mesh, space_fill_type, flow, width_factor, spiralize, speed_factor); + paths.emplace_back(GCodePath{ .z_offset = z_offset, + .config = config, + .mesh = current_mesh, + .space_fill_type = space_fill_type, + .flow = flow, + .width_factor = width_factor, + .spiralize = spiralize, + .speed_factor = speed_factor }); + GCodePath* ret = &paths.back(); ret->skip_agressive_merge_hint = mode_skip_agressive_merge; return ret; @@ -133,8 +78,7 @@ void LayerPlan::forceNewPathStart() paths[paths.size() - 1].done = true; } -LayerPlan::LayerPlan -( +LayerPlan::LayerPlan( const SliceDataStorage& storage, LayerIndex layer_nr, coord_t z, @@ -143,26 +87,26 @@ LayerPlan::LayerPlan const std::vector& fan_speed_layer_time_settings_per_extruder, coord_t comb_boundary_offset, coord_t comb_move_inside_distance, - coord_t travel_avoid_distance -) : - configs_storage(storage, layer_nr, layer_thickness), - z(z), - final_travel_z(z), - mode_skip_agressive_merge(false), - storage(storage), - layer_nr(layer_nr), - is_initial_layer(layer_nr == 0 - static_cast(Raft::getTotalExtraLayers())), - is_raft_layer(layer_nr < 0 - static_cast(Raft::getFillerLayerCount())), - layer_thickness(layer_thickness), - has_prime_tower_planned_per_extruder(Application::getInstance().current_slice->scene.extruders.size(), false), - current_mesh(nullptr), - last_extruder_previous_layer(start_extruder), - last_planned_extruder(&Application::getInstance().current_slice->scene.extruders[start_extruder]), - first_travel_destination_is_inside(false), // set properly when addTravel is called for the first time (otherwise not set properly) - comb_boundary_minimum(computeCombBoundary(CombBoundary::MINIMUM)), - comb_boundary_preferred(computeCombBoundary(CombBoundary::PREFERRED)), - comb_move_inside_distance(comb_move_inside_distance), - fan_speed_layer_time_settings_per_extruder(fan_speed_layer_time_settings_per_extruder) + coord_t travel_avoid_distance) + : configs_storage(storage, layer_nr, layer_thickness) + , z(z) + , final_travel_z(z) + , mode_skip_agressive_merge(false) + , storage(storage) + , layer_nr(layer_nr) + , is_initial_layer(layer_nr == 0 - static_cast(Raft::getTotalExtraLayers())) + , is_raft_layer(layer_nr < 0 - static_cast(Raft::getFillerLayerCount())) + , layer_thickness(layer_thickness) + , has_prime_tower_planned_per_extruder(Application::getInstance().current_slice->scene.extruders.size(), false) + , current_mesh(nullptr) + , last_extruder_previous_layer(start_extruder) + , last_planned_extruder(&Application::getInstance().current_slice->scene.extruders[start_extruder]) + , first_travel_destination_is_inside(false) + , // set properly when addTravel is called for the first time (otherwise not set properly) + comb_boundary_minimum(computeCombBoundary(CombBoundary::MINIMUM)) + , comb_boundary_preferred(computeCombBoundary(CombBoundary::PREFERRED)) + , comb_move_inside_distance(comb_move_inside_distance) + , fan_speed_layer_time_settings_per_extruder(fan_speed_layer_time_settings_per_extruder) { size_t current_extruder = start_extruder; was_inside = true; // not used, because the first travel move is bogus @@ -180,7 +124,14 @@ LayerPlan::LayerPlan layer_start_pos_per_extruder.emplace_back(extruder.settings.get("layer_start_x"), extruder.settings.get("layer_start_y")); } extruder_plans.reserve(Application::getInstance().current_slice->scene.extruders.size()); - extruder_plans.emplace_back(current_extruder, layer_nr, is_initial_layer, is_raft_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[current_extruder], storage.retraction_wipe_config_per_extruder[current_extruder].retraction_config); + extruder_plans.emplace_back( + current_extruder, + layer_nr, + is_initial_layer, + is_raft_layer, + layer_thickness, + fan_speed_layer_time_settings_per_extruder[current_extruder], + storage.retraction_wipe_config_per_extruder[current_extruder].retraction_config); for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) { // Skirt and brim. @@ -211,11 +162,12 @@ Polygons LayerPlan::computeCombBoundary(const CombBoundary boundary_type) } else { - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { + const auto& mesh = *mesh_ptr; const SliceLayer& layer = mesh.layers[static_cast(layer_nr)]; // don't process infill_mesh or anti_overhang_mesh - if (mesh.settings.get("infill_mesh") && mesh.settings.get("anti_overhang_mesh")) + if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) { continue; } @@ -303,7 +255,14 @@ bool LayerPlan::setExtruder(const size_t extruder_nr) { // first extruder plan in a layer might be empty, cause it is made with the last extruder planned in the previous layer extruder_plans.back().extruder_nr = extruder_nr; } - extruder_plans.emplace_back(extruder_nr, layer_nr, is_initial_layer, is_raft_layer, layer_thickness, fan_speed_layer_time_settings_per_extruder[extruder_nr], storage.retraction_wipe_config_per_extruder[extruder_nr].retraction_config); + extruder_plans.emplace_back( + extruder_nr, + layer_nr, + is_initial_layer, + is_raft_layer, + layer_thickness, + fan_speed_layer_time_settings_per_extruder[extruder_nr], + storage.retraction_wipe_config_per_extruder[extruder_nr].retraction_config); assert(extruder_plans.size() <= Application::getInstance().current_slice->scene.extruders.size() && "Never use the same extruder twice on one layer!"); last_planned_extruder = &Application::getInstance().current_slice->scene.extruders[extruder_nr]; @@ -327,7 +286,7 @@ bool LayerPlan::setExtruder(const size_t extruder_nr) } return true; } -void LayerPlan::setMesh(const SliceMeshStorage* mesh) +void LayerPlan::setMesh(const std::shared_ptr& mesh) { current_mesh = mesh; } @@ -370,13 +329,14 @@ std::optional> LayerPlan::getFirstTravelDestinationState( return ret; } -GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) +GCodePath& LayerPlan::addTravel(const Point& p, const bool force_retract, const coord_t z_offset) { const GCodePathConfig& travel_config = configs_storage.travel_config_per_extruder[getExtruder()]; - const RetractionConfig& retraction_config = current_mesh ? current_mesh->retraction_wipe_config.retraction_config : storage.retraction_wipe_config_per_extruder[getExtruder()].retraction_config; + const RetractionConfig& retraction_config + = current_mesh ? current_mesh->retraction_wipe_config.retraction_config : storage.retraction_wipe_config_per_extruder[getExtruder()].retraction_config; - GCodePath* path = getLatestPathWithConfig(travel_config, SpaceFillType::None); + GCodePath* path = getLatestPathWithConfig(travel_config, SpaceFillType::None, z_offset); bool combed = false; @@ -422,20 +382,17 @@ GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) bool unretract_before_last_travel_move = false; // Decided when calculating the combing const bool perform_z_hops = mesh_or_extruder_settings.get("retraction_hop_enabled"); const bool perform_z_hops_only_when_collides = mesh_or_extruder_settings.get("retraction_hop_only_when_collides"); - combed = - comb->calc - ( - perform_z_hops, - perform_z_hops_only_when_collides, - *extruder, - *last_planned_position, - p, - combPaths, - was_inside, - is_inside, - max_distance_ignored, - unretract_before_last_travel_move - ); + combed = comb->calc( + perform_z_hops, + perform_z_hops_only_when_collides, + *extruder, + *last_planned_position, + p, + combPaths, + was_inside, + is_inside, + max_distance_ignored, + unretract_before_last_travel_move); if (combed) { bool retract = path->retract || (combPaths.size() > 1 && retraction_enable); @@ -503,8 +460,9 @@ GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) { if (was_inside) // when the previous location was from printing something which is considered inside (not support or prime tower etc) { // then move inside the printed part, so that we don't ooze on the outer wall while retraction, but on the inside of the print. - assert (extruder != nullptr); - coord_t innermost_wall_line_width = mesh_or_extruder_settings.get((mesh_or_extruder_settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); + assert(extruder != nullptr); + coord_t innermost_wall_line_width + = mesh_or_extruder_settings.get((mesh_or_extruder_settings.get("wall_line_count") > 1) ? "wall_line_width_x" : "wall_line_width_0"); if (layer_nr == 0) { innermost_wall_line_width *= mesh_or_extruder_settings.get("initial_layer_line_width_factor"); @@ -523,7 +481,7 @@ GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) return ret; } -GCodePath& LayerPlan::addTravel_simple(Point p, GCodePath* path) +GCodePath& LayerPlan::addTravel_simple(const Point& p, GCodePath* path) { bool is_first_travel_of_layer = ! static_cast(last_planned_position); if (is_first_travel_of_layer) @@ -550,23 +508,39 @@ void LayerPlan::planPrime(const float& prime_blob_wipe_length) forceNewPathStart(); } -void LayerPlan::addExtrusionMove(Point p, const GCodePathConfig& config, SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, bool spiralize, Ratio speed_factor, double fan_speed) +void LayerPlan::addExtrusionMove( + const Point p, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const Ratio& flow, + const Ratio width_factor, + const bool spiralize, + const Ratio speed_factor, + const double fan_speed) { - GCodePath* path = getLatestPathWithConfig(config, space_fill_type, flow, width_factor, spiralize, speed_factor); + GCodePath* path = getLatestPathWithConfig(config, space_fill_type, config.z_offset, flow, width_factor, spiralize, speed_factor); path->points.push_back(p); path->setFanSpeed(fan_speed); if (! static_cast(first_extrusion_acc_jerk)) { - first_extrusion_acc_jerk = std::make_pair(path->config->getAcceleration(), path->config->getJerk()); + first_extrusion_acc_jerk = std::make_pair(path->config.getAcceleration(), path->config.getJerk()); } last_planned_position = p; } -void LayerPlan::addPolygon(ConstPolygonRef polygon, int start_idx, const bool backwards, const GCodePathConfig& config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio& flow_ratio, bool always_retract) +void LayerPlan::addPolygon( + ConstPolygonRef polygon, + int start_idx, + const bool backwards, + const GCodePathConfig& config, + coord_t wall_0_wipe_dist, + bool spiralize, + const Ratio& flow_ratio, + bool always_retract) { constexpr Ratio width_ratio = 1.0_r; // Not printed with variable line width. Point p0 = polygon[start_idx]; - addTravel(p0, always_retract); + addTravel(p0, always_retract, config.z_offset); const int direction = backwards ? -1 : 1; for (size_t point_idx = 1; point_idx < polygon.size(); point_idx++) { @@ -610,15 +584,16 @@ void LayerPlan::addPolygon(ConstPolygonRef polygon, int start_idx, const bool ba } } -void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, - const GCodePathConfig& config, - const ZSeamConfig& z_seam_config, - coord_t wall_0_wipe_dist, - bool spiralize, - const Ratio flow_ratio, - bool always_retract, - bool reverse_order, - const std::optional start_near_location) +void LayerPlan::addPolygonsByOptimizer( + const Polygons& polygons, + const GCodePathConfig& config, + const ZSeamConfig& z_seam_config, + coord_t wall_0_wipe_dist, + bool spiralize, + const Ratio flow_ratio, + bool always_retract, + bool reverse_order, + const std::optional start_near_location) { if (polygons.empty()) { @@ -650,16 +625,17 @@ void LayerPlan::addPolygonsByOptimizer(const Polygons& polygons, static constexpr float max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate -void LayerPlan::addWallLine(const Point& p0, - const Point& p1, - const Settings& settings, - const GCodePathConfig& non_bridge_config, - const GCodePathConfig& bridge_config, - float flow, - const Ratio width_factor, - float& non_bridge_line_volume, - Ratio speed_factor, - double distance_to_bridge_start) +void LayerPlan::addWallLine( + const Point& p0, + const Point& p1, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + float flow, + const Ratio width_factor, + float& non_bridge_line_volume, + Ratio speed_factor, + double distance_to_bridge_start) { const coord_t min_line_len = 5; // we ignore lines less than 5um long const double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length @@ -685,11 +661,13 @@ void LayerPlan::addWallLine(const Point& p0, while (distance_to_line_end > min_line_len) { // if we are accelerating after a bridge line, the segment length is less than the whole line length - Point segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) ? line_end : cur_point + (line_end - cur_point) * acceleration_segment_len / distance_to_line_end; + Point segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) + ? line_end + : cur_point + (line_end - cur_point) * acceleration_segment_len / distance_to_line_end; // flow required for the next line segment - when accelerating after a bridge segment, the flow is increased in inverse proportion to the speed_factor // so the slower the feedrate, the greater the flow - the idea is to get the extruder back to normal pressure as quickly as possible - const float segment_flow = (speed_factor < 1) ? flow * (1 / speed_factor) : flow; + const float segment_flow = (speed_factor > 1) ? flow * (1 / speed_factor) : flow; // if a bridge is present in this wall, this particular segment may need to be partially or wholely coasted if (distance_to_bridge_start > 0) @@ -714,7 +692,14 @@ void LayerPlan::addWallLine(const Point& p0, if ((len - coast_dist) > min_line_len) { // segment is longer than coast distance so extrude using non-bridge config to start of coast - addExtrusionMove(segment_end + coast_dist * (cur_point - segment_end) / len, non_bridge_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, speed_factor); + addExtrusionMove( + segment_end + coast_dist * (cur_point - segment_end) / len, + non_bridge_config, + SpaceFillType::Polygons, + segment_flow, + width_factor, + spiralize, + speed_factor); } // then coast to start of bridge segment constexpr Ratio flow = 0.0_r; // Coasting has no flow rate. @@ -723,13 +708,14 @@ void LayerPlan::addWallLine(const Point& p0, else { // no coasting required, just normal segment using non-bridge config - addExtrusionMove(segment_end, - non_bridge_config, - SpaceFillType::Polygons, - segment_flow, - width_factor, - spiralize, - (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor); + addExtrusionMove( + segment_end, + non_bridge_config, + SpaceFillType::Polygons, + segment_flow, + width_factor, + spiralize, + (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor); } distance_to_bridge_start -= len; @@ -737,20 +723,21 @@ void LayerPlan::addWallLine(const Point& p0, else { // no coasting required, just normal segment using non-bridge config - addExtrusionMove(segment_end, - non_bridge_config, - SpaceFillType::Polygons, - segment_flow, - width_factor, - spiralize, - (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor); + addExtrusionMove( + segment_end, + non_bridge_config, + SpaceFillType::Polygons, + segment_flow, + width_factor, + spiralize, + (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? speed_factor : overhang_speed_factor); } non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * width_factor * speed_factor * non_bridge_config.getSpeed(); cur_point = segment_end; speed_factor = 1 - (1 - speed_factor) * acceleration_factor; if (speed_factor >= 0.9) { - speed_factor = 1; + speed_factor = 1.0; } distance_to_line_end = vSize(cur_point - line_end); } @@ -759,7 +746,14 @@ void LayerPlan::addWallLine(const Point& p0, if (bridge_wall_mask.empty()) { // no bridges required - addExtrusionMove(p1, non_bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? 1.0_r : overhang_speed_factor); + addExtrusionMove( + p1, + non_bridge_config, + SpaceFillType::Polygons, + flow, + width_factor, + spiralize, + (overhang_mask.empty() || (! overhang_mask.inside(p0, true) && ! overhang_mask.inside(p1, true))) ? 1.0_r : overhang_speed_factor); } else { @@ -851,7 +845,15 @@ void LayerPlan::addWallLine(const Point& p0, } } -void LayerPlan::addWall(ConstPolygonRef wall, int start_idx, const Settings& settings, const GCodePathConfig& non_bridge_config, const GCodePathConfig& bridge_config, coord_t wall_0_wipe_dist, float flow_ratio, bool always_retract) +void LayerPlan::addWall( + ConstPolygonRef wall, + int start_idx, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + float flow_ratio, + bool always_retract) { // TODO: Deprecated in favor of ExtrusionJunction version below. if (wall.size() < 3) @@ -860,10 +862,18 @@ void LayerPlan::addWall(ConstPolygonRef wall, int start_idx, const Settings& set } constexpr size_t dummy_perimeter_id = 0; // <-- Here, don't care about which perimeter any more. - const coord_t nominal_line_width = non_bridge_config.getLineWidth(); // <-- The line width which it's 'supposed to' be will be used to adjust the flow ratio each time, this'll give a flow-ratio-multiplier of 1. + const coord_t nominal_line_width + = non_bridge_config + .getLineWidth(); // <-- The line width which it's 'supposed to' be will be used to adjust the flow ratio each time, this'll give a flow-ratio-multiplier of 1. ExtrusionLine ewall; - std::for_each(wall.begin(), wall.end(), [&dummy_perimeter_id, &nominal_line_width, &ewall](const Point& p) { ewall.emplace_back(p, nominal_line_width, dummy_perimeter_id); }); + std::for_each( + wall.begin(), + wall.end(), + [&dummy_perimeter_id, &nominal_line_width, &ewall](const Point& p) + { + ewall.emplace_back(p, nominal_line_width, dummy_perimeter_id); + }); ewall.emplace_back(*wall.begin(), nominal_line_width, dummy_perimeter_id); constexpr bool is_closed = true; constexpr bool is_reversed = false; @@ -871,17 +881,18 @@ void LayerPlan::addWall(ConstPolygonRef wall, int start_idx, const Settings& set addWall(ewall, start_idx, settings, non_bridge_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } -void LayerPlan::addWall(const ExtrusionLine& wall, - int start_idx, - const Settings& settings, - const GCodePathConfig& non_bridge_config, - const GCodePathConfig& bridge_config, - coord_t wall_0_wipe_dist, - float flow_ratio, - bool always_retract, - const bool is_closed, - const bool is_reversed, - const bool is_linked_path) +void LayerPlan::addWall( + const ExtrusionLine& wall, + int start_idx, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + float flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path) { if (wall.empty()) { @@ -899,7 +910,9 @@ void LayerPlan::addWall(const ExtrusionLine& wall, const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); - const Ratio nominal_line_width_multiplier = 1.0 / Ratio(non_bridge_config.getLineWidth()); // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + const Ratio nominal_line_width_multiplier{ + 1.0 / Ratio{ static_cast(non_bridge_config.getLineWidth()) } + }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this // helper function to calculate the distance from the start of the current wall line to the first bridge segment @@ -1047,12 +1060,29 @@ void LayerPlan::addWall(const ExtrusionLine& wall, if (is_small_feature) { constexpr bool spiralize = false; - addExtrusionMove(destination, non_bridge_config, SpaceFillType::Polygons, flow_ratio, line_width * nominal_line_width_multiplier, spiralize, small_feature_speed_factor); + addExtrusionMove( + destination, + non_bridge_config, + SpaceFillType::Polygons, + flow_ratio, + line_width * nominal_line_width_multiplier, + spiralize, + small_feature_speed_factor); } else { const Point origin = p0.p + normal(line_vector, piece_length * piece); - addWallLine(origin, destination, settings, non_bridge_config, bridge_config, flow_ratio, line_width * nominal_line_width_multiplier, non_bridge_line_volume, speed_factor, distance_to_bridge_start); + addWallLine( + origin, + destination, + settings, + non_bridge_config, + bridge_config, + flow_ratio, + line_width * nominal_line_width_multiplier, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start); } } @@ -1109,7 +1139,7 @@ void LayerPlan::addInfillWall(const ExtrusionLine& wall, const GCodePathConfig& for (const auto& junction_n : wall) { - const Ratio width_factor = junction_n.w / Ratio(path_config.getLineWidth()); + const Ratio width_factor{ static_cast(junction_n.w) / Ratio{ static_cast(path_config.getLineWidth()) } }; constexpr SpaceFillType space_fill_type = SpaceFillType::Polygons; constexpr Ratio flow = 1.0_r; addExtrusionMove(junction_n.p, path_config, space_fill_type, flow, width_factor); @@ -1117,14 +1147,15 @@ void LayerPlan::addInfillWall(const ExtrusionLine& wall, const GCodePathConfig& } } -void LayerPlan::addWalls(const Polygons& walls, - const Settings& settings, - const GCodePathConfig& non_bridge_config, - const GCodePathConfig& bridge_config, - const ZSeamConfig& z_seam_config, - coord_t wall_0_wipe_dist, - float flow_ratio, - bool always_retract) +void LayerPlan::addWalls( + const Polygons& walls, + const Settings& settings, + const GCodePathConfig& non_bridge_config, + const GCodePathConfig& bridge_config, + const ZSeamConfig& z_seam_config, + coord_t wall_0_wipe_dist, + float flow_ratio, + bool always_retract) { // TODO: Deprecated in favor of ExtrusionJunction version below. PathOrderOptimizer orderOptimizer(getLastPlannedPositionOrStartingPosition(), z_seam_config); @@ -1140,16 +1171,17 @@ void LayerPlan::addWalls(const Polygons& walls, } -void LayerPlan::addLinesByOptimizer(const Polygons& polygons, - const GCodePathConfig& config, - const SpaceFillType space_fill_type, - const bool enable_travel_optimization, - const coord_t wipe_dist, - const Ratio flow_ratio, - const std::optional near_start_location, - const double fan_speed, - const bool reverse_print_direction, - const std::unordered_multimap& order_requirements) +void LayerPlan::addLinesByOptimizer( + const Polygons& polygons, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const bool enable_travel_optimization, + const coord_t wipe_dist, + const Ratio flow_ratio, + const std::optional near_start_location, + const double fan_speed, + const bool reverse_print_direction, + const std::unordered_multimap& order_requirements) { Polygons boundary; if (enable_travel_optimization && ! comb_boundary_minimum.empty()) @@ -1159,9 +1191,9 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, if (layer_nr >= 0) { // determine how much the skin/infill lines overlap the combing boundary - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh : storage.meshes) { - const coord_t overlap = std::max(mesh.settings.get("skin_overlap_mm"), mesh.settings.get("infill_overlap_mm")); + const coord_t overlap = std::max(mesh->settings.get("skin_overlap_mm"), mesh->settings.get("infill_overlap_mm")); if (overlap > dist) { dist = overlap; @@ -1174,7 +1206,13 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, boundary = Simplify(MM2INT(0.1), MM2INT(0.1), 0).polygon(boundary); } constexpr bool detect_loops = true; - PathOrderOptimizer order_optimizer(near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), ZSeamConfig(), detect_loops, &boundary, reverse_print_direction, order_requirements); + PathOrderOptimizer order_optimizer( + near_start_location.value_or(getLastPlannedPositionOrStartingPosition()), + ZSeamConfig(), + detect_loops, + &boundary, + reverse_print_direction, + order_requirements); for (size_t line_idx = 0; line_idx < polygons.size(); line_idx++) { order_optimizer.addPolyline(polygons[line_idx]); @@ -1185,7 +1223,13 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, } -void LayerPlan::addLinesInGivenOrder(const std::vector>& paths, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed) +void LayerPlan::addLinesInGivenOrder( + const std::vector>& paths, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const coord_t wipe_dist, + const Ratio flow_ratio, + const double fan_speed) { coord_t half_line_width = config.getLineWidth() / 2; coord_t line_width_2 = half_line_width * half_line_width; @@ -1209,7 +1253,7 @@ void LayerPlan::addLinesInGivenOrder(const std::vector order(monotonic_direction, max_adjacent_distance, last_position); @@ -1335,7 +1383,14 @@ void LayerPlan::addLinesMonotonic(const Polygons& area, addLinesByOptimizer(left_over, config, space_fill_type, true, wipe_dist, flow_ratio, getLastPlannedPositionOrStartingPosition(), fan_speed); } -void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRef wall, ConstPolygonRef last_wall, const int seam_vertex_idx, const int last_seam_vertex_idx, const bool is_top_layer, const bool is_bottom_layer) +void LayerPlan::spiralizeWallSlice( + const GCodePathConfig& config, + ConstPolygonRef wall, + ConstPolygonRef last_wall, + const int seam_vertex_idx, + const int last_seam_vertex_idx, + const bool is_top_layer, + const bool is_bottom_layer) { const bool smooth_contours = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("smooth_spiralized_contours"); constexpr bool spiralize = true; // In addExtrusionMove calls, enable spiralize and use nominal line width. @@ -1484,7 +1539,7 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ { const double minimalSpeed = fan_speed_layer_time_settings.cool_min_speed; const double travelTime = estimates.getTravelTime(); - const double extrudeTime = estimates.getExtrudeTime(); + const double extrudeTime = estimates.extrude_time; const double totalTime = travelTime + extrudeTime + time_other_extr_plans; constexpr double epsilon = 0.01; @@ -1503,10 +1558,10 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ double factor = 0.0; double target_speed = 0.0; - std::function slow_down_func - { - [&target_speed](const GCodePath& path) { return std::min(target_speed / (path.config->getSpeed() * path.speed_factor), 1.0); } - }; + std::function slow_down_func{ [&target_speed](const GCodePath& path) + { + return std::min(target_speed / (path.config.getSpeed() * path.speed_factor), 1.0); + } }; if (minExtrudeTime >= total_extrude_time_at_minimum_speed) { @@ -1525,8 +1580,9 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ { // Slowing down to the slowest path speed is not sufficient, need to slow down further to the minimum speed. // Linear interpolate between total_extrude_time_at_slowest_speed and total_extrude_time_at_minimum_speed - const double factor = (1/total_extrude_time_at_minimum_speed - 1/minExtrudeTime) / (1/total_extrude_time_at_minimum_speed - 1/total_extrude_time_at_slowest_speed); - target_speed = minimalSpeed * (1.0-factor) + slowest_path_speed * factor; + const double factor + = (1 / total_extrude_time_at_minimum_speed - 1 / minExtrudeTime) / (1 / total_extrude_time_at_minimum_speed - 1 / total_extrude_time_at_slowest_speed); + target_speed = minimalSpeed * (1.0 - factor) + slowest_path_speed * factor; temperatureFactor = 1.0 - factor; // Update stored naive time estimates @@ -1536,13 +1592,12 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ { // Slowing down to the slowest_speed is sufficient to respect the minimum layer time. // Linear interpolate between extrudeTime and total_extrude_time_at_slowest_speed - factor = (1/total_extrude_time_at_slowest_speed - 1/minExtrudeTime) / (1/total_extrude_time_at_slowest_speed - 1/extrudeTime); - slow_down_func = - [&slowest_path_speed = slowest_path_speed, &factor](const GCodePath& path) - { - const double target_speed = slowest_path_speed * (1.0 - factor) + (path.config->getSpeed() * path.speed_factor) * factor; - return std::min(target_speed / (path.config->getSpeed() * path.speed_factor), 1.0); - }; + factor = (1 / total_extrude_time_at_slowest_speed - 1 / minExtrudeTime) / (1 / total_extrude_time_at_slowest_speed - 1 / extrudeTime); + slow_down_func = [&slowest_path_speed = slowest_path_speed, &factor](const GCodePath& path) + { + const double target_speed = slowest_path_speed * (1.0 - factor) + (path.config.getSpeed() * path.speed_factor) * factor; + return std::min(target_speed / (path.config.getSpeed() * path.speed_factor), 1.0); + }; // Update stored naive time estimates estimates.extrude_time = minExtrudeTime; @@ -1569,7 +1624,7 @@ double ExtruderPlan::getRetractTime(const GCodePath& path) std::pair ExtruderPlan::getPointToPointTime(const Point& p0, const Point& p1, const GCodePath& path) { const double length = vSizeMM(p0 - p1); - return { length, length / (path.config->getSpeed() * path.speed_factor) }; + return { length, length / (path.config.getSpeed() * path.speed_factor) }; } TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point starting_position) @@ -1577,17 +1632,14 @@ TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point starting_pos Point p0 = starting_position; const double min_path_speed = fan_speed_layer_time_settings.cool_min_speed; - slowest_path_speed = - std::accumulate - ( - paths.begin(), - paths.end(), - std::numeric_limits::max(), - [](double value, const GCodePath& path) - { - return path.isTravelPath() ? value : std::min(value, path.config->getSpeed().value * path.speed_factor); - } - ); + slowest_path_speed = std::accumulate( + paths.begin(), + paths.end(), + std::numeric_limits::max(), + [](double value, const GCodePath& path) + { + return path.isTravelPath() ? value : std::min(value, path.config.getSpeed().value * path.speed_factor); + }); bool was_retracted = false; // wrong assumption; won't matter that much. (TODO) for (GCodePath& path : paths) @@ -1639,9 +1691,9 @@ TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point starting_pos path.estimates.extrude_time_at_minimum_speed += length / min_path_speed; path.estimates.extrude_time_at_slowest_path_speed += length / slowest_path_speed; } - material_estimate += length * INT2MM(layer_thickness) * INT2MM(path.config->getLineWidth()); + material_estimate += length * INT2MM(layer_thickness) * INT2MM(path.config.getLineWidth()); } - double thisTime = length / (path.config->getSpeed() * path.speed_factor); + double thisTime = length / (path.config.getSpeed() * path.speed_factor); *path_time_estimate += thisTime; p0 = p1; } @@ -1706,19 +1758,27 @@ void ExtruderPlan::processFanSpeedForFirstLayers() */ fan_speed = fan_speed_layer_time_settings.cool_fan_speed_min; - if (layer_nr < fan_speed_layer_time_settings.cool_fan_full_layer && fan_speed_layer_time_settings.cool_fan_full_layer > 0 // don't apply initial layer fan speed speedup if disabled. + if (layer_nr < fan_speed_layer_time_settings.cool_fan_full_layer + && fan_speed_layer_time_settings.cool_fan_full_layer > 0 // don't apply initial layer fan speed speedup if disabled. && ! is_raft_layer // don't apply initial layer fan speed speedup to raft, but to model layers ) { // Slow down the fan on the layers below the [cool_fan_full_layer], where layer 0 is speed 0. - fan_speed = fan_speed_layer_time_settings.cool_fan_speed_0 + (fan_speed - fan_speed_layer_time_settings.cool_fan_speed_0) * std::max(LayerIndex(0), layer_nr) / fan_speed_layer_time_settings.cool_fan_full_layer; + fan_speed = fan_speed_layer_time_settings.cool_fan_speed_0 + + (fan_speed - fan_speed_layer_time_settings.cool_fan_speed_0) * std::max(LayerIndex(0), layer_nr) / fan_speed_layer_time_settings.cool_fan_full_layer; } } void LayerPlan::processFanSpeedAndMinimalLayerTime(Point starting_position) { // the minimum layer time behaviour is only applied to the last extruder. - const size_t last_extruder_nr = ranges::max_element(extruder_plans, [](const ExtruderPlan& a, const ExtruderPlan& b) { return a.extruder_nr < b.extruder_nr; })->extruder_nr; + const size_t last_extruder_nr = ranges::max_element( + extruder_plans, + [](const ExtruderPlan& a, const ExtruderPlan& b) + { + return a.extruder_nr < b.extruder_nr; + }) + ->extruder_nr; Point starting_position_last_extruder; unsigned int last_extruder_idx; double other_extr_plan_time = 0.0; @@ -1770,10 +1830,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // flow-rate compensation const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - gcode.setFlowRateExtrusionSettings(mesh_group_settings.get("flow_rate_max_extrusion_offset"), mesh_group_settings.get("flow_rate_extrusion_offset_factor")); // Offset is in mm. + gcode.setFlowRateExtrusionSettings( + mesh_group_settings.get("flow_rate_max_extrusion_offset"), + mesh_group_settings.get("flow_rate_extrusion_offset_factor")); // Offset is in mm. static LayerIndex layer_1{ 1 - static_cast(Raft::getTotalExtraLayers()) }; - if (layer_nr == layer_1 && mesh_group_settings.get("machine_heated_bed") && mesh_group_settings.get("material_bed_temperature") != mesh_group_settings.get("material_bed_temperature_layer_0")) + if (layer_nr == layer_1 && mesh_group_settings.get("machine_heated_bed")) { constexpr bool wait = false; gcode.writeBedTemperatureCommand(mesh_group_settings.get("material_bed_temperature"), wait); @@ -1781,19 +1843,21 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.setZ(z); - const GCodePathConfig* last_extrusion_config = nullptr; // used to check whether we need to insert a TYPE comment in the gcode. + std::optional last_extrusion_config = std::nullopt; // used to check whether we need to insert a TYPE comment in the gcode. size_t extruder_nr = gcode.getExtruderNr(); const bool acceleration_enabled = mesh_group_settings.get("acceleration_enabled"); const bool acceleration_travel_enabled = mesh_group_settings.get("acceleration_travel_enabled"); const bool jerk_enabled = mesh_group_settings.get("jerk_enabled"); const bool jerk_travel_enabled = mesh_group_settings.get("jerk_travel_enabled"); - const SliceMeshStorage* current_mesh = nullptr; + std::shared_ptr current_mesh; for (size_t extruder_plan_idx = 0; extruder_plan_idx < extruder_plans.size(); extruder_plan_idx++) { ExtruderPlan& extruder_plan = extruder_plans[extruder_plan_idx]; - const RetractionAndWipeConfig* retraction_config = current_mesh ? ¤t_mesh->retraction_wipe_config: &storage.retraction_wipe_config_per_extruder[extruder_plan.extruder_nr]; + + const RetractionAndWipeConfig* retraction_config + = current_mesh ? ¤t_mesh->retraction_wipe_config : &storage.retraction_wipe_config_per_extruder[extruder_plan.extruder_nr]; coord_t z_hop_height = retraction_config->retraction_config.zHop; if (extruder_nr != extruder_plan.extruder_nr) @@ -1866,8 +1930,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool update_extrusion_offset = true; double cumulative_path_time = 0.; // Time in seconds. - const std::function insertTempOnTime = - [&](const double to_add, const int64_t path_idx) + const std::function insertTempOnTime = [&](const double to_add, const int64_t path_idx) { cumulative_path_time += to_add; extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); @@ -1887,7 +1950,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeRetraction(retraction_config->retraction_config); } - if (! path.retract && path.config->isTravelPath() && path.points.size() == 1 && path.points[0] == gcode.getPositionXY() && z == gcode.getPositionZ()) + if (z > 0) + { + gcode.setZ(z + path.z_offset); + } + + if (! path.retract && path.config.isTravelPath() && path.points.size() == 1 && path.points[0] == gcode.getPositionXY() && (z + path.z_offset) == gcode.getPositionZ()) { // ignore travel moves to the current location to avoid needless change of acceleration/jerk continue; @@ -1897,7 +1965,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) size_t next_extrusion_idx = path_idx + 1; if ((acceleration_enabled && ! acceleration_travel_enabled) || (jerk_enabled && ! jerk_travel_enabled)) { - while (next_extrusion_idx < paths.size() && paths[next_extrusion_idx].config->isTravelPath()) + while (next_extrusion_idx < paths.size() && paths[next_extrusion_idx].config.isTravelPath()) { ++next_extrusion_idx; } @@ -1905,11 +1973,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) if (acceleration_enabled) { - if (path.config->isTravelPath()) + if (path.config.isTravelPath()) { if (acceleration_travel_enabled) { - gcode.writeTravelAcceleration(path.config->getAcceleration()); + gcode.writeTravelAcceleration(path.config.getAcceleration()); } else { @@ -1923,20 +1991,20 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } else { - gcode.writeTravelAcceleration(paths[next_extrusion_idx].config->getAcceleration()); + gcode.writeTravelAcceleration(paths[next_extrusion_idx].config.getAcceleration()); } } } else { - gcode.writePrintAcceleration(path.config->getAcceleration()); + gcode.writePrintAcceleration(path.config.getAcceleration()); } } if (jerk_enabled) { if (jerk_travel_enabled) { - gcode.writeJerk(path.config->getJerk()); + gcode.writeJerk(path.config.getJerk()); } else { @@ -1950,7 +2018,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } else { - gcode.writeJerk(paths[next_extrusion_idx].config->getJerk()); + gcode.writeJerk(paths[next_extrusion_idx].config.getJerk()); } } } @@ -1970,10 +2038,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeZhopEnd(); } } - if (! path.config->isTravelPath() && last_extrusion_config != path.config) + const auto& extruder_changed = ! last_extrusion_config.has_value() || (last_extrusion_config.value().type != path.config.type); + if (! path.config.isTravelPath() && extruder_changed) { - gcode.writeTypeComment(path.config->type); - if (path.config->isBridgePath()) + gcode.writeTypeComment(path.config.type); + if (path.config.isBridgePath()) { gcode.writeComment("BRIDGE"); } @@ -1985,12 +2054,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) update_extrusion_offset = false; } - double speed = path.config->getSpeed(); + double speed = path.config.getSpeed(); // for some movements such as prime tower purge, the speed may get changed by this factor speed *= path.speed_factor; - //This seems to be the best location to place this, but still not ideal. + // This seems to be the best location to place this, but still not ideal. if (path.mesh != current_mesh) { current_mesh = path.mesh; @@ -1998,7 +2067,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) ss << "MESH:" << (current_mesh ? current_mesh->mesh_name : "NONMESH"); gcode.writeComment(ss.str()); } - if (path.config->isTravelPath()) + if (path.config.isTravelPath()) { // early comp for travel paths, which are handled more simply if (! path.perform_z_hop && final_travel_z != z && extruder_plan_idx == (extruder_plans.size() - 1) && path_idx == (paths.size() - 1)) { @@ -2010,7 +2079,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // Prevent the final travel(s) from resetting to the 'previous' layer height. gcode.setZ(final_travel_z); } - for (unsigned int point_idx = 0; point_idx < path.points.size() - 1; point_idx++) + for (size_t point_idx = 0; point_idx + 1 < path.points.size(); point_idx++) { gcode.writeTravel(path.points[point_idx], speed); } @@ -2044,8 +2113,8 @@ void LayerPlan::writeGCode(GCodeExport& gcode) insertTempOnTime(time, path_idx); const double extrude_speed = speed * path.speed_back_pressure_factor; - communication->sendLineTo(path.config->type, path.points[point_idx], path.getLineWidthForLayerView(), path.config->getLayerThickness(), extrude_speed); - gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config->type, update_extrusion_offset); + communication->sendLineTo(path.config.type, path.points[point_idx], path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config.type, update_extrusion_offset); prev_point = path.points[point_idx]; } @@ -2081,8 +2150,8 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.setZ(std::round(z + layer_thickness * length / totalLength)); const double extrude_speed = speed * path.speed_back_pressure_factor; - communication->sendLineTo(path.config->type, path.points[point_idx], path.getLineWidthForLayerView(), path.config->getLayerThickness(), extrude_speed); - gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config->type, update_extrusion_offset); + communication->sendLineTo(path.config.type, path.points[point_idx], path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config.type, update_extrusion_offset); } // for layer display only - the loop finished at the seam vertex but as we started from // the location of the previous layer's seam vertex the loop may have a gap if this layer's @@ -2093,7 +2162,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // vertex would not be shifted (as it's the last vertex in the sequence). The smoother the model, // the less the vertices are shifted and the less obvious is the ridge. If the layer display // really displayed a spiral rather than slices of a spiral, this would not be required. - communication->sendLineTo(path.config->type, path.points[0], path.getLineWidthForLayerView(), path.config->getLayerThickness(), speed); + communication->sendLineTo(path.config.type, path.points[0], path.getLineWidthForLayerView(), path.config.getLayerThickness(), speed); } path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path } @@ -2102,7 +2171,8 @@ void LayerPlan::writeGCode(GCodeExport& gcode) if (extruder.settings.get("cool_lift_head") && extruder_plan.extraTime > 0.0) { gcode.writeComment("Small layer, adding delay"); - const RetractionAndWipeConfig& retraction_config = current_mesh ? current_mesh->retraction_wipe_config: storage.retraction_wipe_config_per_extruder[gcode.getExtruderNr()]; + const RetractionAndWipeConfig& retraction_config + = current_mesh ? current_mesh->retraction_wipe_config : storage.retraction_wipe_config_per_extruder[gcode.getExtruderNr()]; gcode.writeRetraction(retraction_config.retraction_config); if (extruder_plan_idx == extruder_plans.size() - 1 || ! extruder.settings.get("machine_extruder_end_pos_abs")) { // only do the z-hop if it's the last extruder plan; otherwise it's already at the switching bay area @@ -2113,6 +2183,23 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } extruder_plan.handleAllRemainingInserts(gcode); + scripta::log( + "extruder_plan_2", + extruder_plan.paths, + SectionType::NA, + layer_nr, + scripta::CellVDI{ "flow", &GCodePath::flow }, + scripta::CellVDI{ "width_factor", &GCodePath::width_factor }, + scripta::CellVDI{ "spiralize", &GCodePath::spiralize }, + scripta::CellVDI{ "speed_factor", &GCodePath::speed_factor }, + scripta::CellVDI{ "speed_back_pressure_factor", &GCodePath::speed_back_pressure_factor }, + scripta::CellVDI{ "retract", &GCodePath::retract }, + scripta::CellVDI{ "unretract_before_last_travel_move", &GCodePath::unretract_before_last_travel_move }, + scripta::CellVDI{ "perform_z_hop", &GCodePath::perform_z_hop }, + scripta::CellVDI{ "perform_prime", &GCodePath::perform_prime }, + scripta::CellVDI{ "fan_speed", &GCodePath::getFanSpeed }, + scripta::CellVDI{ "is_travel_path", &GCodePath::isTravelPath }, + scripta::CellVDI{ "extrusion_mm3_per_mm", &GCodePath::getExtrusionMM3perMM }); } // extruder plans /\ . communication->sendLayerComplete(layer_nr, z, layer_thickness); @@ -2154,7 +2241,12 @@ bool LayerPlan::makeRetractSwitchRetract(unsigned int extruder_plan_idx, unsigne } } -bool LayerPlan::writePathWithCoasting(GCodeExport& gcode, const size_t extruder_plan_idx, const size_t path_idx, const coord_t layer_thickness, const std::function insertTempOnTime) +bool LayerPlan::writePathWithCoasting( + GCodeExport& gcode, + const size_t extruder_plan_idx, + const size_t path_idx, + const coord_t layer_thickness, + const std::function insertTempOnTime) { ExtruderPlan& extruder_plan = extruder_plans[extruder_plan_idx]; const ExtruderTrain& extruder = Application::getInstance().current_slice->scene.extruders[extruder_plan.extruder_nr]; @@ -2165,18 +2257,20 @@ bool LayerPlan::writePathWithCoasting(GCodeExport& gcode, const size_t extruder_ } const std::vector& paths = extruder_plan.paths; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! paths[path_idx + 1].config->isTravelPath()) || path.points.size() < 2) + if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! paths[path_idx + 1].config.isTravelPath()) || path.points.size() < 2) { return false; } coord_t coasting_min_dist_considered = MM2INT(0.1); // hardcoded setting for when to not perform coasting - const double extrude_speed = path.config->getSpeed() * path.speed_factor * path.speed_back_pressure_factor; + const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; - const coord_t coasting_dist = MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues + const coord_t coasting_dist + = MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues const double coasting_min_volume = extruder.settings.get("coasting_min_volume"); - const coord_t coasting_min_dist = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config->getLineWidth(); // closing brackets of MM2INT at weird places for precision issues + const coord_t coasting_min_dist + = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues // /\ the minimal distance when coasting will coast the full coasting volume instead of linearly less with linearly smaller paths std::vector accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...) @@ -2253,13 +2347,13 @@ bool LayerPlan::writePathWithCoasting(GCodeExport& gcode, const size_t extruder_ auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); insertTempOnTime(time, path_idx); - communication->sendLineTo(path.config->type, path.points[point_idx], path.getLineWidthForLayerView(), path.config->getLayerThickness(), extrude_speed); - gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config->type); + communication->sendLineTo(path.config.type, path.points[point_idx], path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + gcode.writeExtrusion(path.points[point_idx], extrude_speed, path.getExtrusionMM3perMM(), path.config.type); prev_pt = path.points[point_idx]; } - communication->sendLineTo(path.config->type, start, path.getLineWidthForLayerView(), path.config->getLayerThickness(), extrude_speed); - gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config->type); + communication->sendLineTo(path.config.type, start, path.getLineWidthForLayerView(), path.config.getLayerThickness(), extrude_speed); + gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); } // write coasting path @@ -2269,7 +2363,7 @@ bool LayerPlan::writePathWithCoasting(GCodeExport& gcode, const size_t extruder_ insertTempOnTime(time, path_idx); const Ratio coasting_speed_modifier = extruder.settings.get("coasting_speed"); - const Velocity speed = Velocity(coasting_speed_modifier * path.config->getSpeed()); + const Velocity speed = Velocity(coasting_speed_modifier * path.config.getSpeed()); gcode.writeTravel(path.points[point_idx], speed); prev_pt = path.points[point_idx]; @@ -2277,11 +2371,56 @@ bool LayerPlan::writePathWithCoasting(GCodeExport& gcode, const size_t extruder_ return true; } +void LayerPlan::applyModifyPlugin() +{ + for (auto& extruder_plan : extruder_plans) + { + scripta::log( + "extruder_plan_0", + extruder_plan.paths, + SectionType::NA, + layer_nr, + scripta::CellVDI{ "flow", &GCodePath::flow }, + scripta::CellVDI{ "width_factor", &GCodePath::width_factor }, + scripta::CellVDI{ "spiralize", &GCodePath::spiralize }, + scripta::CellVDI{ "speed_factor", &GCodePath::speed_factor }, + scripta::CellVDI{ "speed_back_pressure_factor", &GCodePath::speed_back_pressure_factor }, + scripta::CellVDI{ "retract", &GCodePath::retract }, + scripta::CellVDI{ "unretract_before_last_travel_move", &GCodePath::unretract_before_last_travel_move }, + scripta::CellVDI{ "perform_z_hop", &GCodePath::perform_z_hop }, + scripta::CellVDI{ "perform_prime", &GCodePath::perform_prime }, + scripta::CellVDI{ "fan_speed", &GCodePath::getFanSpeed }, + scripta::CellVDI{ "is_travel_path", &GCodePath::isTravelPath }, + scripta::CellVDI{ "extrusion_mm3_per_mm", &GCodePath::getExtrusionMM3perMM }); + + extruder_plan.paths = slots::instance().modify(extruder_plan.paths, extruder_plan.extruder_nr, layer_nr); + + scripta::log( + "extruder_plan_1", + extruder_plan.paths, + SectionType::NA, + layer_nr, + scripta::CellVDI{ "flow", &GCodePath::flow }, + scripta::CellVDI{ "width_factor", &GCodePath::width_factor }, + scripta::CellVDI{ "spiralize", &GCodePath::spiralize }, + scripta::CellVDI{ "speed_factor", &GCodePath::speed_factor }, + scripta::CellVDI{ "speed_back_pressure_factor", &GCodePath::speed_back_pressure_factor }, + scripta::CellVDI{ "retract", &GCodePath::retract }, + scripta::CellVDI{ "unretract_before_last_travel_move", &GCodePath::unretract_before_last_travel_move }, + scripta::CellVDI{ "perform_z_hop", &GCodePath::perform_z_hop }, + scripta::CellVDI{ "perform_prime", &GCodePath::perform_prime }, + scripta::CellVDI{ "fan_speed", &GCodePath::getFanSpeed }, + scripta::CellVDI{ "is_travel_path", &GCodePath::isTravelPath }, + scripta::CellVDI{ "extrusion_mm3_per_mm", &GCodePath::getExtrusionMM3perMM }); + } +} + void LayerPlan::applyBackPressureCompensation() { for (auto& extruder_plan : extruder_plans) { - const Ratio back_pressure_compensation = Application::getInstance().current_slice->scene.extruders[extruder_plan.extruder_nr].settings.get("speed_equalize_flow_width_factor"); + const Ratio back_pressure_compensation + = Application::getInstance().current_slice->scene.extruders[extruder_plan.extruder_nr].settings.get("speed_equalize_flow_width_factor"); if (back_pressure_compensation != 0.0) { extruder_plan.applyBackPressureCompensation(back_pressure_compensation); @@ -2289,7 +2428,7 @@ void LayerPlan::applyBackPressureCompensation() } } -int LayerPlan::getLayerNr() const +LayerIndex LayerPlan::getLayerNr() const { return layer_nr; } diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index 1ef34d1e1b..d5a033a136 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -1,13 +1,14 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "LayerPlanBuffer.h" + #include #include "Application.h" //To flush g-code through the communication channel. #include "ExtruderTrain.h" #include "FffProcessor.h" #include "LayerPlan.h" -#include "LayerPlanBuffer.h" #include "Slice.h" #include "communication/Communication.h" //To flush g-code through the communication channel. #include "gcodeExport.h" @@ -99,7 +100,8 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer prev_layer->setIsInside(new_layer_destination_state->second); const bool force_retract = extruder_settings.get("retract_at_layer_change") || (mesh_group_settings.get("travel_retract_before_outer_wall") - && (mesh_group_settings.get("inset_direction") == InsetDirection::OUTSIDE_IN || mesh_group_settings.get("wall_line_count") == 1)); // Moving towards an outer wall. + && (mesh_group_settings.get("inset_direction") == InsetDirection::OUTSIDE_IN + || mesh_group_settings.get("wall_line_count") == 1)); // Moving towards an outer wall. prev_layer->final_travel_z = newest_layer->z; GCodePath& path = prev_layer->addTravel(first_location_new_layer, force_retract); if (force_retract && ! path.retract) @@ -180,7 +182,13 @@ Preheat::WarmUpResult LayerPlanBuffer::computeStandbyTempPlan(std::vector("material_standby_temperature"), initial_print_temp, during_printing); + Preheat::WarmUpResult warm_up = preheat_config.getWarmUpPointAfterCoolDown( + in_between_time, + extruder, + temp_before, + extruder_settings.get("material_standby_temperature"), + initial_print_temp, + during_printing); warm_up.heating_time = std::min(in_between_time, warm_up.heating_time + extra_preheat_time); return warm_up; } @@ -233,10 +241,7 @@ void LayerPlanBuffer::handleStandbyTemp(std::vector& extruder_pla spdlog::warn("Couldn't find previous extruder plan so as to set the standby temperature. Inserting temp command in earliest available layer."); ExtruderPlan& earliest_extruder_plan = *extruder_plans[0]; constexpr bool wait = false; - earliest_extruder_plan.insertCommand(NozzleTempInsert{ .path_idx = 0, - .extruder = extruder, - .temperature = standby_temp, - .wait = wait }); + earliest_extruder_plan.insertCommand(NozzleTempInsert{ .path_idx = 0, .extruder = extruder, .temperature = standby_temp, .wait = wait }); } void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vector& extruder_plans, unsigned int extruder_plan_idx) @@ -281,10 +286,8 @@ void LayerPlanBuffer::insertPreheatCommand_multiExtrusion(std::vectorinsertCommand(NozzleTempInsert { .path_idx = path_idx, - .extruder = extruder, - .temperature = initial_print_temp, - .wait = wait }); // insert preheat command at verfy beginning of buffer + extruder_plans[0]->insertCommand( + NozzleTempInsert{ .path_idx = path_idx, .extruder = extruder, .temperature = initial_print_temp, .wait = wait }); // insert preheat command at verfy beginning of buffer } void LayerPlanBuffer::insertTempCommands(std::vector& extruder_plans, unsigned int extruder_plan_idx) @@ -350,10 +353,7 @@ void LayerPlanBuffer::insertPrintTempCommand(ExtruderPlan& extruder_plan) } } bool wait = false; - extruder_plan.insertCommand(NozzleTempInsert{ .path_idx = path_idx, - .extruder = extruder, - .temperature = print_temp, - .wait = wait }); + extruder_plan.insertCommand(NozzleTempInsert{ .path_idx = path_idx, .extruder = extruder, .temperature = print_temp, .wait = wait }); } extruder_plan.heated_pre_travel_time = heated_pre_travel_time; } @@ -388,12 +388,14 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex } } - double time_window = - 0; // The time window within which the nozzle needs to heat from the initial print temp to the printing temperature and then back to the final print temp; i.e. from the first to the last extrusion move with this extruder - double weighted_average_extrusion_temp = 0; // The average of the normal extrusion temperatures of the extruder plans (which might be different due to flow dependent temp or due to initial layer temp) Weighted by time + double time_window = 0; // The time window within which the nozzle needs to heat from the initial print temp to the printing temperature and then back to the final print temp; + // i.e. from the first to the last extrusion move with this extruder + double weighted_average_extrusion_temp = 0; // The average of the normal extrusion temperatures of the extruder plans (which might be different due to flow dependent temp or + // due to initial layer temp) Weighted by time std::optional initial_print_temp; // The initial print temp of the first extruder plan with this extruder { // compute time window and print temp statistics - double heated_pre_travel_time = -1; // The time before the first extrude move from the start of the extruder plan during which the nozzle is stable at the initial print temperature + double heated_pre_travel_time + = -1; // The time before the first extrude move from the start of the extruder plan during which the nozzle is stable at the initial print temperature for (unsigned int prev_extruder_plan_idx = last_extruder_plan_idx; (int)prev_extruder_plan_idx >= 0; prev_extruder_plan_idx--) { ExtruderPlan& prev_extruder_plan = *extruder_plans[prev_extruder_plan_idx]; @@ -427,7 +429,7 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex return; } - assert((time_window >= 0 || last_extruder_plan.estimates.getMaterial() == 0) && "Time window should always be positive if we actually extrude"); + assert((time_window >= -0.001 || last_extruder_plan.estimates.material == 0) && "Time window should always be positive if we actually extrude"); // ,layer change . // : ,precool command ,layer change . @@ -442,7 +444,8 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex // This approximation is quite ok since it only determines where to insert the precool temp command, // which means the stable temperature of the previous extruder plan and the stable temperature of the next extruder plan couldn't be reached constexpr bool during_printing = true; - Preheat::CoolDownResult warm_cool_result = preheat_config.getCoolDownPointAfterWarmUp(time_window, extruder, *initial_print_temp, weighted_average_extrusion_temp, final_print_temp, during_printing); + Preheat::CoolDownResult warm_cool_result + = preheat_config.getCoolDownPointAfterWarmUp(time_window, extruder, *initial_print_temp, weighted_average_extrusion_temp, final_print_temp, during_printing); double cool_down_time = warm_cool_result.cooling_time; assert(cool_down_time >= 0); @@ -465,7 +468,8 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex } } - // at this point cool_down_time is what time is left if cool down time of extruder plans after precool_extruder_plan (up until last_extruder_plan) are already taken into account + // at this point cool_down_time is what time is left if cool down time of extruder plans after precool_extruder_plan (up until last_extruder_plan) are already taken into + // account { // insert temp command in precool_extruder_plan double extrusion_time_seen = 0; @@ -481,11 +485,8 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex } bool wait = false; double time_after_path_start = extrusion_time_seen - cool_down_time; - precool_extruder_plan->insertCommand(NozzleTempInsert { .path_idx = path_idx, - .extruder = extruder, - .temperature = final_print_temp, - .wait = wait, - .time_after_path_start = time_after_path_start }); + precool_extruder_plan->insertCommand( + NozzleTempInsert{ .path_idx = path_idx, .extruder = extruder, .temperature = final_print_temp, .wait = wait, .time_after_path_start = time_after_path_start }); } } @@ -520,11 +521,11 @@ void LayerPlanBuffer::insertTempCommands() Ratio avg_flow; if (time > 0.0) { - avg_flow = extruder_plan.estimates.getMaterial() / time; + avg_flow = extruder_plan.estimates.material / time; } else { - assert(extruder_plan.estimates.getMaterial() == 0.0 && "No extrusion time should mean no material usage!"); + assert(extruder_plan.estimates.material == 0.0 && "No extrusion time should mean no material usage!"); avg_flow = 0.0; } diff --git a/src/MeshGroup.cpp b/src/MeshGroup.cpp index 1516e64190..84ef4d4923 100644 --- a/src/MeshGroup.cpp +++ b/src/MeshGroup.cpp @@ -1,6 +1,8 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "MeshGroup.h" + #include #include #include @@ -10,7 +12,6 @@ #include #include -#include "MeshGroup.h" #include "settings/types/Ratio.h" //For the shrinkage percentage and scale factor. #include "utils/FMatrix4x3.h" //To transform the input meshes for shrinkage compensation and to align in command line mode. #include "utils/floatpoint.h" //To accept incoming meshes with floating point vertices. @@ -48,7 +49,8 @@ Point3 MeshGroup::min() const Point3 ret(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); for (const Mesh& mesh : meshes) { - if (mesh.settings.get("infill_mesh") || mesh.settings.get("cutting_mesh") || mesh.settings.get("anti_overhang_mesh")) // Don't count pieces that are not printed. + if (mesh.settings.get("infill_mesh") || mesh.settings.get("cutting_mesh") + || mesh.settings.get("anti_overhang_mesh")) // Don't count pieces that are not printed. { continue; } @@ -69,7 +71,8 @@ Point3 MeshGroup::max() const Point3 ret(std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()); for (const Mesh& mesh : meshes) { - if (mesh.settings.get("infill_mesh") || mesh.settings.get("cutting_mesh") || mesh.settings.get("anti_overhang_mesh")) // Don't count pieces that are not printed. + if (mesh.settings.get("infill_mesh") || mesh.settings.get("cutting_mesh") + || mesh.settings.get("anti_overhang_mesh")) // Don't count pieces that are not printed. { continue; } @@ -112,7 +115,9 @@ void MeshGroup::finalize() } mesh.translate(mesh_offset + meshgroup_offset); } - scaleFromBottom(settings.get("material_shrinkage_percentage_xy"), settings.get("material_shrinkage_percentage_z")); // Compensate for the shrinkage of the material. + scaleFromBottom( + settings.get("material_shrinkage_percentage_xy"), + settings.get("material_shrinkage_percentage_z")); // Compensate for the shrinkage of the material. for (const auto& [idx, mesh] : meshes | ranges::views::enumerate) { scripta::log(fmt::format("mesh_{}", idx), mesh, SectionType::NA); @@ -285,7 +290,7 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const FMa if (loadMeshSTL(&mesh, filename, transformation)) // Load it! If successful... { meshgroup->meshes.push_back(mesh); - spdlog::info("loading '{}' took {:3} seconds", filename, load_timer.restart()); + spdlog::info("loading '{}' took {:03.3f} seconds", filename, load_timer.restart()); return true; } } diff --git a/src/Mold.cpp b/src/Mold.cpp index e7d86ffb33..bd5ff2e0b1 100644 --- a/src/Mold.cpp +++ b/src/Mold.cpp @@ -1,14 +1,15 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "Mold.h" #include "Application.h" //To get settings. #include "ExtruderTrain.h" -#include "Mold.h" #include "Scene.h" #include "Slice.h" +#include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "slicer.h" -#include "settings/types/Ratio.h" #include "utils/IntPoint.h" namespace cura @@ -28,18 +29,18 @@ void Mold::process(std::vector& slicer_list) mesh.expandXY(mesh.settings.get("mold_width")); } } - if (!has_any_mold) + if (! has_any_mold) { return; } } - unsigned int layer_count = 0; + LayerIndex layer_count = 0; { // compute layer_count for (unsigned int mesh_idx = 0; mesh_idx < slicer_list.size(); mesh_idx++) { Slicer& slicer = *slicer_list[mesh_idx]; - unsigned int layer_count_here = slicer.layers.size(); + LayerIndex layer_count_here = slicer.layers.size(); layer_count = std::max(layer_count, layer_count_here); } } @@ -56,7 +57,7 @@ void Mold::process(std::vector& slicer_list) { const Mesh& mesh = scene.current_mesh_group->meshes[mesh_idx]; Slicer& slicer = *slicer_list[mesh_idx]; - if (!mesh.settings.get("mold_enabled") || layer_nr >= static_cast(slicer.layers.size())) + if (! mesh.settings.get("mold_enabled") || layer_nr >= static_cast(slicer.layers.size())) { continue; } @@ -92,7 +93,7 @@ void Mold::process(std::vector& slicer_list) // add roofs if (roof_layer_count > 0 && layer_nr > 0) { - unsigned int layer_nr_below = std::max(0, static_cast(layer_nr - roof_layer_count)); + LayerIndex layer_nr_below = std::max(0, static_cast(layer_nr - roof_layer_count)); Polygons roofs = slicer.layers[layer_nr_below].polygons.offset(width, ClipperLib::jtRound); // TODO: don't compute offset twice! layer.polygons = layer.polygons.unionPolygons(roofs); } @@ -107,7 +108,7 @@ void Mold::process(std::vector& slicer_list) for (unsigned int mesh_idx = 0; mesh_idx < slicer_list.size(); mesh_idx++) { const Mesh& mesh = scene.current_mesh_group->meshes[mesh_idx]; - if (!mesh.settings.get("mold_enabled")) + if (! mesh.settings.get("mold_enabled")) { continue; // only cut original models out of all molds } @@ -116,8 +117,7 @@ void Mold::process(std::vector& slicer_list) layer.polygons = layer.polygons.difference(all_original_mold_outlines); } } - } -}//namespace cura +} // namespace cura diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index 3a4846c69c..3883822adc 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -1,65 +1,69 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PrimeTower.h" #include #include #include "Application.h" //To get settings. #include "ExtruderTrain.h" -#include "gcodeExport.h" -#include "infill.h" #include "LayerPlan.h" -#include "PrimeTower.h" #include "PrintFeature.h" -#include "raft.h" #include "Scene.h" #include "Slice.h" +#include "gcodeExport.h" +#include "infill.h" +#include "raft.h" #include "sliceDataStorage.h" -#define CIRCLE_RESOLUTION 32 //The number of vertices in each circle. +#define CIRCLE_RESOLUTION 32 // The number of vertices in each circle. -namespace cura +namespace cura { PrimeTower::PrimeTower() -: wipe_from_middle(false) + : wipe_from_middle(false) { const Scene& scene = Application::getInstance().current_slice->scene; { EPlatformAdhesion adhesion_type = scene.current_mesh_group->settings.get("adhesion_type"); - //When we have multiple extruders sharing the same heater/nozzle, we expect that all the extruders have been + // When we have multiple extruders sharing the same heater/nozzle, we expect that all the extruders have been //'primed' by the print-start gcode script, but we don't know which one has been left at the tip of the nozzle - //and whether it needs 'purging' (before extruding a pure material) or not, so we need to prime (actually purge) - //each extruder before it is used for the model. This can done by the (per-extruder) brim lines or (per-extruder) - //skirt lines when they are used, but we need to do that inside the first prime-tower layer when they are not - //used (sacrifying for this purpose the usual single-extruder first layer, that would be better for prime-tower - //adhesion). - - multiple_extruders_on_first_layer = scene.current_mesh_group->settings.get("machine_extruders_share_nozzle") && ((adhesion_type != EPlatformAdhesion::SKIRT) && (adhesion_type != EPlatformAdhesion::BRIM)); + // and whether it needs 'purging' (before extruding a pure material) or not, so we need to prime (actually purge) + // each extruder before it is used for the model. This can done by the (per-extruder) brim lines or (per-extruder) + // skirt lines when they are used, but we need to do that inside the first prime-tower layer when they are not + // used (sacrifying for this purpose the usual single-extruder first layer, that would be better for prime-tower + // adhesion). + + multiple_extruders_on_first_layer = scene.current_mesh_group->settings.get("machine_extruders_share_nozzle") + && ((adhesion_type != EPlatformAdhesion::SKIRT) && (adhesion_type != EPlatformAdhesion::BRIM)); } - enabled = scene.current_mesh_group->settings.get("prime_tower_enable") - && scene.current_mesh_group->settings.get("prime_tower_min_volume") > 10 + enabled = scene.current_mesh_group->settings.get("prime_tower_enable") && scene.current_mesh_group->settings.get("prime_tower_min_volume") > 10 && scene.current_mesh_group->settings.get("prime_tower_size") > 10; - would_have_actual_tower = enabled; // Assume so for now. + would_have_actual_tower = enabled; // Assume so for now. extruder_count = scene.extruders.size(); extruder_order.resize(extruder_count); for (unsigned int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { - extruder_order[extruder_nr] = extruder_nr; //Start with default order, then sort. + extruder_order[extruder_nr] = extruder_nr; // Start with default order, then sort. } - //Sort from high adhesion to low adhesion. - const Scene* scene_pointer = &scene; //Communicate to lambda via pointer to prevent copy. - std::stable_sort(extruder_order.begin(), extruder_order.end(), [scene_pointer](const unsigned int& extruder_nr_a, const unsigned int& extruder_nr_b) -> bool - { - const Ratio adhesion_a = scene_pointer->extruders[extruder_nr_a].settings.get("material_adhesion_tendency"); - const Ratio adhesion_b = scene_pointer->extruders[extruder_nr_b].settings.get("material_adhesion_tendency"); - return adhesion_a < adhesion_b; - }); + // Sort from high adhesion to low adhesion. + const Scene* scene_pointer = &scene; // Communicate to lambda via pointer to prevent copy. + std::stable_sort( + extruder_order.begin(), + extruder_order.end(), + [scene_pointer](const unsigned int& extruder_nr_a, const unsigned int& extruder_nr_b) -> bool + { + const Ratio adhesion_a = scene_pointer->extruders[extruder_nr_a].settings.get("material_adhesion_tendency"); + const Ratio adhesion_b = scene_pointer->extruders[extruder_nr_b].settings.get("material_adhesion_tendency"); + return adhesion_a < adhesion_b; + }); } void PrimeTower::checkUsed(const SliceDataStorage& storage) @@ -78,12 +82,13 @@ void PrimeTower::checkUsed(const SliceDataStorage& storage) void PrimeTower::generateGroundpoly() { - if (!enabled) + if (! enabled) { return; } - const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + const Scene& scene = Application::getInstance().current_slice->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; const coord_t tower_size = mesh_group_settings.get("prime_tower_size"); const coord_t x = mesh_group_settings.get("prime_tower_position_x"); @@ -97,7 +102,8 @@ void PrimeTower::generateGroundpoly() void PrimeTower::generatePaths(const SliceDataStorage& storage) { - would_have_actual_tower = storage.max_print_height_second_to_last_extruder >= 0; //Maybe it turns out that we don't need a prime tower after all because there are no layer switches. + would_have_actual_tower + = storage.max_print_height_second_to_last_extruder >= 0; // Maybe it turns out that we don't need a prime tower after all because there are no layer switches. if (would_have_actual_tower && enabled) { generatePaths_denseInfill(); @@ -110,54 +116,69 @@ void PrimeTower::generatePaths_denseInfill() const Scene& scene = Application::getInstance().current_slice->scene; const Settings& mesh_group_settings = scene.current_mesh_group->settings; const coord_t layer_height = mesh_group_settings.get("layer_height"); - pattern_per_extruder.resize(extruder_count); - pattern_per_extruder_layer0.resize(extruder_count); + const bool base_enabled = mesh_group_settings.get("prime_tower_brim_enable"); + const coord_t base_extra_radius = scene.settings.get("prime_tower_base_size"); + const bool has_raft = mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT; + const coord_t base_height = std::max(scene.settings.get("prime_tower_base_height"), has_raft ? layer_height : 0); + const double base_curve_magnitude = mesh_group_settings.get("prime_tower_base_curve_magnitude"); + const coord_t line_width = scene.extruders[extruder_order.front()].settings.get("prime_tower_line_width"); - coord_t cumulative_inset = 0; //Each tower shape is going to be printed inside the other. This is the inset we're doing for each extruder. + prime_moves.resize(extruder_count); + base_extra_moves.resize(extruder_count); + + coord_t cumulative_inset = 0; // Each tower shape is going to be printed inside the other. This is the inset we're doing for each extruder. for (size_t extruder_nr : extruder_order) { const coord_t line_width = scene.extruders[extruder_nr].settings.get("prime_tower_line_width"); const coord_t required_volume = MM3_2INT(scene.extruders[extruder_nr].settings.get("prime_tower_min_volume")); const Ratio flow = scene.extruders[extruder_nr].settings.get("prime_tower_flow"); coord_t current_volume = 0; - ExtrusionMoves& pattern = pattern_per_extruder[extruder_nr]; + Polygons& pattern = prime_moves[extruder_nr]; - //Create the walls of the prime tower. + // Create the walls of the prime tower. unsigned int wall_nr = 0; for (; current_volume < required_volume; wall_nr++) { - //Create a new polygon with an offset from the outer polygon. + // Create a new polygon with an offset from the outer polygon. Polygons polygons = outer_poly.offset(-cumulative_inset - wall_nr * line_width - line_width / 2); - pattern.polygons.add(polygons); + pattern.add(polygons); current_volume += polygons.polygonLength() * line_width * layer_height * flow; - if (polygons.empty()) //Don't continue. We won't ever reach the required volume because it doesn't fit. + if (polygons.empty()) // Don't continue. We won't ever reach the required volume because it doesn't fit. { break; } } - //Only the most inside extruder needs to fill the inside of the prime tower - if (extruder_nr != extruder_order.back()) + // The most outside extruder is used for the base + if (extruder_nr == extruder_order.front() && (base_enabled || has_raft) && base_extra_radius > 0 && base_height > 0) { - pattern_per_extruder_layer0 = pattern_per_extruder; + for (coord_t z = 0; z < base_height; z += layer_height) + { + double brim_radius_factor = std::pow((1.0 - static_cast(z) / base_height), base_curve_magnitude); + coord_t extra_radius = base_extra_radius * brim_radius_factor; + size_t extra_rings = extra_radius / line_width; + if (extra_rings == 0) + { + break; + } + extra_radius = line_width * extra_rings; + outer_poly_base.push_back(outer_poly.offset(extra_radius)); + + base_extra_moves[extruder_nr].push_back(PolygonUtils::generateOutset(outer_poly, extra_rings, line_width)); + } } - else + + cumulative_inset += wall_nr * line_width; + + // Only the most inside extruder needs to fill the inside of the prime tower + if (extruder_nr == extruder_order.back()) { - //Generate the pattern for the first layer. - coord_t line_width_layer0 = line_width * scene.extruders[extruder_nr].settings.get("initial_layer_line_width_factor"); - ExtrusionMoves& pattern_layer0 = pattern_per_extruder_layer0[extruder_nr]; - - // Generate a concentric infill pattern in the form insets for the prime tower's first layer instead of using - // the infill pattern because the infill pattern tries to connect polygons in different insets which causes the - // first layer of the prime tower to not stick well. - Polygons inset = outer_poly.offset(-cumulative_inset - line_width_layer0 / 2); - while (!inset.empty()) + Polygons pattern = PolygonUtils::generateInset(outer_poly, line_width, cumulative_inset); + if (! pattern.empty()) { - pattern_layer0.polygons.add(inset); - inset = inset.offset(-line_width_layer0); + base_extra_moves[extruder_nr].push_back(pattern); } } - cumulative_inset += wall_nr * line_width; } } @@ -183,7 +204,7 @@ void PrimeTower::addToGcode(const SliceDataStorage& storage, LayerPlan& gcode_la } const LayerIndex layer_nr = gcode_layer.getLayerNr(); - if (layer_nr < 0 || layer_nr > storage.max_print_height_second_to_last_extruder + 1) + if (layer_nr > storage.max_print_height_second_to_last_extruder + 1) { return; } @@ -207,7 +228,7 @@ void PrimeTower::addToGcode(const SliceDataStorage& storage, LayerPlan& gcode_la // post-wipe: if (post_wipe) { - //Make sure we wipe the old extruder on the prime tower. + // Make sure we wipe the old extruder on the prime tower. const Settings& previous_settings = Application::getInstance().current_slice->scene.extruders[prev_extruder].settings; const Point previous_nozzle_offset = Point(previous_settings.get("machine_nozzle_offset_x"), previous_settings.get("machine_nozzle_offset_y")); const Settings& new_settings = Application::getInstance().current_slice->scene.extruders[new_extruder].settings; @@ -220,37 +241,67 @@ void PrimeTower::addToGcode(const SliceDataStorage& storage, LayerPlan& gcode_la void PrimeTower::addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder_nr) const { - const ExtrusionMoves& pattern = (gcode_layer.getLayerNr() == -static_cast(Raft::getFillerLayerCount())) - ? pattern_per_extruder_layer0[extruder_nr] - : pattern_per_extruder[extruder_nr]; + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + const bool adhesion_raft = raft_total_extra_layers > 0; + LayerIndex absolute_layer_number = gcode_layer.getLayerNr() + raft_total_extra_layers; - const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + if (! adhesion_raft || absolute_layer_number > 0) + { + // Actual prime pattern + const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + const Polygons& pattern = prime_moves[extruder_nr]; + gcode_layer.addPolygonsByOptimizer(pattern, config); + } - gcode_layer.addPolygonsByOptimizer(pattern.polygons, config); - gcode_layer.addLinesByOptimizer(pattern.lines, config, SpaceFillType::Lines); + const std::vector& pattern_extra_brim = base_extra_moves[extruder_nr]; + if (absolute_layer_number < pattern_extra_brim.size()) + { + // Extra rings for stronger base + const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + const Polygons& pattern = pattern_extra_brim[absolute_layer_number]; + gcode_layer.addPolygonsByOptimizer(pattern, config); + } } void PrimeTower::subtractFromSupport(SliceDataStorage& storage) { - const Polygons outside_polygon = outer_poly.getOutsidePolygons(); - AABB outside_polygon_boundary_box(outside_polygon); - for(size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++) + for (size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++) { + const Polygons outside_polygon = getOuterPoly(layer).getOutsidePolygons(); + AABB outside_polygon_boundary_box(outside_polygon); SupportLayer& support_layer = storage.support.supportLayers[layer]; // take the differences of the support infill parts and the prime tower area support_layer.excludeAreasFromSupportInfillAreas(outside_polygon, outside_polygon_boundary_box); } } +const Polygons& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const +{ + const LayerIndex absolute_layer_nr = layer_nr + Raft::getTotalExtraLayers(); + if (absolute_layer_nr < outer_poly_base.size()) + { + return outer_poly_base[absolute_layer_nr]; + } + else + { + return outer_poly; + } +} + +const Polygons& PrimeTower::getGroundPoly() const +{ + return getOuterPoly(-Raft::getTotalExtraLayers()); +} + void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const { - int current_start_location_idx = ((((extruder_nr + 1) * gcode_layer.getLayerNr()) % number_of_prime_tower_start_locations) - + number_of_prime_tower_start_locations) % number_of_prime_tower_start_locations; + int current_start_location_idx = ((((extruder_nr + 1) * gcode_layer.getLayerNr()) % number_of_prime_tower_start_locations) + number_of_prime_tower_start_locations) + % number_of_prime_tower_start_locations; const ClosestPolygonPoint wipe_location = prime_tower_start_locations[current_start_location_idx]; const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; - const coord_t inward_dist = train.settings.get("machine_nozzle_size") * 3 / 2 ; + const coord_t inward_dist = train.settings.get("machine_nozzle_size") * 3 / 2; const coord_t start_dist = train.settings.get("machine_nozzle_size") * 2; const Point prime_end = PolygonUtils::moveInsideDiagonally(wipe_location, inward_dist); const Point outward_dir = wipe_location.location - prime_end; @@ -259,4 +310,4 @@ void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr gcode_layer.addTravel(prime_start); } -}//namespace cura +} // namespace cura diff --git a/src/Scene.cpp b/src/Scene.cpp index ff4baf9adc..ac50185b6d 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,11 +1,12 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "Scene.h" + #include #include "Application.h" #include "FffProcessor.h" //To start a slice. -#include "Scene.h" #include "communication/Communication.h" //To flush g-code and layer view when we're done. #include "progress/Progress.h" #include "sliceDataStorage.h" @@ -13,7 +14,9 @@ namespace cura { -Scene::Scene(const size_t num_mesh_groups) : mesh_groups(num_mesh_groups), current_mesh_group(mesh_groups.begin()) +Scene::Scene(const size_t num_mesh_groups) + : mesh_groups(num_mesh_groups) + , current_mesh_group(mesh_groups.begin()) { for (MeshGroup& mesh_group : mesh_groups) { @@ -78,7 +81,7 @@ void Scene::processMeshGroup(MeshGroup& mesh_group) if (empty) { Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup - spdlog::info("Total time elapsed {:3}s.", time_keeper_total.restart()); + spdlog::info("Total time elapsed {:03.3f}s", time_keeper_total.restart()); return; } @@ -94,7 +97,7 @@ void Scene::processMeshGroup(MeshGroup& mesh_group) Progress::messageProgress(Progress::Stage::FINISH, 1, 1); // 100% on this meshgroup Application::getInstance().communication->flushGCode(); Application::getInstance().communication->sendOptimizedLayerData(); - spdlog::info("Total time elapsed {:3}s.\n", time_keeper_total.restart()); + spdlog::info("Total time elapsed {:03.3f}s\n", time_keeper_total.restart()); } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index e4b6dfd45e..91b587408f 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -3,22 +3,24 @@ #include "SkeletalTrapezoidation.h" +#include "BoostInterface.hpp" +#include "settings/types/Ratio.h" +#include "utils/VoronoiUtils.h" +#include "utils/linearAlg2D.h" +#include "utils/macros.h" + +#include +#include + #include #include #include #include #include -#include -#include - -#include "settings/types/Ratio.h" -#include "BoostInterface.hpp" -#include "utils/VoronoiUtils.h" -#include "utils/linearAlg2D.h" -#include "utils/macros.h" - -#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 // A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). +#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX \ + 1000 // A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing + // performance). namespace cura { @@ -39,7 +41,15 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector& points, const std::vector& segments) +void SkeletalTrapezoidation::transferEdge( + Point from, + Point to, + vd_t::edge_type& vd_edge, + edge_t*& prev_edge, + Point& start_source_point, + Point& end_source_point, + const std::vector& points, + const std::vector& segments) { graph.segments = segments; @@ -108,7 +118,8 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& { spdlog::warn("Previous edge doesn't go anywhere."); } - node_t* v0 = (prev_edge) ? prev_edge->to : &makeNode(*vd_edge.vertex0(), from); // TODO: investigate whether boost:voronoi can produce multiple verts and violates consistency + node_t* v0 + = (prev_edge) ? prev_edge->to : &makeNode(*vd_edge.vertex0(), from); // TODO: investigate whether boost:voronoi can produce multiple verts and violates consistency Point p0 = discretized.front(); for (size_t p1_idx = 1; p1_idx < discretized.size(); p1_idx++) { @@ -327,13 +338,14 @@ std::vector SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_ } -bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, - Point& start_source_point, - Point& end_source_point, - vd_t::edge_type*& starting_vd_edge, - vd_t::edge_type*& ending_vd_edge, - const std::vector& points, - const std::vector& segments) +bool SkeletalTrapezoidation::computePointCellRange( + vd_t::cell_type& cell, + Point& start_source_point, + Point& end_source_point, + vd_t::edge_type*& starting_vd_edge, + vd_t::edge_type*& ending_vd_edge, + const std::vector& points, + const std::vector& segments) { if (cell.incident_edge()->is_infinite()) { @@ -395,13 +407,14 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, return true; } -void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, - Point& start_source_point, - Point& end_source_point, - vd_t::edge_type*& starting_vd_edge, - vd_t::edge_type*& ending_vd_edge, - const std::vector& points, - const std::vector& segments) +void SkeletalTrapezoidation::computeSegmentCellRange( + vd_t::cell_type& cell, + Point& start_source_point, + Point& end_source_point, + vd_t::edge_type*& starting_vd_edge, + vd_t::edge_type*& ending_vd_edge, + const std::vector& points, + const std::vector& segments) { const Segment& source_segment = VoronoiUtils::getSourceSegment(cell, points, segments); Point from = source_segment.from(); @@ -446,15 +459,16 @@ void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, end_source_point = source_segment.from(); } -SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, - const BeadingStrategy& beading_strategy, - AngleRadians transitioning_angle, - coord_t discretization_step_size, - coord_t transition_filter_dist, - coord_t allowed_filter_deviation, - coord_t beading_propagation_transition_dist, - int layer_idx, - SectionType section_type) +SkeletalTrapezoidation::SkeletalTrapezoidation( + const Polygons& polys, + const BeadingStrategy& beading_strategy, + AngleRadians transitioning_angle, + coord_t discretization_step_size, + coord_t transition_filter_dist, + coord_t allowed_filter_deviation, + coord_t beading_propagation_transition_dist, + int layer_idx, + SectionType section_type) : transitioning_angle(transitioning_angle) , discretization_step_size(discretization_step_size) , transition_filter_dist(transition_filter_dist) @@ -464,6 +478,7 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, , layer_idx(layer_idx) , section_type(section_type) { + scripta::log("skeletal_trapezoidation_0", polys, section_type, layer_idx); constructFromPolygons(polys); } @@ -657,44 +672,164 @@ void SkeletalTrapezoidation::generateToolpaths(std::vector& } updateBeadCount(); - scripta::log("st_graph_0", graph, section_type, layer_idx, - scripta::CellVDI{"is_central", [](const auto& edge){ return static_cast(edge.data.is_central); } }, - scripta::CellVDI{"type", [](const auto& edge){ return static_cast(edge.data.type); } }, - scripta::PointVDI{"distance_to_boundary", [](const auto& node){ return node->data.distance_to_boundary; } }, - scripta::PointVDI{"bead_count", [](const auto& node){ return node->data.bead_count; } }, - scripta::PointVDI{"transition_ratio", [](const auto& node){ return node->data.transition_ratio; } }); + scripta::log( + "st_graph_0", + graph, + section_type, + layer_idx, + scripta::CellVDI{ "is_central", + [](const auto& edge) + { + return static_cast(edge.data.is_central); + } }, + scripta::CellVDI{ "type", + [](const auto& edge) + { + return static_cast(edge.data.type); + } }, + scripta::PointVDI{ "distance_to_boundary", + [](const auto& node) + { + return node->data.distance_to_boundary; + } }, + scripta::PointVDI{ "bead_count", + [](const auto& node) + { + return node->data.bead_count; + } }, + scripta::PointVDI{ "transition_ratio", + [](const auto& node) + { + return node->data.transition_ratio; + } }); filterNoncentralRegions(); - scripta::log("st_graph_1", graph, section_type, layer_idx, - scripta::CellVDI{"is_central", [](const auto& edge){ return static_cast(edge.data.is_central); } }, - scripta::CellVDI{"type", [](const auto& edge){ return static_cast(edge.data.type); } }, - scripta::PointVDI{"distance_to_boundary", [](const auto& node){ return node->data.distance_to_boundary; } }, - scripta::PointVDI{"bead_count", [](const auto& node){ return node->data.bead_count; } }, - scripta::PointVDI{"transition_ratio", [](const auto& node){ return node->data.transition_ratio; } }); + scripta::log( + "st_graph_1", + graph, + section_type, + layer_idx, + scripta::CellVDI{ "is_central", + [](const auto& edge) + { + return static_cast(edge.data.is_central); + } }, + scripta::CellVDI{ "type", + [](const auto& edge) + { + return static_cast(edge.data.type); + } }, + scripta::PointVDI{ "distance_to_boundary", + [](const auto& node) + { + return node->data.distance_to_boundary; + } }, + scripta::PointVDI{ "bead_count", + [](const auto& node) + { + return node->data.bead_count; + } }, + scripta::PointVDI{ "transition_ratio", + [](const auto& node) + { + return node->data.transition_ratio; + } }); generateTransitioningRibs(); - scripta::log("st_graph_2", graph, section_type, layer_idx, - scripta::CellVDI{"is_central", [](const auto& edge){ return static_cast(edge.data.is_central); } }, - scripta::CellVDI{"type", [](const auto& edge){ return static_cast(edge.data.type); } }, - scripta::PointVDI{"distance_to_boundary", [](const auto& node){ return node->data.distance_to_boundary; } }, - scripta::PointVDI{"bead_count", [](const auto& node){ return node->data.bead_count; } }, - scripta::PointVDI{"transition_ratio", [](const auto& node){ return node->data.transition_ratio; } }); + scripta::log( + "st_graph_2", + graph, + section_type, + layer_idx, + scripta::CellVDI{ "is_central", + [](const auto& edge) + { + return static_cast(edge.data.is_central); + } }, + scripta::CellVDI{ "type", + [](const auto& edge) + { + return static_cast(edge.data.type); + } }, + scripta::PointVDI{ "distance_to_boundary", + [](const auto& node) + { + return node->data.distance_to_boundary; + } }, + scripta::PointVDI{ "bead_count", + [](const auto& node) + { + return node->data.bead_count; + } }, + scripta::PointVDI{ "transition_ratio", + [](const auto& node) + { + return node->data.transition_ratio; + } }); generateExtraRibs(); - scripta::log("st_graph_3", graph, section_type, layer_idx, - scripta::CellVDI{"is_central", [](const auto& edge){ return static_cast(edge.data.is_central); } }, - scripta::CellVDI{"type", [](const auto& edge){ return static_cast(edge.data.type); } }, - scripta::PointVDI{"distance_to_boundary", [](const auto& node){ return node->data.distance_to_boundary; } }, - scripta::PointVDI{"bead_count", [](const auto& node){ return node->data.bead_count; } }, - scripta::PointVDI{"transition_ratio", [](const auto& node){ return node->data.transition_ratio; } }); + scripta::log( + "st_graph_3", + graph, + section_type, + layer_idx, + scripta::CellVDI{ "is_central", + [](const auto& edge) + { + return static_cast(edge.data.is_central); + } }, + scripta::CellVDI{ "type", + [](const auto& edge) + { + return static_cast(edge.data.type); + } }, + scripta::PointVDI{ "distance_to_boundary", + [](const auto& node) + { + return node->data.distance_to_boundary; + } }, + scripta::PointVDI{ "bead_count", + [](const auto& node) + { + return node->data.bead_count; + } }, + scripta::PointVDI{ "transition_ratio", + [](const auto& node) + { + return node->data.transition_ratio; + } }); generateSegments(); - scripta::log("st_graph_4", graph, section_type, layer_idx, - scripta::CellVDI{"is_central", [](const auto& edge){ return static_cast(edge.data.is_central); } }, - scripta::CellVDI{"type", [](const auto& edge){ return static_cast(edge.data.type); } }, - scripta::PointVDI{"distance_to_boundary", [](const auto& node){ return node->data.distance_to_boundary; } }, - scripta::PointVDI{"bead_count", [](const auto& node){ return node->data.bead_count; } }, - scripta::PointVDI{"transition_ratio", [](const auto& node){ return node->data.transition_ratio; } }); + scripta::log( + "st_graph_4", + graph, + section_type, + layer_idx, + scripta::CellVDI{ "is_central", + [](const auto& edge) + { + return static_cast(edge.data.is_central); + } }, + scripta::CellVDI{ "type", + [](const auto& edge) + { + return static_cast(edge.data.type); + } }, + scripta::PointVDI{ "distance_to_boundary", + [](const auto& node) + { + return node->data.distance_to_boundary; + } }, + scripta::PointVDI{ "bead_count", + [](const auto& node) + { + return node->data.bead_count; + } }, + scripta::PointVDI{ "transition_ratio", + [](const auto& node) + { + return node->data.transition_ratio; + } }); } void SkeletalTrapezoidation::updateIsCentral() @@ -1061,7 +1196,8 @@ void SkeletalTrapezoidation::filterTransitionMids() } } -std::list SkeletalTrapezoidation::dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up) +std::list + SkeletalTrapezoidation::dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up) { std::list to_be_dissolved; if (traveled_dist > max_dist) @@ -1088,7 +1224,9 @@ std::list SkeletalTrapezoidation::diss const coord_t radius_here = edge->from->data.distance_to_boundary; const bool dissolve_result_is_odd = bool(origin_transition.lower_bead_count % 2) == going_up; const coord_t width_deviation = std::abs(origin_radius - radius_here) * 2; // times by two because the deviation happens at both sides of the significant edge - const coord_t line_width_deviation = dissolve_result_is_odd ? width_deviation : width_deviation / 2; // assume the deviation will be split over either 1 or 2 lines, i.e. assume wall_distribution_count = 1 + const coord_t line_width_deviation = dissolve_result_is_odd + ? width_deviation + : width_deviation / 2; // assume the deviation will be split over either 1 or 2 lines, i.e. assume wall_distribution_count = 1 if (line_width_deviation > allowed_filter_deviation) { should_dissolve = false; @@ -1115,7 +1253,8 @@ std::list SkeletalTrapezoidation::diss } if (should_dissolve && ! seen_transition_on_this_edge) { - std::list to_be_dissolved_here = dissolveNearbyTransitions(edge, origin_transition, traveled_dist + ab_size, max_dist, going_up); + std::list to_be_dissolved_here + = dissolveNearbyTransitions(edge, origin_transition, traveled_dist + ab_size, max_dist, going_up); if (to_be_dissolved_here.empty()) { // The region is too long to be dissolved in this direction, so it cannot be dissolved in any direction. to_be_dissolved.clear(); @@ -1215,7 +1354,7 @@ void SkeletalTrapezoidation::generateTransitionEnds(edge_t& edge, coord_t mid_po const float transition_mid_position = beading_strategy.getTransitionAnchorPos(lower_bead_count); constexpr float inner_bead_width_ratio_after_transition = 1.0; - constexpr coord_t start_rest = 0; + constexpr Ratio start_rest{ 0.0 }; const float mid_rest = transition_mid_position * inner_bead_width_ratio_after_transition; constexpr float end_rest = inner_bead_width_ratio_after_transition; @@ -1233,7 +1372,7 @@ void SkeletalTrapezoidation::generateTransitionEnds(edge_t& edge, coord_t mid_po #ifdef DEBUG if (! generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends)) { - spdlog::warn("There must have been at least one direction in which the bead count is increasing enough for the transition to happen!"); + spdlog::debug("There must have been at least one direction in which the bead count is increasing enough for the transition to happen!"); } #else generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends); @@ -1241,14 +1380,15 @@ void SkeletalTrapezoidation::generateTransitionEnds(edge_t& edge, coord_t mid_po } } -bool SkeletalTrapezoidation::generateTransitionEnd(edge_t& edge, - coord_t start_pos, - coord_t end_pos, - coord_t transition_half_length, - Ratio start_rest, - Ratio end_rest, - coord_t lower_bead_count, - ptr_vector_t>& edge_transition_ends) +bool SkeletalTrapezoidation::generateTransitionEnd( + edge_t& edge, + coord_t start_pos, + coord_t end_pos, + coord_t transition_half_length, + Ratio start_rest, + Ratio end_rest, + coord_t lower_bead_count, + ptr_vector_t>& edge_transition_ends) { Point a = edge.from->p; Point b = edge.to->p; @@ -1440,7 +1580,11 @@ void SkeletalTrapezoidation::applyTransitions(ptr_vector_tp - edge.from->p, discretization_step_size) || edge.from->data.distance_to_boundary >= edge.to->data.distance_to_boundary) + if (! edge.data.isCentral() || shorterThen(edge.to->p - edge.from->p, discretization_step_size) + || edge.from->data.distance_to_boundary >= edge.to->data.distance_to_boundary) { continue; } @@ -1574,34 +1719,35 @@ void SkeletalTrapezoidation::generateSegments() } } - std::sort(upward_quad_mids.begin(), - upward_quad_mids.end(), - [this](edge_t* a, edge_t* b) - { - if (a->to->data.distance_to_boundary == b->to->data.distance_to_boundary) - { // PathOrdering between two 'upward' edges of the same distance is important when one of the edges is flat and connected to the other - if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary && b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) - { - coord_t max = std::numeric_limits::max(); - coord_t a_dist_from_up = std::min(a->distToGoUp().value_or(max), a->twin->distToGoUp().value_or(max)) - vSize(a->to->p - a->from->p); - coord_t b_dist_from_up = std::min(b->distToGoUp().value_or(max), b->twin->distToGoUp().value_or(max)) - vSize(b->to->p - b->from->p); - return a_dist_from_up < b_dist_from_up; - } - else if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary) - { - return true; // Edge a might be 'above' edge b - } - else if (b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) - { - return false; // Edge b might be 'above' edge a - } - else - { - // PathOrdering is not important - } - } - return a->to->data.distance_to_boundary > b->to->data.distance_to_boundary; - }); + std::sort( + upward_quad_mids.begin(), + upward_quad_mids.end(), + [this](edge_t* a, edge_t* b) + { + if (a->to->data.distance_to_boundary == b->to->data.distance_to_boundary) + { // PathOrdering between two 'upward' edges of the same distance is important when one of the edges is flat and connected to the other + if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary && b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) + { + coord_t max = std::numeric_limits::max(); + coord_t a_dist_from_up = std::min(a->distToGoUp().value_or(max), a->twin->distToGoUp().value_or(max)) - vSize(a->to->p - a->from->p); + coord_t b_dist_from_up = std::min(b->distToGoUp().value_or(max), b->twin->distToGoUp().value_or(max)) - vSize(b->to->p - b->from->p); + return a_dist_from_up < b_dist_from_up; + } + else if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary) + { + return true; // Edge a might be 'above' edge b + } + else if (b->from->data.distance_to_boundary == b->to->data.distance_to_boundary) + { + return false; // Edge b might be 'above' edge a + } + else + { + // PathOrdering is not important + } + } + return a->to->data.distance_to_boundary > b->to->data.distance_to_boundary; + }); ptr_vector_t node_beadings; { // Store beading @@ -1696,8 +1842,10 @@ void SkeletalTrapezoidation::propagateBeadingsUpward(std::vector& upwar { // Only propagate to places where there is place continue; } - assert((upward_edge->from->data.distance_to_boundary != upward_edge->to->data.distance_to_boundary || shorterThen(upward_edge->to->p - upward_edge->from->p, central_filter_dist)) - && "zero difference R edges should always be central"); + assert( + (upward_edge->from->data.distance_to_boundary != upward_edge->to->data.distance_to_boundary + || shorterThen(upward_edge->to->p - upward_edge->from->p, central_filter_dist)) + && "zero difference R edges should always be central"); coord_t length = vSize(upward_edge->to->p - upward_edge->from->p); BeadingPropagation upper_beading = lower_beading; upper_beading.dist_to_bottom_source += length; @@ -1716,7 +1864,8 @@ void SkeletalTrapezoidation::propagateBeadingsDownward(std::vector& upw if (! upward_quad_mid->data.isCentral()) { // for equidistant edge: propagate from known beading to node with unknown beading - if (upward_quad_mid->from->data.distance_to_boundary == upward_quad_mid->to->data.distance_to_boundary && upward_quad_mid->from->data.hasBeading() && ! upward_quad_mid->to->data.hasBeading()) + if (upward_quad_mid->from->data.distance_to_boundary == upward_quad_mid->to->data.distance_to_boundary && upward_quad_mid->from->data.hasBeading() + && ! upward_quad_mid->to->data.hasBeading()) { propagateBeadingsDownward(upward_quad_mid->twin, node_beadings); } @@ -1785,7 +1934,7 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin // TODO: don't use toolpath locations past the middle! // TODO: stretch bead widths and locations of the higher bead count beading to fit in the left over space coord_t next_inset_idx; - for (next_inset_idx = left.toolpath_locations.size() - 1; next_inset_idx >= 0; next_inset_idx--) + for (next_inset_idx = static_cast(left.toolpath_locations.size()) - 1; next_inset_idx >= 0; next_inset_idx--) { if (switching_radius > left.toolpath_locations[next_inset_idx]) { @@ -1812,7 +1961,8 @@ SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beadin // f*(l-r) + r = s // f*(l-r) = s - r // f = (s-r) / (l-r) - float new_ratio = static_cast(switching_radius - right.toolpath_locations[next_inset_idx]) / static_cast(left.toolpath_locations[next_inset_idx] - right.toolpath_locations[next_inset_idx]); + float new_ratio = static_cast(switching_radius - right.toolpath_locations[next_inset_idx]) + / static_cast(left.toolpath_locations[next_inset_idx] - right.toolpath_locations[next_inset_idx]); new_ratio = std::min(1.0, new_ratio + 0.1); return interpolate(left, new_ratio, right); } @@ -1960,12 +2110,17 @@ std::shared_ptr SkeletalTrapezo { edge_t* edge_to; coord_t dist; - DistEdge(edge_t* edge_to, coord_t dist) : edge_to(edge_to), dist(dist) + DistEdge(edge_t* edge_to, coord_t dist) + : edge_to(edge_to) + , dist(dist) { } }; - auto compare = [](const DistEdge& l, const DistEdge& r) -> bool { return l.dist > r.dist; }; + auto compare = [](const DistEdge& l, const DistEdge& r) -> bool + { + return l.dist > r.dist; + }; std::priority_queue, decltype(compare)> further_edges(compare); bool first = true; for (edge_t* outgoing = node->incident_edge; outgoing && (first || outgoing != node->incident_edge); outgoing = outgoing->twin->next) @@ -2010,19 +2165,21 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c generated_toolpaths.resize(inset_idx + 1); } assert((generated_toolpaths[inset_idx].empty() || ! generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated"); - if (generated_toolpaths[inset_idx].empty() || generated_toolpaths[inset_idx].back().is_odd != is_odd || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent + if (generated_toolpaths[inset_idx].empty() || generated_toolpaths[inset_idx].back().is_odd != is_odd + || generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent ) { force_new_path = true; } - if (! force_new_path && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, 10) && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < 10 - && ! from_is_3way // force new path at 3way intersection + if (! force_new_path && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, 10) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < 10 && ! from_is_3way // force new path at 3way intersection ) { generated_toolpaths[inset_idx].back().junctions.push_back(to); } - else if (! force_new_path && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, 10) && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < 10 - && ! to_is_3way // force new path at 3way intersection + else if ( + ! force_new_path && shorterThen(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, 10) + && std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < 10 && ! to_is_3way // force new path at 3way intersection ) { if (! is_odd) @@ -2125,7 +2282,10 @@ void SkeletalTrapezoidation::connectJunctions(ptr_vector_t& edge_ // assert(std::abs(int(from_junctions.size()) - int(to_junctions.size())) <= 1); // at transitions one end has more beads if (std::abs(int(from_junctions.size()) - int(to_junctions.size())) > 1) { - spdlog::warn("Can't create a transition when connecting two perimeters where the number of beads differs too much! {} vs. {}", from_junctions.size(), to_junctions.size()); + spdlog::warn( + "Can't create a transition when connecting two perimeters where the number of beads differs too much! {} vs. {}", + from_junctions.size(), + to_junctions.size()); } size_t segment_count = std::min(from_junctions.size(), to_junctions.size()); diff --git a/src/SkeletalTrapezoidationGraph.cpp b/src/SkeletalTrapezoidationGraph.cpp index e8c1522d1c..47c356d2a7 100644 --- a/src/SkeletalTrapezoidationGraph.cpp +++ b/src/SkeletalTrapezoidationGraph.cpp @@ -3,17 +3,18 @@ #include "SkeletalTrapezoidationGraph.h" -#include +#include "utils/linearAlg2D.h" +#include "utils/macros.h" #include -#include "utils/linearAlg2D.h" -#include "utils/macros.h" +#include namespace cura { -STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) +STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) + : HalfEdge(data) { } @@ -131,7 +132,8 @@ STHalfEdge* STHalfEdge::getNextUnconnected() return result->twin; } -STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) +STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) + : HalfEdgeNode(data, p) { } @@ -223,7 +225,10 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) } }; - auto should_collapse = [snap_dist](node_t* a, node_t* b) { return shorterThen(a->p - b->p, snap_dist); }; + auto should_collapse = [snap_dist](node_t* a, node_t* b) + { + return shorterThen(a->p - b->p, snap_dist); + }; for (auto edge_it = edges.begin(); edge_it != edges.end();) { @@ -253,10 +258,6 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) { edge_from_3->from = quad_mid->from; edge_from_3->twin->to = quad_mid->from; - if (count > 50) - { - std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n'; - } if (++count > 1000) { break; diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index b568a5a3ef..4b77b449d3 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -1,31 +1,31 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include - #include "SkirtBrim.h" +#include + #include "Application.h" #include "ExtruderTrain.h" #include "Slice.h" +#include "settings/EnumSettings.h" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "support.h" -#include "utils/Simplify.h" //Simplifying the brim/skirt at every inset. -#include "settings/EnumSettings.h" #include "utils/PolylineStitcher.h" +#include "utils/Simplify.h" //Simplifying the brim/skirt at every inset. namespace cura { -SkirtBrim::SkirtBrim(SliceDataStorage& storage) : - storage(storage), - adhesion_type(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("adhesion_type")), - has_ooze_shield(storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0), - has_draft_shield(storage.draft_protection_shield.size() > 0), - extruders(Application::getInstance().current_slice->scene.extruders), - extruder_count(extruders.size()), - extruder_is_used(storage.getExtrudersUsed()) +SkirtBrim::SkirtBrim(SliceDataStorage& storage) + : storage(storage) + , adhesion_type(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("adhesion_type")) + , has_ooze_shield(storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0) + , has_draft_shield(storage.draft_protection_shield.size() > 0) + , extruders(Application::getInstance().current_slice->scene.extruders) + , extruder_count(extruders.size()) + , extruder_is_used(storage.getExtrudersUsed()) { first_used_extruder_nr = 0; for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) @@ -42,7 +42,7 @@ SkirtBrim::SkirtBrim(SliceDataStorage& storage) : // NOTE: the line count will only be satisfied for the first extruder used. skirt_brim_extruder_nr = first_used_extruder_nr; } - + line_widths.resize(extruder_count); skirt_brim_minimal_length.resize(extruder_count); external_polys_only.resize(extruder_count); @@ -50,7 +50,7 @@ SkirtBrim::SkirtBrim(SliceDataStorage& storage) : gap.resize(extruder_count); for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { - if (!extruder_is_used[extruder_nr]) + if (! extruder_is_used[extruder_nr]) { continue; } @@ -86,7 +86,7 @@ std::vector SkirtBrim::generateBrimOffsetPlan(std::vector= 0 && extruder_nr != skirt_brim_extruder_nr) || starting_outlines[extruder_nr].empty()) + if (! extruder_is_used[extruder_nr] || (skirt_brim_extruder_nr >= 0 && extruder_nr != skirt_brim_extruder_nr) || starting_outlines[extruder_nr].empty()) { continue; // only include offsets for brim extruder } @@ -110,39 +110,16 @@ std::vector SkirtBrim::generateBrimOffsetPlan(std::vector SkirtBrim::generatePrimeTowerBrimForSkirtAdhesionOffsetPlan() -{ - std::vector prime_brim_offsets; - - const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const bool prime_tower_brim_enable = global_settings.get("prime_tower_brim_enable"); - if (adhesion_type == EPlatformAdhesion::SKIRT && prime_tower_brim_enable && storage.primeTower.enabled) - { - const int extruder_nr = storage.primeTower.extruder_order[0]; - const ExtruderTrain& extruder = extruders[extruder_nr]; - int line_count = extruder.settings.get("brim_line_count"); - coord_t gap = extruder.settings.get("brim_gap"); - for (int line_idx = 0; line_idx < line_count; line_idx++) - { - const bool is_last = line_idx == line_count - 1; - coord_t offset = gap + line_widths[extruder_nr] / 2 + line_widths[extruder_nr] * line_idx; - prime_brim_offsets.emplace_back(&storage.primeTower.outer_poly, external_polys_only[extruder_nr], offset, offset, line_idx, extruder_nr, is_last); - } - } - - std::sort(prime_brim_offsets.begin(), prime_brim_offsets.end(), OffsetSorter); - return prime_brim_offsets; -} - void SkirtBrim::generate() { std::vector starting_outlines(extruder_count); std::vector all_brim_offsets = generateBrimOffsetPlan(starting_outlines); - std::vector prime_brim_offsets_for_skirt = generatePrimeTowerBrimForSkirtAdhesionOffsetPlan(); - + constexpr LayerIndex layer_nr = 0; constexpr bool include_support = true; - Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, /*include_prime_tower*/ true, /*external_polys_only*/ false); + const bool include_prime_tower = adhesion_type == EPlatformAdhesion::SKIRT; + const bool has_prime_tower = storage.primeTower.enabled; + Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, /*external_polys_only*/ false); std::vector allowed_areas_per_extruder(extruder_count); for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) @@ -159,14 +136,11 @@ void SkirtBrim::generate() // so that the brim lines don't overlap with the holes by half the line width allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(getInternalHoleExclusionArea(covered_area, extruder_nr)); } - } - // Note that the brim generated here for the prime-tower is _only_ when the rest if the model uses skirt. - // If everything uses brim to begin with, _including_ the prime-tower, it's not generated here, but along the rest. - if (! prime_brim_offsets_for_skirt.empty()) - { - // Note that his ignores the returned lengths: Less 'correct' in a sense, will be predictable for the user. - generatePrimaryBrim(prime_brim_offsets_for_skirt, covered_area, allowed_areas_per_extruder); + if (has_prime_tower) + { + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(storage.primeTower.getGroundPoly()); + } } // Apply 'approximate convex hull' if the adhesion is skirt _after_ any skirt but also prime-tower-brim adhesion. @@ -192,7 +166,7 @@ void SkirtBrim::generate() // Secondary brim of all other materials which don;t meet minimum length constriant yet generateSecondarySkirtBrim(covered_area, allowed_areas_per_extruder, total_length); - + // simplify paths to prevent buffer unnerruns in firmware const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const coord_t maximum_resolution = global_settings.get("meshfix_maximum_resolution"); @@ -210,7 +184,7 @@ void SkirtBrim::generate() std::vector SkirtBrim::generatePrimaryBrim(std::vector& all_brim_offsets, Polygons& covered_area, std::vector& allowed_areas_per_extruder) { - std::vector total_length(extruder_count, 0u); + std::vector total_length(extruder_count, 0U); for (size_t offset_idx = 0; offset_idx < all_brim_offsets.size(); offset_idx++) { @@ -220,32 +194,29 @@ std::vector SkirtBrim::generatePrimaryBrim(std::vector& all_bri storage.skirt_brim[offset.extruder_nr].resize(offset.inset_idx + 1); } SkirtBrimLine& output_location = storage.skirt_brim[offset.extruder_nr][offset.inset_idx]; - coord_t added_length = generateOffset(offset, covered_area, allowed_areas_per_extruder, output_location); - if (! added_length) + const coord_t added_length = generateOffset(offset, covered_area, allowed_areas_per_extruder, output_location); + + if (added_length == 0) { // no more place for more brim. Trying to satisfy minimum length constraint with generateSecondarySkirtBrim - break; + continue; } total_length[offset.extruder_nr] += added_length; - - if - ( - offset.is_last && - total_length[offset.extruder_nr] < skirt_brim_minimal_length[offset.extruder_nr] && // This was the last offset of this extruder, but the brim lines don't meet minimal length yet + + if (offset.is_last && total_length[offset.extruder_nr] < skirt_brim_minimal_length[offset.extruder_nr] + && // This was the last offset of this extruder, but the brim lines don't meet minimal length yet total_length[offset.extruder_nr] > 0u // No lines got added; we have no extrusion lines to build on ) { offset.is_last = false; constexpr bool is_last = true; - all_brim_offsets.emplace_back - ( + all_brim_offsets.emplace_back( offset.inset_idx, external_polys_only[offset.extruder_nr], line_widths[offset.extruder_nr], offset.total_offset + line_widths[offset.extruder_nr], offset.inset_idx + 1, offset.extruder_nr, - is_last - ); + is_last); std::sort(all_brim_offsets.begin() + offset_idx + 1, all_brim_offsets.end(), OffsetSorter); // reorder remaining offsets } } @@ -267,7 +238,7 @@ Polygons SkirtBrim::getInternalHoleExclusionArea(const Polygons& outline, const { Polygon hole_poly = part[hole_idx]; hole_poly.reverse(); - Polygons disallowed_region = hole_poly.offset(10u).difference(hole_poly.offset( - line_widths[extruder_nr] / 2 - hole_brim_distance)); + Polygons disallowed_region = hole_poly.offset(10u).difference(hole_poly.offset(-line_widths[extruder_nr] / 2 - hole_brim_distance)); ret = ret.unionPolygons(disallowed_region); } } @@ -315,12 +286,10 @@ coord_t SkirtBrim::generateOffset(const Offset& offset, Polygons& covered_area, auto offset_dist = line_widths[offset.extruder_nr]; Polygons local_brim; - auto closed_polygons_brim = storage.skirt_brim[offset.extruder_nr][reference_idx].closed_polygons - .offset(offset_dist, ClipperLib::jtRound); + auto closed_polygons_brim = storage.skirt_brim[offset.extruder_nr][reference_idx].closed_polygons.offsetPolyLine(offset_dist, ClipperLib::jtRound, true); local_brim.add(closed_polygons_brim); - auto open_polylines_brim = storage.skirt_brim[offset.extruder_nr][reference_idx].open_polylines - .offsetPolyLine(offset_dist, ClipperLib::jtRound); + auto open_polylines_brim = storage.skirt_brim[offset.extruder_nr][reference_idx].open_polylines.offsetPolyLine(offset_dist, ClipperLib::jtRound); local_brim.add(open_polylines_brim); local_brim.unionPolygons(); @@ -338,9 +307,9 @@ coord_t SkirtBrim::generateOffset(const Offset& offset, Polygons& covered_area, const coord_t max_stitch_distance = line_widths[offset.extruder_nr]; PolylineStitcher::stitch(brim_lines, result.open_polylines, result.closed_polygons, max_stitch_distance); - + // clean up too small lines - for (size_t line_idx = 0; line_idx < result.open_polylines.size(); ) + for (size_t line_idx = 0; line_idx < result.open_polylines.size();) { PolygonRef line = result.open_polylines[line_idx]; if (line.shorterThan(min_brim_line_length)) @@ -353,7 +322,7 @@ coord_t SkirtBrim::generateOffset(const Offset& offset, Polygons& covered_area, } } } - + { // update allowed_areas_per_extruder for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { @@ -373,28 +342,44 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) Polygons first_layer_outline; Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; int reference_extruder_nr = skirt_brim_extruder_nr; - assert( ! (reference_extruder_nr == -1 && extruder_nr == -1) && "We should only request the outlines of all layers when the brim is being generated for only one material"); + assert(! (reference_extruder_nr == -1 && extruder_nr == -1) && "We should only request the outlines of all layers when the brim is being generated for only one material"); if (reference_extruder_nr == -1) { reference_extruder_nr = extruder_nr; } const int primary_line_count = line_count[reference_extruder_nr]; - const bool external_only = adhesion_type == EPlatformAdhesion::SKIRT || external_polys_only[reference_extruder_nr]; // Whether to include holes or not. Skirt doesn't have any holes. + const bool external_only + = adhesion_type == EPlatformAdhesion::SKIRT || external_polys_only[reference_extruder_nr]; // Whether to include holes or not. Skirt doesn't have any holes. + const bool has_prime_tower = storage.primeTower.enabled; const LayerIndex layer_nr = 0; if (adhesion_type == EPlatformAdhesion::SKIRT) { constexpr bool include_support = true; - const bool skirt_around_prime_tower_brim = storage.primeTower.enabled && global_settings.get("prime_tower_brim_enable"); - const bool include_prime_tower = ! skirt_around_prime_tower_brim; // include manually otherwise - first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_only, extruder_nr); - if (skirt_around_prime_tower_brim) + const bool include_prime_tower = ! has_prime_tower; // include manually otherwise + + first_layer_outline = Polygons(); + int skirt_height = 0; + for (const auto& extruder : Application::getInstance().current_slice->scene.extruders) + { + if (extruder_nr == -1 || extruder_nr == extruder.extruder_nr) + { + skirt_height = std::max(skirt_height, extruder.settings.get("skirt_height")); + } + } + skirt_height = std::min(skirt_height, static_cast(storage.print_layer_count)); + + for (int i_layer = layer_nr; i_layer < skirt_height; ++i_layer) { - const int prime_tower_brim_extruder_nr = storage.primeTower.extruder_order[0]; - const ExtruderTrain& prime_tower_brim_extruder = extruders[prime_tower_brim_extruder_nr]; - int line_count = prime_tower_brim_extruder.settings.get("brim_line_count"); - coord_t tower_gap = prime_tower_brim_extruder.settings.get("brim_gap"); - coord_t brim_width = tower_gap + line_count * line_widths[prime_tower_brim_extruder_nr]; - first_layer_outline = first_layer_outline.unionPolygons(storage.primeTower.outer_poly.offset(brim_width)); + for (const auto& extruder : Application::getInstance().current_slice->scene.extruders) + { + first_layer_outline + = first_layer_outline.unionPolygons(storage.getLayerOutlines(i_layer, include_support, include_prime_tower, external_only, extruder.extruder_nr)); + } + } + + if (has_prime_tower) + { + first_layer_outline = first_layer_outline.unionPolygons(storage.primeTower.getGroundPoly()); } Polygons shields; @@ -418,7 +403,7 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) else { // add brim underneath support by removing support where there's brim around the model constexpr bool include_support = false; // Include manually below. - constexpr bool include_prime_tower = false; // Include manually below. + constexpr bool include_prime_tower = false; // Not included. constexpr bool external_outlines_only = false; // Remove manually below. first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_outlines_only, extruder_nr); first_layer_outline = first_layer_outline.unionPolygons(); // To guard against overlapping outlines, which would produce holes according to the even-odd rule. @@ -429,8 +414,7 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) first_layer_outline = first_layer_outline.removeEmptyHoles(); } if (storage.support.generated && primary_line_count > 0 && ! storage.support.supportLayers.empty() - && (extruder_nr == -1 || extruder_nr == global_settings.get("support_infill_extruder_nr")) - ) + && (extruder_nr == -1 || extruder_nr == global_settings.get("support_infill_extruder_nr"))) { // remove model-brim from support SupportLayer& support_layer = storage.support.supportLayers[0]; const ExtruderTrain& support_infill_extruder = global_settings.get("support_infill_extruder_nr"); @@ -444,8 +428,9 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) // |+-+| |+--+| // +---+ +----+ const coord_t primary_extruder_skirt_brim_line_width = line_widths[reference_extruder_nr]; - Polygons model_brim_covered_area = first_layer_outline.offset(primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), - ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides + Polygons model_brim_covered_area = first_layer_outline.offset( + primary_extruder_skirt_brim_line_width * (primary_line_count + primary_line_count % 2), + ClipperLib::jtRound); // always leave a gap of an even number of brim lines, so that it fits if it's generating brim from both sides if (external_only) { // don't remove support within empty holes where no brim is generated. model_brim_covered_area.add(first_layer_empty_holes); @@ -463,15 +448,6 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) first_layer_outline.add(support_layer.support_bottom); first_layer_outline.add(support_layer.support_roof); } - if - ( - storage.primeTower.enabled && - global_settings.get("prime_tower_brim_enable") && - (extruder_nr == -1 || int(storage.primeTower.extruder_order[0]) == extruder_nr) - ) - { - first_layer_outline.add(storage.primeTower.outer_poly); // don't remove parts of the prime tower, but make a brim for it - } } constexpr coord_t join_distance = 20; first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon @@ -507,7 +483,8 @@ void SkirtBrim::generateShieldBrim(Polygons& brim_covered_area, std::vector expand to fit an extra brim line // |+-+| |+--+| // +---+ +----+ - const coord_t primary_skirt_brim_width = (primary_line_count + primary_line_count % 2) * primary_extruder_skirt_brim_line_width; // always use an even number, because we will fil the area from both sides + const coord_t primary_skirt_brim_width + = (primary_line_count + primary_line_count % 2) * primary_extruder_skirt_brim_line_width; // always use an even number, because we will fil the area from both sides Polygons shield_brim; if (has_ooze_shield) @@ -516,7 +493,8 @@ void SkirtBrim::generateShieldBrim(Polygons& brim_covered_area, std::vector 0) { shield_brim = shield_brim.offset(-primary_extruder_skirt_brim_line_width); - storage.skirt_brim[extruder_nr].back().closed_polygons.add(shield_brim); // throw all polygons for the shileds onto one heap; because the brim lines are generated from both sides the order will not be important + storage.skirt_brim[extruder_nr].back().closed_polygons.add( + shield_brim); // throw all polygons for the shileds onto one heap; because the brim lines are generated from both sides the order will not be important } } @@ -577,19 +556,19 @@ void SkirtBrim::generateSecondarySkirtBrim(Polygons& covered_area, std::vector

scene; const ExtruderTrain& support_infill_extruder = scene.current_mesh_group->settings.get("support_infill_extruder_nr"); - const coord_t brim_line_width = support_infill_extruder.settings.get("skirt_brim_line_width") * support_infill_extruder.settings.get("initial_layer_line_width_factor"); + const coord_t brim_line_width + = support_infill_extruder.settings.get("skirt_brim_line_width") * support_infill_extruder.settings.get("initial_layer_line_width_factor"); size_t line_count = support_infill_extruder.settings.get("support_brim_line_count"); const coord_t minimal_length = support_infill_extruder.settings.get("skirt_brim_minimal_length"); if (! storage.support.generated || line_count <= 0 || storage.support.supportLayers.empty()) @@ -611,6 +591,12 @@ void SkirtBrim::generateSupportBrim() const coord_t brim_width = brim_line_width * line_count; coord_t skirt_brim_length = 0; + + if (storage.skirt_brim[support_infill_extruder.extruder_nr].empty()) + { + storage.skirt_brim[support_infill_extruder.extruder_nr].emplace_back(); + } + for (const SkirtBrimLine& brim_line : storage.skirt_brim[support_infill_extruder.extruder_nr]) { skirt_brim_length += brim_line.closed_polygons.polygonLength(); @@ -645,8 +631,8 @@ void SkirtBrim::generateSupportBrim() } storage.support_brim.add(brim_line); - - const coord_t length = skirt_brim_length + storage.support_brim.polygonLength(); + // In case of adhesion::NONE length of support brim is only the length of the brims formed for the support + const coord_t length = (adhesion_type == EPlatformAdhesion::NONE) ? skirt_brim_length : skirt_brim_length + storage.support_brim.polygonLength(); if (skirt_brim_number + 1 >= line_count && length > 0 && length < minimal_length) // Make brim or skirt have more lines when total length is too small. { line_count++; diff --git a/src/SupportInfillPart.cpp b/src/SupportInfillPart.cpp index 76344af468..ae0b8cf7c1 100644 --- a/src/SupportInfillPart.cpp +++ b/src/SupportInfillPart.cpp @@ -2,17 +2,19 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "SupportInfillPart.h" + #include "support.h" using namespace cura; -SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate, coord_t custom_line_distance) -: outline(outline) -, outline_boundary_box(outline) -, support_line_width(support_line_width) -, inset_count_to_generate(inset_count_to_generate) -, custom_line_distance(custom_line_distance) +SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate, coord_t custom_line_distance) + : outline(outline) + , outline_boundary_box(outline) + , support_line_width(support_line_width) + , inset_count_to_generate(inset_count_to_generate) + , custom_line_distance(custom_line_distance) + , use_fractional_config(use_fractional_config) { infill_area_per_combine_per_density.clear(); } diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 86d546e4ad..9ed9cc7ede 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -1,28 +1,29 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include "infill.h" -#include "LayerPlan.h" -#include "sliceDataStorage.h" #include "TopSurface.h" + #include "ExtruderTrain.h" +#include "LayerPlan.h" +#include "infill.h" +#include "sliceDataStorage.h" namespace cura { TopSurface::TopSurface() { - //Do nothing. Areas stays empty. + // Do nothing. Areas stays empty. } void TopSurface::setAreasFromMeshAndLayerNumber(SliceMeshStorage& mesh, size_t layer_number) { - //The top surface is all parts of the mesh where there's no mesh above it, so find the layer above it first. + // The top surface is all parts of the mesh where there's no mesh above it, so find the layer above it first. Polygons mesh_above; if (layer_number < mesh.layers.size() - 1) { mesh_above = mesh.layers[layer_number + 1].getOutlines(); - } //If this is the top-most layer, mesh_above stays empty. + } // If this is the top-most layer, mesh_above stays empty. if (mesh.settings.get("magic_spiralize")) { @@ -39,13 +40,14 @@ void TopSurface::setAreasFromMeshAndLayerNumber(SliceMeshStorage& mesh, size_t l } } -bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer, const FffGcodeWriter& gcode_writer) const +bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const GCodePathConfig& line_config, LayerPlan& layer, const FffGcodeWriter& gcode_writer) + const { if (areas.empty()) { - return false; //Nothing to do. + return false; // Nothing to do. } - //Generate the lines to cover the surface. + // Generate the lines to cover the surface. const int extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; const EFillMethod pattern = mesh.settings.get("ironing_pattern"); const bool zig_zaggify_infill = pattern == EFillMethod::ZIG_ZAG; @@ -55,7 +57,7 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); const std::vector& top_most_skin_angles = (roofing_layer_count > 0) ? mesh.roofing_angles : mesh.skin_angles; assert(top_most_skin_angles.size() > 0); - const AngleDegrees direction = top_most_skin_angles[layer.getLayerNr() % top_most_skin_angles.size()] + AngleDegrees(90.0); //Always perpendicular to the skin lines. + const AngleDegrees direction = top_most_skin_angles[layer.getLayerNr() % top_most_skin_angles.size()] + AngleDegrees(90.0); // Always perpendicular to the skin lines. constexpr coord_t infill_overlap = 0; constexpr int infill_multiplier = 1; constexpr coord_t shift = 0; @@ -64,59 +66,77 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage const Ratio ironing_flow = mesh.settings.get("ironing_flow"); const bool enforce_monotonic_order = mesh.settings.get("ironing_monotonic"); constexpr size_t wall_line_count = 0; - const coord_t small_area_width = mesh.settings.get("min_even_wall_line_width") * 2; // Maximum width of a region that can still be filled with one wall. + const coord_t small_area_width = 0; // This shouldn't be on for ironing. const Point infill_origin = Point(); const bool skip_line_stitching = enforce_monotonic_order; coord_t ironing_inset = -mesh.settings.get("ironing_inset"); if (pattern == EFillMethod::ZIG_ZAG) { - //Compensate for the outline_offset decrease that takes place when using the infill generator to generate ironing with the zigzag pattern + // Compensate for the outline_offset decrease that takes place when using the infill generator to generate ironing with the zigzag pattern const Ratio width_scale = (float)mesh.settings.get("layer_height") / mesh.settings.get("infill_sparse_thickness"); ironing_inset += width_scale * line_width / 2; - //Align the edge of the ironing line with the edge of the outer wall + // Align the edge of the ironing line with the edge of the outer wall ironing_inset -= ironing_flow * line_width / 2; } else if (pattern == EFillMethod::CONCENTRIC) { - //Counteract the outline_offset increase that takes place when using the infill generator to generate ironing with the concentric pattern + // Counteract the outline_offset increase that takes place when using the infill generator to generate ironing with the concentric pattern ironing_inset += line_spacing - line_width / 2; - //Align the edge of the ironing line with the edge of the outer wall + // Align the edge of the ironing line with the edge of the outer wall ironing_inset -= ironing_flow * line_width / 2; } Polygons ironed_areas = areas.offset(ironing_inset); - Infill infill_generator(pattern, zig_zaggify_infill, connect_polygons, ironed_areas, line_width, line_spacing, infill_overlap, infill_multiplier, direction, layer.z - 10, shift, max_resolution, max_deviation, wall_line_count, small_area_width, infill_origin, skip_line_stitching); + Infill infill_generator( + pattern, + zig_zaggify_infill, + connect_polygons, + ironed_areas, + line_width, + line_spacing, + infill_overlap, + infill_multiplier, + direction, + layer.z - 10, + shift, + max_resolution, + max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_line_stitching); std::vector ironing_paths; Polygons ironing_polygons; Polygons ironing_lines; infill_generator.generate(ironing_paths, ironing_polygons, ironing_lines, mesh.settings, layer.getLayerNr(), SectionType::IRONING); - if(ironing_polygons.empty() && ironing_lines.empty() && ironing_paths.empty()) + if (ironing_polygons.empty() && ironing_lines.empty() && ironing_paths.empty()) { - return false; //Nothing to do. + return false; // Nothing to do. } layer.mode_skip_agressive_merge = true; bool added = false; - if(!ironing_polygons.empty()) + if (! ironing_polygons.empty()) { constexpr bool force_comb_retract = false; layer.addTravel(ironing_polygons[0][0], force_comb_retract); layer.addPolygonsByOptimizer(ironing_polygons, line_config, ZSeamConfig()); added = true; } - if(!ironing_lines.empty()) + if (! ironing_lines.empty()) { if (pattern == EFillMethod::LINES || pattern == EFillMethod::ZIG_ZAG) { - //Move to a corner of the area that is perpendicular to the ironing lines, to reduce the number of seams. + // Move to a corner of the area that is perpendicular to the ironing lines, to reduce the number of seams. const AABB bounding_box(ironed_areas); PointMatrix rotate(-direction + 90); const Point center = bounding_box.getMiddle(); - const Point far_away = rotate.apply(Point(0, vSize(bounding_box.max - center) * 100)); //Some direction very far away in the direction perpendicular to the ironing lines, relative to the centre. - //Two options to start, both perpendicular to the ironing lines. Which is closer? + const Point far_away = rotate.apply( + Point(0, vSize(bounding_box.max - center) * 100)); // Some direction very far away in the direction perpendicular to the ironing lines, relative to the centre. + // Two options to start, both perpendicular to the ironing lines. Which is closer? const Point front_side = PolygonUtils::findNearestVert(center + far_away, ironed_areas).p(); const Point back_side = PolygonUtils::findNearestVert(center - far_away, ironed_areas).p(); if (vSize2(layer.getLastPlannedPositionOrStartingPosition() - front_side) < vSize2(layer.getLastPlannedPositionOrStartingPosition() - back_side)) @@ -129,25 +149,40 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage } } - if( ! enforce_monotonic_order) + if (! enforce_monotonic_order) { layer.addLinesByOptimizer(ironing_lines, line_config, SpaceFillType::PolyLines); } else { - const coord_t max_adjacent_distance = line_spacing * 1.1; //Lines are considered adjacent - meaning they need to be printed in monotonic order - if spaced 1 line apart, with 10% extra play. + const coord_t max_adjacent_distance + = line_spacing * 1.1; // Lines are considered adjacent - meaning they need to be printed in monotonic order - if spaced 1 line apart, with 10% extra play. layer.addLinesMonotonic(Polygons(), ironing_lines, line_config, SpaceFillType::PolyLines, AngleRadians(direction), max_adjacent_distance); } added = true; } - if(!ironing_paths.empty()) + if (! ironing_paths.empty()) { constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0u; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); - InsetOrderOptimizer wall_orderer(gcode_writer, storage, layer, mesh.settings, extruder_nr, - line_config, line_config, line_config, line_config, - retract_before_outer_wall, wipe_dist, wipe_dist, extruder_nr, extruder_nr, z_seam_config, ironing_paths); + InsetOrderOptimizer wall_orderer( + gcode_writer, + storage, + layer, + mesh.settings, + extruder_nr, + line_config, + line_config, + line_config, + line_config, + retract_before_outer_wall, + wipe_dist, + wipe_dist, + extruder_nr, + extruder_nr, + z_seam_config, + ironing_paths); wall_orderer.addToLayer(); added = true; } @@ -156,4 +191,4 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage return added; } -} +} // namespace cura diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index f7ab838ab0..4604967baf 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -1,7 +1,13 @@ -// Copyright (c) 2021 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "TreeModelVolumes.h" + +#include +#include +#include +#include + #include "TreeSupport.h" #include "TreeSupportEnums.h" #include "progress/Progress.h" @@ -9,16 +15,10 @@ #include "utils/ThreadPool.h" #include "utils/algorithm.h" -#include -#include -#include -#include - namespace cura { -TreeModelVolumes::TreeModelVolumes -( +TreeModelVolumes::TreeModelVolumes( const SliceDataStorage& storage, const coord_t max_move, const coord_t max_move_slow, @@ -26,15 +26,16 @@ TreeModelVolumes::TreeModelVolumes size_t current_mesh_idx, double progress_multiplier, double progress_offset, - const std::vector& additional_excluded_areas -) : - max_move_{ std::max(max_move - 2, coord_t(0)) }, // -2 to avoid rounding errors - max_move_slow_{ std::max(max_move_slow - 2, coord_t(0)) }, // -2 to avoid rounding errors - min_offset_per_step_{ min_offset_per_step }, - progress_multiplier{ progress_multiplier }, - progress_offset{ progress_offset }, - machine_border_{ calculateMachineBorderCollision(storage.getMachineBorder())}, - machine_area_ { storage.getMachineBorder()} + const std::vector& additional_excluded_areas) + : max_move_{ std::max(max_move - 2, coord_t(0)) } + , // -2 to avoid rounding errors + max_move_slow_{ std::max(max_move_slow - 2, coord_t(0)) } + , // -2 to avoid rounding errors + min_offset_per_step_{ min_offset_per_step } + , progress_multiplier{ progress_multiplier } + , progress_offset{ progress_offset } + , machine_border_{ calculateMachineBorderCollision(storage.getMachineBorder()) } + , machine_area_{ storage.getMachineBorder() } { anti_overhang_ = std::vector(storage.support.supportLayers.size(), Polygons()); std::unordered_map mesh_to_layeroutline_idx; @@ -48,8 +49,9 @@ TreeModelVolumes::TreeModelVolumes coord_t min_maximum_area_deviation = std::numeric_limits::max(); support_rests_on_model = false; - for (auto [mesh_idx, mesh] : storage.meshes | ranges::views::enumerate) + for (auto [mesh_idx, mesh_ptr] : storage.meshes | ranges::views::enumerate) { + auto& mesh = *mesh_ptr; bool added = false; for (auto [idx, layer_outline] : layer_outlines_ | ranges::views::enumerate) { @@ -102,11 +104,10 @@ TreeModelVolumes::TreeModelVolumes { // Workaround for compiler bug on apple-clang -- Closure won't properly capture variables in capture lists in outer scope. const auto& mesh_idx_l = mesh_idx; - const auto& mesh_l = mesh; + const auto& mesh_l = *mesh; // ^^^ Remove when fixed (and rename accordingly in the below parallel-for). - cura::parallel_for - ( + cura::parallel_for( 0, LayerIndex(layer_outlines_[mesh_to_layeroutline_idx[mesh_idx_l]].second.size()), [&](const LayerIndex layer_idx) @@ -117,26 +118,22 @@ TreeModelVolumes::TreeModelVolumes } Polygons outline = extractOutlineFromMesh(mesh_l, layer_idx); layer_outlines_[mesh_to_layeroutline_idx[mesh_idx_l]].second[layer_idx].add(outline); - } - ); + }); } // Merge all the layer outlines together. for (auto& layer_outline : layer_outlines_) { - cura::parallel_for - ( + cura::parallel_for( 0, LayerIndex(anti_overhang_.size()), [&](const LayerIndex layer_idx) { layer_outline.second[layer_idx] = layer_outline.second[layer_idx].unionPolygons(); - } - ); + }); } // Gather all excluded areas, like support-blockers and trees that where already generated. - cura::parallel_for - ( + cura::parallel_for( 0, LayerIndex(anti_overhang_.size()), [&](const LayerIndex layer_idx) @@ -153,14 +150,14 @@ TreeModelVolumes::TreeModelVolumes if (storage.primeTower.enabled) { - anti_overhang_[layer_idx].add(storage.primeTower.outer_poly); + anti_overhang_[layer_idx].add(storage.primeTower.getGroundPoly()); } anti_overhang_[layer_idx] = anti_overhang_[layer_idx].unionPolygons(); - } - ); + }); - for (max_layer_idx_without_blocker =0; max_layer_idx_without_blocker +1 radius_until_layer; - // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if something of the radius calculation is changed. - // Tested overhead was neligable (milliseconds for thounds of layers). + // while it is possible to calculate, up to which layer the avoidance should be calculated, this simulation is easier to understand, and does not need to be adjusted if + // something of the radius calculation is changed. Tested overhead was neligable (milliseconds for thounds of layers). for (LayerIndex simulated_dtt = 0; simulated_dtt <= max_layer; simulated_dtt++) { const LayerIndex current_layer = max_layer - simulated_dtt; @@ -235,7 +233,8 @@ void TreeModelVolumes::precalculate(coord_t max_layer) // Append additional radiis needed for collision. - radius_until_layer[ceilRadius(increase_until_radius, false)] = max_layer; // To calculate collision holefree for every radius, the collision of radius increase_until_radius will be required. + radius_until_layer[ceilRadius(increase_until_radius, false)] + = max_layer; // To calculate collision holefree for every radius, the collision of radius increase_until_radius will be required. // Collision for radius 0 needs to be calculated everywhere, as it will be used to ensure valid xy_distance in drawAreas. radius_until_layer[0] = max_layer; if (current_min_xy_dist_delta != 0) @@ -244,7 +243,10 @@ void TreeModelVolumes::precalculate(coord_t max_layer) } std::deque relevant_collision_radiis; - relevant_collision_radiis.insert(relevant_collision_radiis.end(), radius_until_layer.begin(), radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of old and regular required radius just copy. + relevant_collision_radiis.insert( + relevant_collision_radiis.end(), + radius_until_layer.begin(), + radius_until_layer.end()); // Now that required_avoidance_limit contains the maximum of old and regular required radius just copy. // ### Calculate the relevant collisions calculateCollision(relevant_collision_radiis); @@ -253,7 +255,7 @@ void TreeModelVolumes::precalculate(coord_t max_layer) std::deque relevant_hole_collision_radiis; for (RadiusLayerPair key : relevant_avoidance_radiis) { - spdlog::debug("Calculating avoidance of radius {} up to layer {}",key.first,key.second); + spdlog::debug("Calculating avoidance of radius {} up to layer {}", key.first, key.second); if (key.first < increase_until_radius + current_min_xy_dist_delta) { relevant_hole_collision_radiis.emplace_back(key); @@ -267,12 +269,10 @@ void TreeModelVolumes::precalculate(coord_t max_layer) auto t_acc = std::chrono::high_resolution_clock::now(); - if (max_layer_idx_without_blocker (t_coll - t_start).count(); - const auto dur_acc = 0.001 * std::chrono::duration_cast(t_acc-t_coll).count(); + const auto dur_acc = 0.001 * std::chrono::duration_cast(t_acc - t_coll).count(); const auto dur_avo = 0.001 * std::chrono::duration_cast(t_avo - t_acc).count(); const auto dur_col_avo = 0.001 * std::chrono::duration_cast(t_colAvo - t_avo).count(); - spdlog::info("Pre-calculating collision took {} ms. Pre-calculating avoidance took {} ms. Pre-calculating accumulated Placeables with radius 0 took {} ms. Pre-calculating collision-avoidance took {} ms. ", dur_col, dur_avo, dur_acc, dur_col_avo); - - + spdlog::info( + "Pre-calculating collision took {} ms. Pre-calculating avoidance took {} ms. Pre-calculating accumulated Placeables with radius 0 took {} ms. Pre-calculating " + "collision-avoidance took {} ms. ", + dur_col, + dur_avo, + dur_acc, + dur_col_avo); } const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_idx, bool min_xy_dist) @@ -334,7 +337,8 @@ const Polygons& TreeModelVolumes::getCollision(coord_t radius, LayerIndex layer_ radius += current_min_xy_dist_delta; } - // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as possible. + // special case as if a radius 0 is requested it could be to ensure correct xy distance. As such it is beneficial if the collision is as close to the configured values as + // possible. if (orig_radius != 0) { radius = ceilRadius(radius); @@ -463,9 +467,14 @@ const Polygons& TreeModelVolumes::getAvoidance(coord_t radius, LayerIndex layer_ } if (precalculated) { - spdlog::warn("Had to calculate Avoidance (to model-bool: {}) at radius {} and layer {} and type {}, but precalculate was called. Performance may suffer!", to_model, key.first, key.second,coord_t(type)); + spdlog::warn( + "Had to calculate Avoidance (to model-bool: {}) at radius {} and layer {} and type {}, but precalculate was called. Performance may suffer!", + to_model, + key.first, + key.second, + coord_t(type)); } - if(type == AvoidanceType::COLLISION) + if (type == AvoidanceType::COLLISION) { calculateCollisionAvoidance(key); } @@ -528,7 +537,7 @@ const Polygons& TreeModelVolumes::getWallRestriction(coord_t radius, LayerIndex std::unordered_map* cache_ptr = min_xy_dist ? &wall_restrictions_cache_min_ : &wall_restrictions_cache_; { - std::lock_guard critical_section(min_xy_dist ? *critical_wall_restrictions_cache_min_ : *critical_wall_restrictions_cache_); + std::lock_guard critical_section(min_xy_dist ? *critical_wall_restrictions_cache_min_ : *critical_wall_restrictions_cache_); result = getArea(*cache_ptr, key); } if (result) @@ -604,8 +613,7 @@ LayerIndex TreeModelVolumes::getMaxCalculatedLayer(coord_t radius, const std::un void TreeModelVolumes::calculateCollision(const std::deque& keys) { - cura::parallel_for - ( + cura::parallel_for( 0, keys.size(), [&](const size_t i) @@ -627,8 +635,8 @@ void TreeModelVolumes::calculateCollision(const std::deque& key const LayerIndex max_anti_overhang_layer = anti_overhang_.size() - 1; const LayerIndex max_required_layer = keys[i].second + std::max(coord_t(1), z_distance_top_layers); const coord_t xy_distance = outline_idx == current_outline_idx ? current_min_xy_dist : layer_outlines_[outline_idx].first.get("support_xy_distance"); - // Technically this causes collision for the normal xy_distance to be larger by current_min_xy_dist_delta for all not currently processing meshes as this delta will be added at request time. - // Avoiding this would require saving each collision for each outline_idx separately, + // Technically this causes collision for the normal xy_distance to be larger by current_min_xy_dist_delta for all not currently processing meshes as this delta will + // be added at request time. Avoiding this would require saving each collision for each outline_idx separately, // and later for each avoidance... But avoidance calculation has to be for the whole scene and can NOT be done for each outline_idx separately and combined later. // So avoiding this inaccuracy seems infeasible as it would require 2x the avoidance calculations => 0.5x the performance. coord_t min_layer_bottom; @@ -649,7 +657,9 @@ void TreeModelVolumes::calculateCollision(const std::deque& key { collision_areas.add(layer_outlines_[outline_idx].second[layer_idx]); } - collision_areas = collision_areas.offset(radius + xy_distance); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. + collision_areas = collision_areas.offset( + radius + + xy_distance); // jtRound is not needed here, as the overshoot can not cause errors in the algorithm, because no assumptions are made about the model. data[key].add(collision_areas); // if a key does not exist when it is accessed it is added! } @@ -661,13 +671,14 @@ void TreeModelVolumes::calculateCollision(const std::deque& key { data[key].add(data[RadiusLayerPair(radius, layer_idx - layer_offset)]); } - //Placeable areas also have to be calculated when a collision has to be calculated if called outside of precalculate to prevent an infinite loop when they are invalidly requested... - if ((support_rests_on_this_model||precalculationFinished||!precalculated) && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) + // Placeable areas also have to be calculated when a collision has to be calculated if called outside of precalculate to prevent an infinite loop when they are + // invalidly requested... + if ((support_rests_on_this_model || precalculationFinished || ! precalculated) && radius == 0 && layer_idx < coord_t(1 + keys[i].second)) { data[key] = data[key].unionPolygons(); Polygons above = data[RadiusLayerPair(radius, layer_idx + 1)]; above = above.unionPolygons(max_anti_overhang_layer >= layer_idx + 1 ? anti_overhang_[layer_idx] : Polygons()); - // Empty polygons on condition: Just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. + // Empty polygons on condition: Just to be sure the area is correctly unioned as otherwise difference may behave unexpectedly. Polygons placeable = data[key].unionPolygons().difference(above); data_placeable[RadiusLayerPair(radius, layer_idx + 1)] = data_placeable[RadiusLayerPair(radius, layer_idx + 1)].unionPolygons(placeable); @@ -678,7 +689,9 @@ void TreeModelVolumes::calculateCollision(const std::deque& key for (const auto layer_idx : ranges::views::iota(min_layer_bottom, max_required_layer + 1)) { key.second = layer_idx; - for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers && layer_offset + layer_idx < std::min(coord_t(layer_outlines_[outline_idx].second.size()), coord_t(max_required_layer + 1)); layer_offset++) + for (coord_t layer_offset = 1; layer_offset <= z_distance_top_layers + && layer_offset + layer_idx < std::min(coord_t(layer_outlines_[outline_idx].second.size()), coord_t(max_required_layer + 1)); + layer_offset++) { // If just the collision (including the xy distance) of the layers above is accumulated, it leads to the following issue: // Example: assuming the z distance is 2 layer @@ -693,20 +706,20 @@ void TreeModelVolumes::calculateCollision(const std::deque& key // If just the collision above is accumulated the overhang will get overwritten by the xy_distance of the layer below the overhang... // // This only causes issues if the overhang area is thinner than xy_distance - // Just accumulating areas of the model above without the xy distance is also problematic, as then support may get closer to the model (on the diagonal downwards) than the user intended. - // Example (s = support): + // Just accumulating areas of the model above without the xy distance is also problematic, as then support may get closer to the model (on the diagonal + // downwards) than the user intended. Example (s = support): // +-----+ // +-----+ // +-----+ // s+-----+ - // Technically the calculation below is off by one layer, as the actual distance between plastic one layer down is 0 not layer height, as this layer is filled with said plastic. - // But otherwise a part of the overhang that is expected to be supported is overwritten by the remaining part of the xy distance of the layer below the to be supported area. + // Technically the calculation below is off by one layer, as the actual distance between plastic one layer down is 0 not layer height, as this layer is + // filled with said plastic. But otherwise a part of the overhang that is expected to be supported is overwritten by the remaining part of the xy distance + // of the layer below the to be supported area. const coord_t required_range_x = coord_t(xy_distance - ((layer_offset - (z_distance_top_layers == 1 ? 0.5 : 0)) * xy_distance / z_distance_top_layers)); // ^^^ The conditional -0.5 ensures that plastic can never touch on the diagonal downward when the z_distance_top_layers = 1. // It is assumed to be better to not support an overhang<90� than to risk fusing to it. data[key].add(layer_outlines_[outline_idx].second[layer_idx + layer_offset].offset(radius + required_range_x)); - } data[key] = data[key].unionPolygons(max_anti_overhang_layer >= layer_idx ? anti_overhang_[layer_idx].offset(radius) : Polygons()); } @@ -752,8 +765,7 @@ void TreeModelVolumes::calculateCollision(const std::deque& key placeable_areas_cache_.insert(data_placeable_outer.begin(), data_placeable_outer.end()); } } - } - ); + }); } void TreeModelVolumes::calculateCollisionHolefree(const std::deque& keys) @@ -764,8 +776,7 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::deque - ( + cura::parallel_for( 0, LayerIndex(max_layer + 1), [&](const LayerIndex layer_idx) @@ -786,8 +797,7 @@ void TreeModelVolumes::calculateCollisionHolefree(const std::deque critical_section(*critical_collision_cache_holefree_); collision_cache_holefree_.insert(data.begin(), data.end()); } - } - ); + }); } void TreeModelVolumes::calculateAccumulatedPlaceable0(const LayerIndex max_layer) @@ -801,14 +811,15 @@ void TreeModelVolumes::calculateAccumulatedPlaceable0(const LayerIndex max_layer { start_layer++; } - start_layer = std::max(start_layer.value + 1, 1); + start_layer = std::max(LayerIndex{ start_layer + 1 }, LayerIndex{ 1 }); } if (start_layer > max_layer) { spdlog::debug("Requested calculation for value already calculated ?"); return; } - Polygons accumulated_placeable_0 = start_layer == 1 ? machine_area_ : getAccumulatedPlaceable0(start_layer - 1).offset(FUDGE_LENGTH + (current_min_xy_dist + current_min_xy_dist_delta)); + Polygons accumulated_placeable_0 + = start_layer == 1 ? machine_area_ : getAccumulatedPlaceable0(start_layer - 1).offset(FUDGE_LENGTH + (current_min_xy_dist + current_min_xy_dist_delta)); // ^^^ The calculation here is done on the areas that are increased by xy_distance, but the result is saved without xy_distance, // so here it "restores" the previous state to continue calculating from about where it ended. // It would be better to ensure placeable areas of radius 0 do not include the xy distance, and removing the code compensating for it here and in calculatePlaceables. @@ -821,75 +832,73 @@ void TreeModelVolumes::calculateAccumulatedPlaceable0(const LayerIndex max_layer accumulated_placeable_0 = simplifier.polygon(accumulated_placeable_0); data[layer] = std::pair(layer, accumulated_placeable_0); } - cura::parallel_for - ( - std::max(start_layer-1,LayerIndex(1)), - data.size(), - [&](const coord_t layer_idx) - { - data[layer_idx].second = data[layer_idx].second.offset(-(current_min_xy_dist+current_min_xy_dist_delta)); - }); + cura::parallel_for( + std::max(LayerIndex{ start_layer - 1 }, LayerIndex{ 1 }), + data.size(), + [&](const coord_t layer_idx) + { + data[layer_idx].second = data[layer_idx].second.offset(-(current_min_xy_dist + current_min_xy_dist_delta)); + }); { - std::lock_guard critical_section(* critical_accumulated_placeables_cache_radius_0_); + std::lock_guard critical_section(*critical_accumulated_placeables_cache_radius_0_); accumulated_placeables_cache_radius_0_.insert(data.begin(), data.end()); } } - void TreeModelVolumes::calculateCollisionAvoidance(const std::deque& keys) { - cura::parallel_for - ( - 0, - keys.size(), - [&, keys](const size_t key_idx) + cura::parallel_for( + 0, + keys.size(), + [&, keys](const size_t key_idx) + { + const coord_t radius = keys[key_idx].first; + const LayerIndex max_required_layer = keys[key_idx].second; + const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); + LayerIndex start_layer = 0; { + std::lock_guard critical_section(*critical_avoidance_cache_collision_); + start_layer = 1 + std::max(getMaxCalculatedLayer(radius, avoidance_cache_collision_), max_layer_idx_without_blocker); + } - const coord_t radius = keys[key_idx].first; - const LayerIndex max_required_layer = keys[key_idx].second; - const coord_t max_step_move = std::max(1.9 * radius, current_min_xy_dist * 1.9); - LayerIndex start_layer = 0; - { - std::lock_guard critical_section(*critical_avoidance_cache_collision_); - start_layer = 1 + std::max(getMaxCalculatedLayer(radius, avoidance_cache_collision_), max_layer_idx_without_blocker); - } + if (start_layer > max_required_layer) + { + return; + } - if(start_layer>max_required_layer) - { - return; - } + std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); + RadiusLayerPair key(radius, 0); + + Polygons latest_avoidance = getAvoidance(radius, start_layer - 1, AvoidanceType::COLLISION, true, true); + for (const LayerIndex layer : ranges::views::iota(static_cast(start_layer), max_required_layer + 1UL)) + { + key.second = layer; + Polygons col = getCollision(radius, layer, true); + latest_avoidance = safeOffset(latest_avoidance, -max_move_, ClipperLib::jtRound, -max_step_move, col); - std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - RadiusLayerPair key(radius, 0); + Polygons placeable0RadiusCompensated = getAccumulatedPlaceable0(layer).offset(-std::max(radius, increase_until_radius), ClipperLib::jtRound); + latest_avoidance = latest_avoidance.difference(placeable0RadiusCompensated).unionPolygons(getCollision(radius, layer, true)); - Polygons latest_avoidance = getAvoidance(radius,start_layer-1,AvoidanceType::COLLISION,true,true); - for (const LayerIndex layer : ranges::views::iota(static_cast(start_layer), max_required_layer + 1UL)) - { - key.second = layer; - Polygons col = getCollision(radius, layer, true); - latest_avoidance = safeOffset(latest_avoidance, -max_move_, ClipperLib::jtRound, -max_step_move, col); - - Polygons placeable0RadiusCompensated = getAccumulatedPlaceable0(layer).offset(-std::max(radius,increase_until_radius), ClipperLib::jtRound); - latest_avoidance = latest_avoidance.difference(placeable0RadiusCompensated).unionPolygons(getCollision(radius,layer,true)); - - Polygons next_latest_avoidance = simplifier.polygon(latest_avoidance); - latest_avoidance = next_latest_avoidance.unionPolygons(latest_avoidance); - // ^^^ Ensure the simplification only causes the avoidance to become larger. - // If the deviation of the simplification causes the avoidance to become smaller than it should be it can cause issues, if it is larger the worst case is that the xy distance is effectively increased by deviation. - // If there would be an option to ensure the resulting polygon only gets larger by simplifying, it should improve performance further. - data[layer] = std::pair(key, latest_avoidance); - } + Polygons next_latest_avoidance = simplifier.polygon(latest_avoidance); + latest_avoidance = next_latest_avoidance.unionPolygons(latest_avoidance); + // ^^^ Ensure the simplification only causes the avoidance to become larger. + // If the deviation of the simplification causes the avoidance to become smaller than it should be it can cause issues, if it is larger the worst case is that the + // xy distance is effectively increased by deviation. If there would be an option to ensure the resulting polygon only gets larger by simplifying, it should improve + // performance further. + data[layer] = std::pair(key, latest_avoidance); + } - { - std::lock_guard critical_section(* critical_avoidance_cache_collision_); - avoidance_cache_collision_.insert(data.begin(), data.end()); - } - }); + { + std::lock_guard critical_section(*critical_avoidance_cache_collision_); + avoidance_cache_collision_.insert(data.begin(), data.end()); + } + }); } -// Ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls defined by the collision when offsetting to fast. +// Ensures offsets are only done in sizes with a max step size per offset while adding the collision offset after each step, this ensures that areas cannot glitch through walls +// defined by the collision when offsetting to fast. Polygons TreeModelVolumes::safeOffset(const Polygons& me, coord_t distance, ClipperLib::JoinType jt, coord_t max_safe_step_distance, const Polygons& collision) const { const size_t steps = std::abs(distance / std::max(min_offset_per_step_, std::abs(max_safe_step_distance))); @@ -909,9 +918,9 @@ void TreeModelVolumes::calculateAvoidance(const std::deque& key { // For every RadiusLayer pair there are 3 avoidances that have to be calculate, calculated in the same paralell_for loop for better parallelization. const std::vector all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; - // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' one right now. - cura::parallel_for - ( + // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' + // one right now. + cura::parallel_for( 0, keys.size() * 3, [&, keys, all_types](const size_t iter_idx) @@ -949,7 +958,8 @@ void TreeModelVolumes::calculateAvoidance(const std::deque& key start_layer = std::max(start_layer, LayerIndex(1)); // Ensure StartLayer is at least 1 as if no avoidance was calculated getMaxCalculatedLayer returns -1 std::vector> data(max_required_layer + 1, std::pair(RadiusLayerPair(radius, -1), Polygons())); - latest_avoidance = getAvoidance(radius, start_layer - 1, type, false, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. + latest_avoidance + = getAvoidance(radius, start_layer - 1, type, false, true); // minDist as the delta was already added, also avoidance for layer 0 will return the collision. // ### main loop doing the calculation for (const LayerIndex layer : ranges::views::iota(static_cast(start_layer), max_required_layer + 1UL)) @@ -969,8 +979,9 @@ void TreeModelVolumes::calculateAvoidance(const std::deque& key Polygons next_latest_avoidance = simplifier.polygon(latest_avoidance); latest_avoidance = next_latest_avoidance.unionPolygons(latest_avoidance); // ^^^ Ensure the simplification only causes the avoidance to become larger. - // If the deviation of the simplification causes the avoidance to become smaller than it should be it can cause issues, if it is larger the worst case is that the xy distance is effectively increased by deviation. - // If there would be an option to ensure the resulting polygon only gets larger by simplifying, it should improve performance further. + // If the deviation of the simplification causes the avoidance to become smaller than it should be it can cause issues, if it is larger the worst case is that the + // xy distance is effectively increased by deviation. If there would be an option to ensure the resulting polygon only gets larger by simplifying, it should improve + // performance further. data[layer] = std::pair(key, latest_avoidance); } @@ -988,15 +999,14 @@ void TreeModelVolumes::calculateAvoidance(const std::deque& key std::lock_guard critical_section(*(slow ? critical_avoidance_cache_slow_ : holefree ? critical_avoidance_cache_holefree_ : critical_avoidance_cache_)); (slow ? avoidance_cache_slow_ : holefree ? avoidance_cache_hole_ : avoidance_cache_).insert(data.begin(), data.end()); } - } - ); + }); } void TreeModelVolumes::calculatePlaceables(const std::deque& keys) { - // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' one right now. - cura::parallel_for - ( + // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' + // one right now. + cura::parallel_for( 0, keys.size(), [&, keys](const size_t key_idx) @@ -1046,8 +1056,7 @@ void TreeModelVolumes::calculatePlaceables(const std::deque& ke std::lock_guard critical_section(*critical_placeable_areas_cache_); placeable_areas_cache_.insert(data.begin(), data.end()); } - } - ); + }); } @@ -1055,9 +1064,9 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque all_types = { AvoidanceType::SLOW, AvoidanceType::FAST_SAFE, AvoidanceType::FAST }; - // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' one right now. - cura::parallel_for - ( + // TODO: This should be a parallel for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' + // one right now. + cura::parallel_for( 0, keys.size() * 3, [&, keys, all_types](const size_t iter_idx) @@ -1085,7 +1094,10 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque critical_section(*(slow ? critical_avoidance_cache_to_model_slow_ : holefree ? critical_avoidance_cache_holefree_to_model_ : critical_avoidance_cache_to_model_)); + std::lock_guard critical_section( + *(slow ? critical_avoidance_cache_to_model_slow_ + : holefree ? critical_avoidance_cache_holefree_to_model_ + : critical_avoidance_cache_to_model_)); start_layer = 1 + getMaxCalculatedLayer(radius, slow ? avoidance_cache_to_model_slow_ : holefree ? avoidance_cache_hole_to_model_ : avoidance_cache_to_model_); } if (start_layer > max_required_layer) @@ -1094,7 +1106,8 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque(start_layer), max_required_layer + 1)) @@ -1115,8 +1128,9 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque(key, latest_avoidance); } @@ -1131,18 +1145,20 @@ void TreeModelVolumes::calculateAvoidanceToModel(const std::deque critical_section(*(slow ? critical_avoidance_cache_to_model_slow_ : holefree ? critical_avoidance_cache_holefree_to_model_ : critical_avoidance_cache_to_model_)); + std::lock_guard critical_section( + *(slow ? critical_avoidance_cache_to_model_slow_ + : holefree ? critical_avoidance_cache_holefree_to_model_ + : critical_avoidance_cache_to_model_)); (slow ? avoidance_cache_to_model_slow_ : holefree ? avoidance_cache_hole_to_model_ : avoidance_cache_to_model_).insert(data.begin(), data.end()); } - } - ); + }); } void TreeModelVolumes::calculateWallRestrictions(const std::deque& keys) { - // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no excuse for moving through a wall. - // As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty space,d = blocked area because of z distance) - // Assume maximum movement distance is two characters and maximum safe movement distance of one character + // Wall restrictions are mainly important when they represent actual walls that are printed, and not "just" the configured z_distance, because technically valid placement is no + // excuse for moving through a wall. As they exist to prevent accidentially moving though a wall at high speed between layers like thie (x = wall,i = influence area,o= empty + // space,d = blocked area because of z distance) Assume maximum movement distance is two characters and maximum safe movement distance of one character /* Potential issue addressed by the wall restrictions: Influence area may lag through a wall * layer z+1:iiiiiiiiiiioooo @@ -1150,7 +1166,8 @@ void TreeModelVolumes::calculateWallRestrictions(const std::deque - ( + // FIXME: This should be a parallel-for nowait (non-blocking), but as the parallel-for situation (as in, proper compiler support) continues to change, we're using the 'normal' + // one right now. + cura::parallel_for( 0, keys.size(), [&, keys](const size_t key_idx) @@ -1201,7 +1218,8 @@ void TreeModelVolumes::calculateWallRestrictions(const std::deque 0) { @@ -1219,8 +1237,7 @@ void TreeModelVolumes::calculateWallRestrictions(const std::deque critical_section(*critical_wall_restrictions_cache_min_); wall_restrictions_cache_min_.insert(data_min.begin(), data_min.end()); } - } - ); + }); } coord_t TreeModelVolumes::ceilRadius(coord_t radius) const @@ -1240,7 +1257,7 @@ coord_t TreeModelVolumes::ceilRadius(coord_t radius) const for (const auto step : ranges::views::iota(0UL, SUPPORT_TREE_PRE_EXPONENTIAL_STEPS)) { result += stepsize; - if (result >= radius && !ignorable_radii_.count(result)) + if (result >= radius && ! ignorable_radii_.count(result)) { return result; } @@ -1253,7 +1270,7 @@ coord_t TreeModelVolumes::ceilRadius(coord_t radius) const return exponential_result; } -template +template const std::optional> TreeModelVolumes::getArea(const std::unordered_map& cache, const KEY key) const { const auto it = cache.find(key); diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 5c7585cfbb..bf876ec582 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2,6 +2,21 @@ // CuraEngine is released under the terms of the AGPLv3 or higher #include "TreeSupport.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + #include "Application.h" //To get settings. #include "TreeSupportTipGenerator.h" #include "TreeSupportUtils.h" @@ -17,20 +32,6 @@ #include "utils/polygonUtils.h" //For moveInside. #include "utils/section_type.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - namespace cura { @@ -38,18 +39,20 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) { size_t largest_printed_mesh_idx = 0; - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { - TreeSupportSettings::some_model_contains_thick_roof |= - mesh.settings.get("support_roof_height") >= 2 * mesh.settings.get("layer_height"); - TreeSupportSettings::has_to_rely_on_min_xy_dist_only |= - mesh.settings.get("support_top_distance") == 0 || mesh.settings.get("support_bottom_distance") == 0 || mesh.settings.get("min_feature_size") < (FUDGE_LENGTH * 2); + const auto& mesh = *mesh_ptr; + TreeSupportSettings::some_model_contains_thick_roof |= mesh.settings.get("support_roof_height") >= 2 * mesh.settings.get("layer_height"); + TreeSupportSettings::has_to_rely_on_min_xy_dist_only |= mesh.settings.get("support_top_distance") == 0 + || mesh.settings.get("support_bottom_distance") == 0 + || mesh.settings.get("min_feature_size") < (FUDGE_LENGTH * 2); } // Group all meshes that can be processed together. NOTE this is different from mesh-groups! // Only one setting object is needed per group, as different settings in the same group may only occur in the tip, which uses the original settings objects from the meshes. - for (auto [mesh_idx, mesh] : storage.meshes | ranges::views::enumerate) + for (auto [mesh_idx, mesh_ptr] : storage.meshes | ranges::views::enumerate) { + SliceMeshStorage& mesh = *mesh_ptr; const bool non_supportable_mesh = mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh") || mesh.settings.get("support_mesh"); if (mesh.settings.get("support_structure") != ESupportStructure::TREE || ! mesh.settings.get("support_enable") || non_supportable_mesh) { @@ -66,8 +69,10 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) { added = true; grouped_mesh.second.emplace_back(mesh_idx); - // Handle some settings that are only used for performance reasons. This ensures that a horrible set setting intended to improve performance can not reduce it drastically. - grouped_mesh.first.performance_interface_skip_layers = std::min(grouped_mesh.first.performance_interface_skip_layers, next_settings.performance_interface_skip_layers); + // Handle some settings that are only used for performance reasons. This ensures that a horrible set setting intended to improve performance can not reduce it + // drastically. + grouped_mesh.first.performance_interface_skip_layers + = std::min(grouped_mesh.first.performance_interface_skip_layers, next_settings.performance_interface_skip_layers); } } if (! added) @@ -76,14 +81,14 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) } // no need to do this per mesh group as adaptive layers and raft setting are not setable per mesh. - if (storage.meshes[largest_printed_mesh_idx].layers.back().printZ < mesh.layers.back().printZ) + if (storage.meshes[largest_printed_mesh_idx]->layers.back().printZ < mesh.layers.back().printZ) { largest_printed_mesh_idx = mesh_idx; } } - std::vector known_z(storage.meshes[largest_printed_mesh_idx].layers.size()); + std::vector known_z(storage.meshes[largest_printed_mesh_idx]->layers.size()); - for (auto [z, layer] : ranges::views::enumerate(storage.meshes[largest_printed_mesh_idx].layers)) + for (auto [z, layer] : ranges::views::enumerate(storage.meshes[largest_printed_mesh_idx]->layers)) { known_z[z] = layer.printZ; } @@ -93,8 +98,7 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) mesh.first.setActualZ(known_z); } - placed_support_lines_support_areas = std::vector(storage.support.supportLayers.size(),Polygons()); - + placed_support_lines_support_areas = std::vector(storage.support.supportLayers.size(), Polygons()); } void TreeSupport::generateSupportAreas(SliceDataStorage& storage) @@ -110,12 +114,14 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) } // Process every mesh group. These groups can not be processed parallel, as the processing in each group is parallelized, and nested parallelization is disables and slow. - for (auto [counter, processing] : grouped_meshes | ranges::views::enumerate ) + for (auto [counter, processing] : grouped_meshes | ranges::views::enumerate) { // process each combination of meshes - std::vector> move_bounds(storage.support.supportLayers.size()); // Value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas. + std::vector> move_bounds( + storage.support.supportLayers + .size()); // Value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in drawAreas. - additional_required_support_area=std::vector(storage.support.supportLayers.size(),Polygons()); + additional_required_support_area = std::vector(storage.support.supportLayers.size(), Polygons()); spdlog::info("Processing support tree mesh group {} of {} containing {} meshes.", counter + 1, grouped_meshes.size(), grouped_meshes[counter].second.size()); @@ -123,8 +129,7 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) auto t_start = std::chrono::high_resolution_clock::now(); // get all already existing support areas and exclude them - cura::parallel_for - ( + cura::parallel_for( LayerIndex(0), LayerIndex(storage.support.supportLayers.size()), [&](const LayerIndex layer_idx) @@ -138,12 +143,20 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) } exclude[layer_idx] = exlude_at_layer.unionPolygons(); scripta::log("tree_support_exclude", exclude[layer_idx], SectionType::SUPPORT, layer_idx); - } - ); - config = processing.first; // This struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of the existence of multiple meshes being processed. + }); + config = processing.first; // This struct is used to easy retrieve setting. No other function except those in TreeModelVolumes and generateInitialAreas have knowledge of + // the existence of multiple meshes being processed. progress_multiplier = 1.0 / double(grouped_meshes.size()); progress_offset = counter == 0 ? 0 : TREE_PROGRESS_TOTAL * (double(counter) * progress_multiplier); - volumes_ = TreeModelVolumes(storage, config.maximum_move_distance, config.maximum_move_distance_slow, config.support_line_width / 2, processing.second.front(), progress_multiplier, progress_offset, exclude); + volumes_ = TreeModelVolumes( + storage, + config.maximum_move_distance, + config.maximum_move_distance_slow, + config.support_line_width / 2, + processing.second.front(), + progress_multiplier, + progress_offset, + exclude); // ### Precalculate avoidances, collision etc. precalculate(storage, processing.second); @@ -152,7 +165,7 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) // ### Place tips of the support tree for (size_t mesh_idx : processing.second) { - generateInitialAreas(storage.meshes[mesh_idx], move_bounds, storage); + generateInitialAreas(*storage.meshes[mesh_idx], move_bounds, storage); } const auto t_gen = std::chrono::high_resolution_clock::now(); @@ -174,19 +187,18 @@ void TreeSupport::generateSupportAreas(SliceDataStorage& storage) const auto dur_place = 0.001 * std::chrono::duration_cast(t_place - t_path).count(); const auto dur_draw = 0.001 * std::chrono::duration_cast(t_draw - t_place).count(); const auto dur_total = 0.001 * std::chrono::duration_cast(t_draw - t_start).count(); - spdlog::info - ( + spdlog::info( "Total time used creating Tree support for the currently grouped meshes: {} ms. Different subtasks:\n" - "Calculating Avoidance: {} ms Creating inital influence areas: {} ms Influence area creation: {} ms Placement of Points in InfluenceAreas: {} ms Drawing result as support {} ms", + "Calculating Avoidance: {} ms Creating inital influence areas: {} ms Influence area creation: {} ms Placement of Points in InfluenceAreas: {} ms Drawing result as " + "support {} ms", dur_total, dur_pre_gen, dur_gen, dur_path, dur_place, - dur_draw - ); + dur_draw); + - for (auto& layer : move_bounds) { for (auto elem : layer) @@ -206,11 +218,11 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector("layer_height"); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); const size_t z_distance_top_layers = round_up_divide(z_distance_top, - layer_height) + 1; // Support must always be 1 layer below overhang. + layer_height) + 1; // Support must always be 1 layer below overhang. if (mesh.overhang_areas.size() <= z_distance_top_layers) { continue; @@ -220,7 +232,7 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector max_layer) // iterates over multiple meshes { @@ -242,8 +254,7 @@ void TreeSupport::generateInitialAreas(const SliceMeshStorage& mesh, std::vector tip_gen.generateTips(storage, mesh, move_bounds, additional_required_support_area, placed_support_lines_support_areas); } -void TreeSupport::mergeHelper -( +void TreeSupport::mergeHelper( std::map& reduced_aabb, std::map& input_aabb, const PropertyAreasUnordered& to_bp_areas, @@ -252,12 +263,14 @@ void TreeSupport::mergeHelper PropertyAreasUnordered& insert_bp_areas, PropertyAreasUnordered& insert_model_areas, PropertyAreasUnordered& insert_influence, - std::vector& erase, const LayerIndex layer_idx -) + std::vector& erase, + const LayerIndex layer_idx) { const bool first_merge_iteration = reduced_aabb.empty(); // If this is the first iteration, all elements in input have to be merged with each other - const std::function getRadiusFunction = - [&](const size_t distance_to_top, const double buildplate_radius_increases) { return config.getRadius(distance_to_top, buildplate_radius_increases); }; + const std::function getRadiusFunction = [&](const size_t distance_to_top, const double buildplate_radius_increases) + { + return config.getRadius(distance_to_top, buildplate_radius_increases); + }; for (auto& influence : input_aabb) { bool merged = false; @@ -269,7 +282,7 @@ void TreeSupport::mergeHelper AABB aabb = reduced_check.second; if (aabb.hit(influence_aabb)) { - if (!first_merge_iteration && input_aabb.count(reduced_check.first)) + if (! first_merge_iteration && input_aabb.count(reduced_check.first)) { break; // Do not try to merge elements that already should have been merged. Done for potential performance improvement. } @@ -278,14 +291,15 @@ void TreeSupport::mergeHelper // ^^^ We do not want to merge a gracious with a non gracious area as bad placement could negatively impact the dependability of the whole subtree. const bool merging_to_bp = reduced_check.first.to_buildplate && influence.first.to_buildplate; const bool merging_min_and_regular_xy = reduced_check.first.use_min_xy_dist != influence.first.use_min_xy_dist; - // ^^^ Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased by the delta to the larger it is engulfed by it already. + // ^^^ Could cause some issues with the increase of one area, as it is assumed that if the smaller is increased by the delta to the larger it is engulfed by it + // already. // But because a different collision may be removed from the in drawArea generated circles, this assumption could be wrong. - const bool merging_different_range_limits = - reduced_check.first.influence_area_limit_active && influence.first.influence_area_limit_active && influence.first.influence_area_limit_range != reduced_check.first.influence_area_limit_range; + const bool merging_different_range_limits = reduced_check.first.influence_area_limit_active && influence.first.influence_area_limit_active + && influence.first.influence_area_limit_range != reduced_check.first.influence_area_limit_range; coord_t increased_to_model_radius = 0; size_t larger_to_model_dtt = 0; - if (!merging_to_bp) + if (! merging_to_bp) { const coord_t infl_radius = config.getRadius(influence.first); // Get the real radius increase as the user does not care for the collision model. const coord_t redu_radius = config.getRadius(reduced_check.first); @@ -311,14 +325,9 @@ void TreeSupport::mergeHelper // If a merge could place a stable branch on unstable ground, would be increasing the radius further than allowed to when merging to model and to_bp trees or // would merge to model before it is known they will even been drawn the merge is skipped - if - ( - merging_min_and_regular_xy || - merging_gracious_and_non_gracious || - increased_to_model_radius > config.max_to_model_radius_increase || - (!merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && !reduced_check.first.supports_roof && !influence.first.supports_roof) || - merging_different_range_limits - ) + if (merging_min_and_regular_xy || merging_gracious_and_non_gracious || increased_to_model_radius > config.max_to_model_radius_increase + || (! merging_to_bp && larger_to_model_dtt < config.min_dtt_to_model && ! reduced_check.first.supports_roof && ! influence.first.supports_roof) + || merging_different_range_limits) { continue; } @@ -327,41 +336,29 @@ void TreeSupport::mergeHelper Polygons relevant_redu; if (merging_to_bp) { - relevant_infl = to_bp_areas.count(influence.first) ? to_bp_areas.at(influence.first) : Polygons(); // influence.first is a new element => not required to check if it was changed - relevant_redu = - insert_bp_areas.count - ( - reduced_check.first) ? - insert_bp_areas[reduced_check.first] : - (to_bp_areas.count(reduced_check.first) ? to_bp_areas.at(reduced_check.first) : Polygons() - ); + relevant_infl = to_bp_areas.count(influence.first) ? to_bp_areas.at(influence.first) + : Polygons(); // influence.first is a new element => not required to check if it was changed + relevant_redu = insert_bp_areas.count(reduced_check.first) ? insert_bp_areas[reduced_check.first] + : (to_bp_areas.count(reduced_check.first) ? to_bp_areas.at(reduced_check.first) : Polygons()); } else { relevant_infl = to_model_areas.count(influence.first) ? to_model_areas.at(influence.first) : Polygons(); - relevant_redu = - insert_model_areas.count - ( - reduced_check.first) ? - insert_model_areas[reduced_check.first] : - (to_model_areas.count(reduced_check.first) ? to_model_areas.at(reduced_check.first) : Polygons() - ); + relevant_redu = insert_model_areas.count(reduced_check.first) + ? insert_model_areas[reduced_check.first] + : (to_model_areas.count(reduced_check.first) ? to_model_areas.at(reduced_check.first) : Polygons()); } const bool red_bigger = config.getCollisionRadius(reduced_check.first) > config.getCollisionRadius(influence.first); - std::pair smaller_rad = - red_bigger ? - std::pair(influence.first, relevant_infl) : - std::pair(reduced_check.first, relevant_redu); - std::pair bigger_rad = - red_bigger ? - std::pair(reduced_check.first, relevant_redu) : - std::pair(influence.first, relevant_infl); + std::pair smaller_rad = red_bigger ? std::pair(influence.first, relevant_infl) + : std::pair(reduced_check.first, relevant_redu); + std::pair bigger_rad = red_bigger ? std::pair(reduced_check.first, relevant_redu) + : std::pair(influence.first, relevant_infl); const coord_t real_radius_delta = std::abs(config.getRadius(bigger_rad.first) - config.getRadius(smaller_rad.first)); const coord_t smaller_collision_radius = config.getCollisionRadius(smaller_rad.first); // the area of the bigger radius is used to ensure correct placement regarding the relevant avoidance, so if that would change an invalid area may be created - if (!bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) + if (! bigger_rad.first.can_use_safe_radius && smaller_rad.first.can_use_safe_radius) { continue; } @@ -376,23 +373,23 @@ void TreeSupport::mergeHelper // a branch (of the larger collision radius) placed in this intersection, has already engulfed the branch of the smaller collision radius. // Because of this a merge may happen even if the influence areas (that represent possible center points of branches) do not intersect yet. // Remember that collision radius <= real radius as otherwise this assumption would be false. - const Polygons small_rad_increased_by_big_minus_small = - TreeSupportUtils::safeOffsetInc - ( - smaller_rad.second, - real_radius_delta, volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), - 2 * (config.xy_distance + smaller_collision_radius - EPSILON), // Epsilon avoids possible rounding errors - 0, - 0, - config.support_line_distance / 2, - &config.simplifier - ); + const Polygons small_rad_increased_by_big_minus_small = TreeSupportUtils::safeOffsetInc( + smaller_rad.second, + real_radius_delta, + volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), + 2 * (config.xy_distance + smaller_collision_radius - EPSILON), // Epsilon avoids possible rounding errors + 0, + 0, + config.support_line_distance / 2, + &config.simplifier); Polygons intersect = small_rad_increased_by_big_minus_small.intersection(bigger_rad.second); - if (intersect.area() > 1) // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) + if (intersect.area() + > 1) // dont use empty as a line is not empty, but for this use-case it very well may be (and would be one layer down as union does not keep lines) { - // Check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change it. - if (intersect.offset(-FUDGE_LENGTH/2).area() <= 1) + // Check if the overlap is large enough (Small ares tend to attract rounding errors in clipper). While 25 was guessed as enough, i did not have reason to change + // it. + if (intersect.offset(-FUDGE_LENGTH / 2).area() <= 1) { continue; } @@ -413,8 +410,7 @@ void TreeSupport::mergeHelper increased_to_model_radius = std::max(reduced_check.first.increased_to_model_radius, influence.first.increased_to_model_radius); } - const TreeSupportElement key - ( + const TreeSupportElement key( reduced_check.first, influence.first, layer_idx - 1, @@ -423,31 +419,30 @@ void TreeSupport::mergeHelper getRadiusFunction, config.diameter_scale_bp_radius, config.branch_radius, - config.diameter_angle_scale_factor - ); + config.diameter_angle_scale_factor); - const auto getIntersectInfluence = - [&] (const PropertyAreasUnordered& insert_infl, const PropertyAreas& infl_areas) - { - const Polygons infl_small = insert_infl.count(smaller_rad.first) ? insert_infl.at(smaller_rad.first) : (infl_areas.count(smaller_rad.first) ? infl_areas.at(smaller_rad.first) : Polygons()); - const Polygons infl_big = insert_infl.count(bigger_rad.first) ? insert_infl.at(bigger_rad.first) : (infl_areas.count(bigger_rad.first) ? infl_areas.at(bigger_rad.first) : Polygons()); - const Polygons small_rad_increased_by_big_minus_small_infl = - TreeSupportUtils::safeOffsetInc - ( - infl_small, - real_radius_delta, - volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), - 2 * (config.xy_distance + smaller_collision_radius - EPSILON), - 0, - 0, - config.support_line_distance / 2, - &config.simplifier - ); - return small_rad_increased_by_big_minus_small_infl.intersection(infl_big); // If the one with the bigger radius with the lower radius removed overlaps we can merge. - }; + const auto getIntersectInfluence = [&](const PropertyAreasUnordered& insert_infl, const PropertyAreas& infl_areas) + { + const Polygons infl_small = insert_infl.count(smaller_rad.first) ? insert_infl.at(smaller_rad.first) + : (infl_areas.count(smaller_rad.first) ? infl_areas.at(smaller_rad.first) : Polygons()); + const Polygons infl_big = insert_infl.count(bigger_rad.first) ? insert_infl.at(bigger_rad.first) + : (infl_areas.count(bigger_rad.first) ? infl_areas.at(bigger_rad.first) : Polygons()); + const Polygons small_rad_increased_by_big_minus_small_infl = TreeSupportUtils::safeOffsetInc( + infl_small, + real_radius_delta, + volumes_.getCollision(smaller_collision_radius, layer_idx - 1, use_min_radius), + 2 * (config.xy_distance + smaller_collision_radius - EPSILON), + 0, + 0, + config.support_line_distance / 2, + &config.simplifier); + return small_rad_increased_by_big_minus_small_infl.intersection( + infl_big); // If the one with the bigger radius with the lower radius removed overlaps we can merge. + }; Polygons intersect_influence; - intersect_influence = TreeSupportUtils::safeUnion(intersect, getIntersectInfluence(insert_influence, influence_areas)); // Rounding errors again. Do not ask me where or why. + intersect_influence + = TreeSupportUtils::safeUnion(intersect, getIntersectInfluence(insert_influence, influence_areas)); // Rounding errors again. Do not ask me where or why. Polygons intersect_to_model; if (merging_to_bp && config.support_rests_on_model) @@ -473,8 +468,10 @@ void TreeSupport::mergeHelper erase.emplace_back(reduced_check.first); erase.emplace_back(influence.first); - const Polygons merge = intersect.unionPolygons(intersect_to_model).offset(config.getRadius(key), ClipperLib::jtRound).difference(volumes_.getCollision(0, layer_idx - 1)); - // ^^^ Regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a negative area.). + const Polygons merge + = intersect.unionPolygons(intersect_to_model).offset(config.getRadius(key), ClipperLib::jtRound).difference(volumes_.getCollision(0, layer_idx - 1)); + // ^^^ Regular union should be preferable here as Polygons tend to only become smaller through rounding errors (smaller!=has smaller area as holes have a + // negative area.). // And if this area disappears because of rounding errors, the only downside is that it can not merge again on this layer. reduced_aabb.erase(reduced_check.first); // This invalidates reduced_check. @@ -493,13 +490,7 @@ void TreeSupport::mergeHelper } } -void TreeSupport::mergeInfluenceAreas -( - PropertyAreasUnordered& to_bp_areas, - PropertyAreas& to_model_areas, - PropertyAreas& influence_areas, - LayerIndex layer_idx -) +void TreeSupport::mergeInfluenceAreas(PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, LayerIndex layer_idx) { /* * Idea behind this is that the calculation of merges can be accelerated a bit using divide and conquer: @@ -519,13 +510,15 @@ void TreeSupport::mergeInfluenceAreas // max_bucket_count is input_size/min_elements_per_bucket round down to the next 2^n. // The rounding to 2^n is to ensure improved performance, as every iteration two buckets will be merged, halving the amount of buckets. - // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be optimal performance-wise. + // If halving would cause an uneven count, e.g. 3 Then bucket 0 and 1 would have to be merged, and in the next iteration the last remaining buckets. This is assumed to not be + // optimal performance-wise. const size_t max_bucket_count = std::pow(2, std::floor(std::log(round_up_divide(input_size, min_elements_per_bucket)))); int bucket_count = std::min(max_bucket_count, num_threads); // do not use more buckets than available threads. // To achieve that every element in a bucket is already correctly merged with other elements in this bucket // an extra empty bucket is created for each bucket, and the elements are merged into the empty one. - // Each thread will then process two buckets by merging all elements in the second bucket into the first one as mergeHelper will disable not trying to merge elements from the same bucket in this case. + // Each thread will then process two buckets by merging all elements in the second bucket into the first one as mergeHelper will disable not trying to merge elements from the + // same bucket in this case. std::vector buckets_area(2 * bucket_count); std::vector> buckets_aabb(2 * bucket_count); @@ -549,8 +542,7 @@ void TreeSupport::mergeInfluenceAreas } // Precalculate the AABBs from the influence areas. - cura::parallel_for - ( + cura::parallel_for( 0, buckets_area.size() / 2, [&](size_t idx) // +=2 as in the beginning only uneven buckets will be filled @@ -562,8 +554,7 @@ void TreeSupport::mergeInfluenceAreas outer_support_wall_aabb.expand(config.getRadius(input_pair.first)); buckets_aabb[idx].emplace(input_pair.first, outer_support_wall_aabb); } - } - ); + }); while (buckets_area.size() > 1) { @@ -573,16 +564,14 @@ void TreeSupport::mergeInfluenceAreas std::vector insert_influence(buckets_area.size() / 2); std::vector> erase(buckets_area.size() / 2); - cura::parallel_for - ( + cura::parallel_for( 0, (coord_t)buckets_area.size() / 2, [&](size_t bucket_pair_idx) { bucket_pair_idx *= 2; // this is eqivalent to a parallel for(size_t idx=0;idx x) mutable { return x.empty(); }); + const auto position_aabb = std::remove_if( + buckets_aabb.begin(), + buckets_aabb.end(), + [&](const std::map x) mutable + { + return x.empty(); + }); buckets_aabb.erase(position_aabb, buckets_aabb.end()); } } -std::optional TreeSupport::increaseSingleArea -( +std::optional TreeSupport::increaseSingleArea( AreaIncreaseSettings settings, LayerIndex layer_idx, TreeSupportElement* parent, const Polygons& relevant_offset, - Polygons& to_bp_data, Polygons& to_model_data, + Polygons& to_bp_data, + Polygons& to_model_data, Polygons& increased, const coord_t overspeed, - const bool mergelayer -) + const bool mergelayer) { TreeSupportElement current_elem(parent); // Also increases DTT by one. Polygons check_layer_data; @@ -657,22 +655,19 @@ std::optional TreeSupport::increaseSingleArea increased = relevant_offset; if (overspeed > 0) { - const coord_t safe_movement_distance = - (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + const coord_t safe_movement_distance = (current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); // The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later. // The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later. - increased = - TreeSupportUtils::safeOffsetInc - ( - increased, - overspeed, - volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), - safe_movement_distance, - safe_movement_distance + radius, - 1, - config.support_line_distance / 2, - nullptr - ); + increased = TreeSupportUtils::safeOffsetInc( + increased, + overspeed, + volumes_.getWallRestriction(config.getCollisionRadius(*parent), layer_idx, parent->use_min_xy_dist), + safe_movement_distance, + safe_movement_distance + radius, + 1, + config.support_line_distance / 2, + nullptr); } if (settings.no_error && settings.move) { @@ -709,7 +704,8 @@ std::optional TreeSupport::increaseSingleArea } else { - to_model_data = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, AvoidanceType::COLLISION, true, settings.use_min_distance))); + to_model_data + = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, AvoidanceType::COLLISION, true, settings.use_min_distance))); } } } @@ -718,29 +714,35 @@ std::optional TreeSupport::increaseSingleArea if (settings.increase_radius && check_layer_data.area() > 1) { - std::function validWithRadius = - [&](coord_t next_radius) + std::function validWithRadius = [&](coord_t next_radius) + { + if (volumes_.ceilRadius(next_radius, settings.use_min_distance) <= volumes_.ceilRadius(radius, settings.use_min_distance)) { - if (volumes_.ceilRadius(next_radius, settings.use_min_distance) <= volumes_.ceilRadius(radius, settings.use_min_distance)) - { - return true; - } + return true; + } - Polygons to_bp_data_2; - if (current_elem.to_buildplate) - { - // Regular union as output will not be used later => this area should always be a subset of the safeUnion one. - to_bp_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)).unionPolygons(); - } - Polygons to_model_data_2; - if (config.support_rests_on_model && !current_elem.to_buildplate) - { - to_model_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, current_elem.to_model_gracious? settings.type:AvoidanceType::COLLISION, true, settings.use_min_distance)).unionPolygons(); - } - Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; + Polygons to_bp_data_2; + if (current_elem.to_buildplate) + { + // Regular union as output will not be used later => this area should always be a subset of the safeUnion one. + to_bp_data_2 = increased.difference(volumes_.getAvoidance(next_radius, layer_idx - 1, settings.type, false, settings.use_min_distance)).unionPolygons(); + } + Polygons to_model_data_2; + if (config.support_rests_on_model && ! current_elem.to_buildplate) + { + to_model_data_2 = increased + .difference(volumes_.getAvoidance( + next_radius, + layer_idx - 1, + current_elem.to_model_gracious ? settings.type : AvoidanceType::COLLISION, + true, + settings.use_min_distance)) + .unionPolygons(); + } + Polygons check_layer_data_2 = current_elem.to_buildplate ? to_bp_data_2 : to_model_data_2; - return check_layer_data_2.area() > 1; - }; + return check_layer_data_2.area() > 1; + }; coord_t ceil_radius_before = volumes_.ceilRadius(radius, settings.use_min_distance); // If the Collision Radius is smaller than the actual radius, check if it can catch up without violating the avoidance. @@ -754,18 +756,15 @@ std::optional TreeSupport::increaseSingleArea current_ceil_radius = volumes_.getRadiusNextCeil(current_ceil_radius + 1, settings.use_min_distance); } size_t resulting_eff_dtt = current_elem.effective_radius_height; - while - ( - resulting_eff_dtt + 1 < current_elem.distance_to_top && - config.getRadius(resulting_eff_dtt + 1, current_elem.buildplate_radius_increases) <= current_ceil_radius && - config.getRadius(resulting_eff_dtt + 1, current_elem.buildplate_radius_increases) <= config.getRadius(current_elem) - ) + while (resulting_eff_dtt + 1 < current_elem.distance_to_top && config.getRadius(resulting_eff_dtt + 1, current_elem.buildplate_radius_increases) <= current_ceil_radius + && config.getRadius(resulting_eff_dtt + 1, current_elem.buildplate_radius_increases) <= config.getRadius(current_elem)) { resulting_eff_dtt++; } current_elem.effective_radius_height = resulting_eff_dtt; - // If catchup is not possible, it is likely that there is a hole below. Assuming the branches are in some kind of bowl, the branches should still stay away from the wall of the bowl if possible. + // If catchup is not possible, it is likely that there is a hole below. Assuming the branches are in some kind of bowl, the branches should still stay away from the + // wall of the bowl if possible. if (config.getCollisionRadius(current_elem) < config.increase_radius_until_radius && config.getCollisionRadius(current_elem) < config.getRadius(current_elem)) { Polygons new_to_bp_data; @@ -779,7 +778,7 @@ std::optional TreeSupport::increaseSingleArea to_bp_data = new_to_bp_data; } } - if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) + if (config.support_rests_on_model && (! current_elem.to_buildplate || mergelayer)) { new_to_model_data = to_model_data.difference(volumes_.getCollision(config.getRadius(current_elem), layer_idx - 1, current_elem.use_min_xy_dist)); if (new_to_model_data.area() > EPSILON) @@ -788,21 +787,24 @@ std::optional TreeSupport::increaseSingleArea } } } - } radius = config.getCollisionRadius(current_elem); const coord_t foot_radius_increase = config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)); const double planned_foot_increase = std::min(1.0, double(config.recommendedMinRadius(layer_idx - 1) - config.getRadius(current_elem)) / foot_radius_increase); - // ^^^ Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become bigger than precalculated. + // ^^^ Is nearly all of the time 1, but sometimes an increase of 1 could cause the radius to become bigger than recommendedMinRadius, which could cause the radius to become + // bigger than precalculated. - // If the support_rest_preference is GRACEFUL, increase buildplate_radius_increases anyway. This does ONLY affect the CollisionRadius, as the regular radius only includes the buildplate_radius_increases when the SupportElement is to_buildplate (which it can not be when support_rest_preference is GRACEFUL). - // If the branch later rests on the buildplate the to_buildplate flag will only need to be updated to ensure that the radius is also correctly increased. - // Downside is that the enlargement of the CollisionRadius can cause branches, that could rest on the model if the radius was not increased, to instead rest on the buildplate. - // A better way could be changing avoidance to model to not include the buildplate and then calculate avoidances by combining the to model avoidance without the radius increase with the to buildplate avoidance with the larger radius. - // This would require ensuring all requests for the avoidance would have to ensure that the correct hybrid avoidance is requested (which would only be relevant when support_rest_preference is GRACEFUL) - // Also unioning areas when an avoidance is requested may also have a relevant performance impact, so there can be an argument made that the current workaround is preferable. - const bool increase_bp_foot = planned_foot_increase > 0 && (current_elem.to_buildplate || (current_elem.to_model_gracious && config.support_rest_preference == RestPreference::GRACEFUL)); + // If the support_rest_preference is GRACEFUL, increase buildplate_radius_increases anyway. This does ONLY affect the CollisionRadius, as the regular radius only includes + // the buildplate_radius_increases when the SupportElement is to_buildplate (which it can not be when support_rest_preference is GRACEFUL). If the branch later rests on the + // buildplate the to_buildplate flag will only need to be updated to ensure that the radius is also correctly increased. Downside is that the enlargement of the + // CollisionRadius can cause branches, that could rest on the model if the radius was not increased, to instead rest on the buildplate. A better way could be changing + // avoidance to model to not include the buildplate and then calculate avoidances by combining the to model avoidance without the radius increase with the to buildplate + // avoidance with the larger radius. This would require ensuring all requests for the avoidance would have to ensure that the correct hybrid avoidance is requested (which + // would only be relevant when support_rest_preference is GRACEFUL) Also unioning areas when an avoidance is requested may also have a relevant performance impact, so there + // can be an argument made that the current workaround is preferable. + const bool increase_bp_foot + = planned_foot_increase > 0 && (current_elem.to_buildplate || (current_elem.to_model_gracious && config.support_rest_preference == RestPreference::GRACEFUL)); if (increase_bp_foot && config.getRadius(current_elem) >= config.branch_radius && config.getRadius(current_elem) >= config.increase_radius_until_radius) @@ -820,26 +822,30 @@ std::optional TreeSupport::increaseSingleArea { to_bp_data = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, settings.type, false, settings.use_min_distance))); } - if (config.support_rests_on_model && (!current_elem.to_buildplate || mergelayer)) + if (config.support_rests_on_model && (! current_elem.to_buildplate || mergelayer)) { - to_model_data = TreeSupportUtils::safeUnion(increased.difference(volumes_.getAvoidance(radius, layer_idx - 1, current_elem.to_model_gracious? settings.type:AvoidanceType::COLLISION, true, settings.use_min_distance))); + to_model_data = TreeSupportUtils::safeUnion(increased.difference( + volumes_.getAvoidance(radius, layer_idx - 1, current_elem.to_model_gracious ? settings.type : AvoidanceType::COLLISION, true, settings.use_min_distance))); } check_layer_data = current_elem.to_buildplate ? to_bp_data : to_model_data; if (check_layer_data.area() < 1) { - spdlog::error("Lost area by doing catch up from {} to radius {}", ceil_radius_before, volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance)); + spdlog::error( + "Lost area by doing catch up from {} to radius {}", + ceil_radius_before, + volumes_.ceilRadius(config.getCollisionRadius(current_elem), settings.use_min_distance)); } } } - if (current_elem.influence_area_limit_active && !current_elem.use_min_xy_dist && check_layer_data.area() > 1 && (current_elem.to_model_gracious || current_elem.distance_to_top <= config.min_dtt_to_model)) + if (current_elem.influence_area_limit_active && ! current_elem.use_min_xy_dist && check_layer_data.area() > 1 + && (current_elem.to_model_gracious || current_elem.distance_to_top <= config.min_dtt_to_model)) { - const coord_t max_radius_increase = - std::max - ( - static_cast((config.branch_radius - config.min_radius) / config.tip_layers), - static_cast((config.branch_radius * config.diameter_angle_scale_factor) + config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0))) - ); + const coord_t max_radius_increase = std::max( + static_cast((config.branch_radius - config.min_radius) / config.tip_layers), + static_cast( + (config.branch_radius * config.diameter_angle_scale_factor) + + config.branch_radius * (std::max(config.diameter_scale_bp_radius - config.diameter_angle_scale_factor, 0.0)))); bool limit_range_validated = false; // Rounding errors in a while loop can cause non-termination, so better safe than sorry. See https://github.com/Ultimaker/Cura/issues/14133 for an example. to_bp_data = TreeSupportUtils::safeUnion(to_bp_data); @@ -866,7 +872,7 @@ std::optional TreeSupport::increaseSingleArea limit_range_validated = true; } } - if (!limit_range_validated) + if (! limit_range_validated) { const coord_t reach_increase = std::max(current_elem.influence_area_limit_range / 4, (config.maximum_move_distance + max_radius_increase)); current_elem.influence_area_limit_range += reach_increase; @@ -878,19 +884,17 @@ std::optional TreeSupport::increaseSingleArea return check_layer_data.area() > 1 ? std::optional(current_elem) : std::optional(); } -void TreeSupport::increaseAreas -( +void TreeSupport::increaseAreas( PropertyAreasUnordered& to_bp_areas, PropertyAreas& to_model_areas, PropertyAreas& influence_areas, std::vector& bypass_merge_areas, const std::vector& last_layer, - const LayerIndex layer_idx, const bool mergelayer -) + const LayerIndex layer_idx, + const bool mergelayer) { std::mutex critical_sections; - cura::parallel_for - ( + cura::parallel_for( 0, last_layer.size(), [&](const size_t idx) @@ -910,25 +914,28 @@ void TreeSupport::increaseAreas // As the branch may have become larger the distance between these 2 walls is smaller than the distance of the center points. // These extra distance is added to the movement distance possible for this layer. - coord_t extra_speed = EPSILON; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause issues at VERY VERY small layer heights. + coord_t extra_speed = EPSILON; // The extra speed is added to both movement distances. Also move 5 microns faster than allowed to avoid rounding errors, this may cause + // issues at VERY VERY small layer heights. coord_t extra_slow_speed = 0; // Only added to the slow movement distance. const coord_t ceiled_parent_radius = volumes_.ceilRadius(config.getCollisionRadius(*parent), parent->use_min_xy_dist); const coord_t projected_radius_increased = config.getRadius(parent->effective_radius_height + 1, parent->buildplate_radius_increases); const coord_t projected_radius_delta = projected_radius_increased - config.getCollisionRadius(*parent); - // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): + // When z distance is more than one layer up and down the Collision used to calculate the wall restriction will always include the wall (and not just the + // xy_min_distance) of the layer above and below like this (d = blocked area because of z distance): /* * layer z+1:dddddiiiiiioooo * layer z+0:xxxxxdddddddddd * layer z-1:dddddxxxxxxxxxx * For more detailed visualisation see calculateWallRestrictions */ - const coord_t safe_movement_distance = - (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + - (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); - if (ceiled_parent_radius == volumes_.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) || projected_radius_increased < config.increase_radius_until_radius) + const coord_t safe_movement_distance = (elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) + + (std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0); + if (ceiled_parent_radius == volumes_.ceilRadius(projected_radius_increased, parent->use_min_xy_dist) + || projected_radius_increased < config.increase_radius_until_radius) { - // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall + // If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of + // the slower moving wall extra_speed += projected_radius_delta; } else @@ -938,16 +945,20 @@ void TreeSupport::increaseAreas extra_slow_speed += std::min(projected_radius_delta, (config.maximum_move_distance + extra_speed) - (config.maximum_move_distance_slow + extra_slow_speed)); } - if (config.layer_start_bp_radius > layer_idx && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.buildplate_radius_increases)) + if (config.layer_start_bp_radius > layer_idx + && config.recommendedMinRadius(layer_idx - 1) < config.getRadius(elem.effective_radius_height + 1, elem.buildplate_radius_increases)) { // Can guarantee elephant foot radius increase. - if (ceiled_parent_radius == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->buildplate_radius_increases + 1), parent->use_min_xy_dist)) + if (ceiled_parent_radius + == volumes_.ceilRadius(config.getRadius(parent->effective_radius_height + 1, parent->buildplate_radius_increases + 1), parent->use_min_xy_dist)) { extra_speed += config.branch_radius * config.diameter_scale_bp_radius; } else { - extra_slow_speed += std::min(coord_t(config.branch_radius * config.diameter_scale_bp_radius), config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); + extra_slow_speed += std::min( + coord_t(config.branch_radius * config.diameter_scale_bp_radius), + config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed)); } } @@ -968,60 +979,70 @@ void TreeSupport::increaseAreas // Determine in which order configurations are checked if they result in a valid influence area. Check will stop if a valid area is found std::deque order; - std::function insertSetting = - [&](const AreaIncreaseSettings& settings, bool back) + std::function insertSetting = [&](const AreaIncreaseSettings& settings, bool back) + { + if (std::find(order.begin(), order.end(), settings) == order.end()) { - if (std::find(order.begin(), order.end(), settings) == order.end()) + if (back) { - if (back) - { - order.emplace_back(settings); - } - else - { - order.emplace_front(settings); - } + order.emplace_back(settings); + } + else + { + order.emplace_front(settings); } - }; + } + }; const bool parent_moved_slow = elem.last_area_increase.increase_speed < config.maximum_move_distance; const bool avoidance_speed_mismatch = parent_moved_slow && elem.last_area_increase.type != AvoidanceType::SLOW; - if - ( - elem.last_area_increase.move && - elem.last_area_increase.no_error && - elem.can_use_safe_radius && - ! mergelayer && - ! avoidance_speed_mismatch && - (elem.distance_to_top >= config.tip_layers || parent_moved_slow) - ) + if (elem.last_area_increase.move && elem.last_area_increase.no_error && elem.can_use_safe_radius && ! mergelayer && ! avoidance_speed_mismatch + && (elem.distance_to_top >= config.tip_layers || parent_moved_slow)) { // Assume that the avoidance type that was best for the parent is best for me. Makes this function about 7% faster. const auto slow_or_fast = elem.last_area_increase.increase_speed < config.maximum_move_distance ? slow_speed : fast_speed; - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, slow_or_fast, increase_radius, elem.last_area_increase.no_error, ! use_min_radius, elem.last_area_increase.move), true); - insertSetting(AreaIncreaseSettings(elem.last_area_increase.type, slow_or_fast, ! increase_radius, elem.last_area_increase.no_error, ! use_min_radius, elem.last_area_increase.move), true); + insertSetting( + AreaIncreaseSettings( + elem.last_area_increase.type, + slow_or_fast, + increase_radius, + elem.last_area_increase.no_error, + ! use_min_radius, + elem.last_area_increase.move), + true); + insertSetting( + AreaIncreaseSettings( + elem.last_area_increase.type, + slow_or_fast, + ! increase_radius, + elem.last_area_increase.no_error, + ! use_min_radius, + elem.last_area_increase.move), + true); } // Branch may still go though a hole, so a check has to be done whether the hole was already passed, and the regular avoidance can be used. if (! elem.can_use_safe_radius) { // If the radius until which it is always increased can not be guaranteed, move fast. This is to avoid holes smaller than the real branch radius. // This does not guarantee the avoidance of such holes, but ensures they are avoided if possible. - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, !move), true); // Did we go through the hole. + insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, ! move), true); // Did we go through the hole. // In many cases the definition of hole is overly restrictive, so to avoid unnecessary fast movement in the tip, it is ignored there for a bit. // This CAN cause a branch to go though a hole it otherwise may have avoided. if (elem.distance_to_top < round_up_divide(config.tip_layers, 2)) { - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, ! use_min_radius, !move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, slow_speed, increase_radius, no_error, ! use_min_radius, ! move), true); } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, !move), true); // Did we manage to avoid the hole, - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, !increase_radius, no_error, ! use_min_radius, move), true); - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, no_error, ! use_min_radius, move), true); + insertSetting( + AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, increase_radius, no_error, ! use_min_radius, ! move), + true); // Did we manage to avoid the hole, + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST_SAFE, fast_speed, ! increase_radius, no_error, ! use_min_radius, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, no_error, ! use_min_radius, move), true); } else { insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, increase_radius, no_error, ! use_min_radius, move), true); - // While moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a layer shift and can reduce stability. - // As such idx have chosen to only use the user setting for radius increases as a friendly recommendation. + // While moving fast to be able to increase the radius (b) may seems preferable (over a) this can cause the a sudden skip in movement, which looks similar to a + // layer shift and can reduce stability. As such idx have chosen to only use the user setting for radius increases as a friendly recommendation. insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, slow_speed, ! increase_radius, no_error, ! use_min_radius, move), true); // a (See above.) if (elem.distance_to_top < config.tip_layers) { @@ -1044,43 +1065,48 @@ void TreeSupport::increaseAreas order = new_order; } - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); //simplifying is very important for performance, but before an error is compensated by moving faster it makes sense to check to see if the simplifying has caused issues + insertSetting( + AreaIncreaseSettings(AvoidanceType::FAST, fast_speed, ! increase_radius, ! no_error, elem.use_min_xy_dist, move), + true); // simplifying is very important for performance, but before an error is compensated by moving faster it makes sense to check to see if the simplifying has + // caused issues // The getAccumulatedPlaceable0 intersection is just a quick and dirty check to see that at least a part of the branch would correctly rest on the model. - // Proper way would be to offset getAccumulatedPlaceable0 by -radius first, but the small benefit to maybe detect an error, that should not be happening anyway is not worth the performance impact in the expected case when a branch rests on the model. - if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty())) || (!elem.to_model_gracious && (parent->area->intersection(volumes_.getAccumulatedPlaceable0(layer_idx)).empty())) ) // Error case. + // Proper way would be to offset getAccumulatedPlaceable0 by -radius first, but the small benefit to maybe detect an error, that should not be happening anyway is not + // worth the performance impact in the expected case when a branch rests on the model. + if (elem.to_buildplate || (elem.to_model_gracious && (parent->area->intersection(volumes_.getPlaceableAreas(radius, layer_idx)).empty())) + || (! elem.to_model_gracious && (parent->area->intersection(volumes_.getAccumulatedPlaceable0(layer_idx)).empty()))) // Error case. { // It is normal that we won't be able to find a new area at some point in time if we won't be able to reach layer 0 aka have to connect with the model. - insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed * 1.5, !increase_radius, !no_error, elem.use_min_xy_dist, move), true); + insertSetting(AreaIncreaseSettings(AvoidanceType::FAST, fast_speed * 1.5, ! increase_radius, ! no_error, elem.use_min_xy_dist, move), true); } if (elem.distance_to_top < elem.dont_move_until && elem.can_use_safe_radius) // Only do not move when holes would be avoided in every case. { - insertSetting(AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, !use_min_radius, !move), false); // Only do not move when already in a no hole avoidance with the regular xy distance. + insertSetting( + AreaIncreaseSettings(AvoidanceType::SLOW, 0, increase_radius, no_error, ! use_min_radius, ! move), + false); // Only do not move when already in a no hole avoidance with the regular xy distance. } Polygons inc_wo_collision; - // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to calculate the fast one. - // Calculated by comparing the steps saved when calculating independently with the saved steps when not. - const bool offset_independent_faster = - (radius / safe_movement_distance - (((config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) > - (round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance)); + // Check whether it is faster to calculate the area increased with the fast speed independently from the slow area, or time could be saved by reusing the slow area to + // calculate the fast one. Calculated by comparing the steps saved when calculating independently with the saved steps when not. + const bool offset_independent_faster = (radius / safe_movement_distance - (((config.maximum_move_distance + extra_speed) < (radius + safe_movement_distance)) ? 1 : 0)) + > (round_up_divide((extra_speed + extra_slow_speed + config.maximum_move_distance_slow), safe_movement_distance)); for (AreaIncreaseSettings settings : order) { if (settings.move) { if (offset_slow.empty() && (settings.increase_speed == slow_speed || ! offset_independent_faster)) { - offset_slow = - TreeSupportUtils::safeOffsetInc - ( - *parent->area, - extra_speed + extra_slow_speed + config.maximum_move_distance_slow, - wall_restriction, - safe_movement_distance, offset_independent_faster ? safe_movement_distance + radius : 0, - 2, // Offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. - config.support_line_distance / 2, - &config.simplifier - ).unionPolygons(); + offset_slow = TreeSupportUtils::safeOffsetInc( + *parent->area, + extra_speed + extra_slow_speed + config.maximum_move_distance_slow, + wall_restriction, + safe_movement_distance, + offset_independent_faster ? safe_movement_distance + radius : 0, + 2, // Offsetting in 2 steps makes our offsetted area rounder preventing (rounding) errors created by to pointy areas. + config.support_line_distance / 2, + &config.simplifier) + .unionPolygons(); // At this point one can see that the Polygons class was never made for precision in the single digit micron range. } @@ -1088,22 +1114,30 @@ void TreeSupport::increaseAreas { if (offset_independent_faster) { - offset_fast = - TreeSupportUtils::safeOffsetInc - ( - *parent->area, - extra_speed + config.maximum_move_distance, - wall_restriction, - safe_movement_distance, offset_independent_faster ? safe_movement_distance + radius : 0, - 1, - config.support_line_distance / 2, - &config.simplifier - ).unionPolygons(); + offset_fast = TreeSupportUtils::safeOffsetInc( + *parent->area, + extra_speed + config.maximum_move_distance, + wall_restriction, + safe_movement_distance, + offset_independent_faster ? safe_movement_distance + radius : 0, + 1, + config.support_line_distance / 2, + &config.simplifier) + .unionPolygons(); } else { const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed); - offset_fast = TreeSupportUtils::safeOffsetInc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independent_faster ? 2 : 1, config.support_line_distance / 2, &config.simplifier).unionPolygons(); + offset_fast = TreeSupportUtils::safeOffsetInc( + offset_slow, + delta_slow_fast, + wall_restriction, + safe_movement_distance, + safe_movement_distance + radius, + offset_independent_faster ? 2 : 1, + config.support_line_distance / 2, + &config.simplifier) + .unionPolygons(); } } } @@ -1112,20 +1146,22 @@ void TreeSupport::increaseAreas // Check for errors! if (! settings.no_error) { - - // If the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, it still actually has an area that can be increased + // If the area becomes for whatever reason something that clipper sees as a line, offset would stop working, so ensure that even if if wrongly would be a line, + // it still actually has an area that can be increased Polygons lines_offset = TreeSupportUtils::toPolylines(*parent->area).offsetPolyLine(EPSILON); Polygons base_error_area = parent->area->unionPolygons(lines_offset); result = increaseSingleArea(settings, layer_idx, parent, base_error_area, to_bp_data, to_model_data, inc_wo_collision, settings.increase_speed, mergelayer); - if(fast_speed < settings.increase_speed) + if (fast_speed < settings.increase_speed) { - spdlog::warn - ( + spdlog::warn( "Influence area could not be increased! Data about the Influence area: " - "Radius: {} at layer: {} NextTarget: {} Distance to top: {} Elephant foot increases {} use_min_xy_dist {} to buildplate {} gracious {} safe {} until move {} \n " - "Parent {}: Radius: {} at layer: {} NextTarget: {} Distance to top: {} Elephant foot increases {} use_min_xy_dist {} to buildplate {} gracious {} safe {} until move {}", - radius, layer_idx - 1, + "Radius: {} at layer: {} NextTarget: {} Distance to top: {} Elephant foot increases {} use_min_xy_dist {} to buildplate {} gracious {} safe {} until " + "move {} \n " + "Parent {}: Radius: {} at layer: {} NextTarget: {} Distance to top: {} Elephant foot increases {} use_min_xy_dist {} to buildplate {} gracious {} " + "safe {} until move {}", + radius, + layer_idx - 1, elem.next_height, elem.distance_to_top, elem.buildplate_radius_increases, @@ -1136,20 +1172,29 @@ void TreeSupport::increaseAreas elem.dont_move_until, fmt::ptr(parent), config.getCollisionRadius(*parent), - layer_idx, parent->next_height, + layer_idx, + parent->next_height, parent->distance_to_top, parent->buildplate_radius_increases, parent->use_min_xy_dist, parent->to_buildplate, parent->to_model_gracious, parent->can_use_safe_radius, - parent->dont_move_until - ); + parent->dont_move_until); } } else { - result = increaseSingleArea(settings, layer_idx, parent, settings.increase_speed == slow_speed ? offset_slow : offset_fast, to_bp_data, to_model_data, inc_wo_collision, std::max(settings.increase_speed-fast_speed,coord_t(0)), mergelayer); + result = increaseSingleArea( + settings, + layer_idx, + parent, + settings.increase_speed == slow_speed ? offset_slow : offset_fast, + to_bp_data, + to_model_data, + inc_wo_collision, + std::max(settings.increase_speed - fast_speed, coord_t(0)), + mergelayer); } if (result) @@ -1158,7 +1203,10 @@ void TreeSupport::increaseAreas radius = config.getCollisionRadius(elem); elem.last_area_increase = settings; add = true; - bypass_merge = ! settings.move || (settings.use_min_distance && elem.distance_to_top < config.tip_layers); // Do not merge if the branch should not move or the priority has to be to get farther away from the model. + bypass_merge + = ! settings.move + || (settings.use_min_distance + && elem.distance_to_top < config.tip_layers); // Do not merge if the branch should not move or the priority has to be to get farther away from the model. if (settings.move) { elem.dont_move_until = 0; @@ -1188,7 +1236,9 @@ void TreeSupport::increaseAreas if (add) { - Polygons max_influence_area = TreeSupportUtils::safeUnion(inc_wo_collision.difference(volumes_.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), TreeSupportUtils::safeUnion(to_bp_data, to_model_data)); + Polygons max_influence_area = TreeSupportUtils::safeUnion( + inc_wo_collision.difference(volumes_.getCollision(radius, layer_idx - 1, elem.use_min_xy_dist)), + TreeSupportUtils::safeUnion(to_bp_data, to_model_data)); // ^^^ Note: union seems useless, but some rounding errors somewhere can cause to_bp_data to be slightly bigger than it should be { @@ -1220,8 +1270,7 @@ void TreeSupport::increaseAreas // A point can be set on the top most tip layer (maybe more if it should not move for a few layers). parent->result_on_layer = Point(-1, -1); } - } - ); + }); } void TreeSupport::createLayerPathing(std::vector>& move_bounds) @@ -1239,7 +1288,9 @@ void TreeSupport::createLayerPathing(std::vector>& bool new_element = false; // Ensure at least one merge operation per 3mm height, 50 layers, 1 mm movement of slow speed or 5mm movement of fast speed (whatever is lowest). Values were guessed. - size_t max_merge_every_x_layers = std::min(std::min(5000 / (std::max(config.maximum_move_distance, static_cast(100))), 1000 / std::max(config.maximum_move_distance_slow, static_cast(20))), 3000 / config.layer_height); + size_t max_merge_every_x_layers = std::min( + std::min(5000 / (std::max(config.maximum_move_distance, static_cast(100))), 1000 / std::max(config.maximum_move_distance_slow, static_cast(20))), + 3000 / config.layer_height); size_t merge_every_x_layers = 1; // Calculate the influence areas for each layer below (Top down) @@ -1257,7 +1308,8 @@ void TreeSupport::createLayerPathing(std::vector>& PropertyAreas influence_areas; // Over this map will be iterated when merging, as such it has to be ordered to ensure deterministic results. PropertyAreas to_model_areas; // The area of these SupportElement is not set, to avoid to much allocation and deallocation on the heap. PropertyAreasUnordered to_bp_areas; // Same. - std::vector bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. + std::vector + bypass_merge_areas; // Different to the other maps of SupportElements as these here have the area already set, as they are already to be inserted into move_bounds. const auto time_a = std::chrono::high_resolution_clock::now(); @@ -1277,7 +1329,7 @@ void TreeSupport::createLayerPathing(std::vector>& last_merge = layer_idx; reduced_by_merging = count_before_merge > influence_areas.size(); - if (! reduced_by_merging && !new_element) + if (! reduced_by_merging && ! new_element) { merge_every_x_layers = std::min(max_merge_every_x_layers, merge_every_x_layers + 1); } @@ -1332,16 +1384,21 @@ void TreeSupport::setPointsOnAreas(const TreeSupportElement* elem) for (TreeSupportElement* next_elem : elem->parents) { - if (next_elem->result_on_layer != Point(-1, -1)) // If the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. + if (next_elem->result_on_layer + != Point(-1, -1)) // If the value was set somewhere else it it kept. This happens when a branch tries not to move after being unable to create a roof. { continue; } Point from = elem->result_on_layer; - if (!(next_elem->area->inside(from, true))) + if (! (next_elem->area->inside(from, true))) { - PolygonUtils::moveInside(*next_elem->area, from, 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 - // It is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. While this seems like a problem it may for example occur after merges. + PolygonUtils::moveInside( + *next_elem->area, + from, + 0); // Move inside has edgecases (see tests) so DONT use Polygons.inside to confirm correct move, Error with distance 0 is <= 1 + // It is not required to check if how far this move moved a point as is can be larger than maximum_movement_distance. While this seems like a problem it may for example + // occur after merges. } next_elem->result_on_layer = from; // Do not call recursive because then amount of layers would be restricted by the stack size. @@ -1365,7 +1422,8 @@ bool TreeSupport::setToModelContact(std::vector>& Polygons valid_place_area; - // Check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest up layer index. + // Check for every layer upwards, up to the point where this influence area was created (either by initial insert or merge) if the branch could be placed on it, and highest + // up layer index. for (LayerIndex layer_check = layer_idx; check->next_height >= layer_check; layer_check++) { Polygons check_valid_place_area = check->area->intersection(volumes_.getPlaceableAreas(config.getCollisionRadius(*check), layer_check)); @@ -1409,7 +1467,8 @@ bool TreeSupport::setToModelContact(std::vector>& } } - for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; ++layer) // NOTE: Use of 'itoa' will make this crash in the loop, even though the operation should be equivalent. + for (LayerIndex layer = layer_idx + 1; layer < last_successfull_layer - 1; + ++layer) // NOTE: Use of 'itoa' will make this crash in the loop, even though the operation should be equivalent. { move_bounds[layer].erase(checked[layer - layer_idx]); delete checked[layer - layer_idx]->area; @@ -1438,29 +1497,36 @@ bool TreeSupport::setToModelContact(std::vector>& else // can not add graceful => just place it here and hope for the best { Point best = first_elem->next_position; - Polygons valid_place_area = first_elem->area->difference(volumes_.getAvoidance(config.getCollisionRadius(first_elem), layer_idx, AvoidanceType::COLLISION, first_elem->use_min_xy_dist)); + Polygons valid_place_area + = first_elem->area->difference(volumes_.getAvoidance(config.getCollisionRadius(first_elem), layer_idx, AvoidanceType::COLLISION, first_elem->use_min_xy_dist)); - if (!valid_place_area.inside(best, true)) + if (! valid_place_area.inside(best, true)) { - if (!valid_place_area.empty()) + if (! valid_place_area.empty()) { PolygonUtils::moveInside(valid_place_area, best); } else { bool found_partial_placement; - for (coord_t radius_offset : { -config.getCollisionRadius(first_elem), -config.getCollisionRadius(first_elem) / 2, coord_t(0) }) // Interestingly the first radius is working most of the time, even though it seems like it shouldn't. + for (coord_t radius_offset : { -config.getCollisionRadius(first_elem), + -config.getCollisionRadius(first_elem) / 2, + coord_t(0) }) // Interestingly the first radius is working most of the time, even though it seems like it shouldn't. { valid_place_area = first_elem->area->intersection(volumes_.getAccumulatedPlaceable0(layer_idx).offset(radius_offset)); - if (!valid_place_area.empty()) + if (! valid_place_area.empty()) { PolygonUtils::moveInside(valid_place_area, best); - spdlog::warn("Not able to place branch fully on non support blocker at layer {} using offset {} for radius {}", layer_idx, radius_offset, config.getCollisionRadius(first_elem)); + spdlog::warn( + "Not able to place branch fully on non support blocker at layer {} using offset {} for radius {}", + layer_idx, + radius_offset, + config.getCollisionRadius(first_elem)); found_partial_placement = true; break; } } - if (!found_partial_placement) + if (! found_partial_placement) { PolygonUtils::moveInside(*first_elem->area, best); spdlog::warn("Not able to place branch on non support blocker at layer {}", layer_idx); @@ -1476,7 +1542,8 @@ bool TreeSupport::setToModelContact(std::vector>& void TreeSupport::createNodesFromArea(std::vector>& move_bounds) { - // Initialize points on layer 0, with a "random" point in the influence area. Point is chosen based on an inaccurate estimate where the branches will split into two, but every point inside the influence area would produce a valid result. + // Initialize points on layer 0, with a "random" point in the influence area. Point is chosen based on an inaccurate estimate where the branches will split into two, but every + // point inside the influence area would produce a valid result. std::unordered_set remove; for (TreeSupportElement* init : move_bounds[0]) { @@ -1497,7 +1564,8 @@ void TreeSupport::createNodesFromArea(std::vector> } else { - // If the support_rest_preference is GRACEFUL the collision radius is increased, but the radius will only be increased if the element is to_buildplate, so if the branch rests on the buildplate, the element will have to be updated to include this information. + // If the support_rest_preference is GRACEFUL the collision radius is increased, but the radius will only be increased if the element is to_buildplate, so if the + // branch rests on the buildplate, the element will have to be updated to include this information. init->setToBuildplateForAllParents(true); } } @@ -1518,17 +1586,23 @@ void TreeSupport::createNodesFromArea(std::vector> bool removed = false; if (elem->result_on_layer == Point(-1, -1)) // Check if the resulting center point is not yet set. { - if (elem->to_buildplate || (!elem->to_buildplate && elem->distance_to_top < config.min_dtt_to_model && !elem->supports_roof)) + if (elem->to_buildplate || (! elem->to_buildplate && elem->distance_to_top < config.min_dtt_to_model && ! elem->supports_roof)) { if (elem->to_buildplate) { - spdlog::error("Uninitialized Influence area targeting ({},{}) at target_height: {} layer: {}", elem->target_position.X, elem->target_position.Y, elem->target_height, layer_idx); + spdlog::error( + "Uninitialized Influence area targeting ({},{}) at target_height: {} layer: {}", + elem->target_position.X, + elem->target_position.Y, + elem->target_height, + layer_idx); } remove.emplace(elem); // We dont need to remove yet the parents as they will have a lower dtt and also no result_on_layer set. removed = true; for (TreeSupportElement* parent : elem->parents) { - // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs to be removed => all parents result_on_layer have to be invalidated. + // When the roof was not able to generate downwards enough, the top elements may have not moved, and have result_on_layer already set. As this branch needs + // to be removed => all parents result_on_layer have to be invalidated. parent->result_on_layer = Point(-1, -1); } continue; @@ -1544,7 +1618,7 @@ void TreeSupport::createNodesFromArea(std::vector> } } - if (!removed) + if (! removed) { setPointsOnAreas(elem); // Element is valid now setting points in the layer above. } @@ -1561,7 +1635,10 @@ void TreeSupport::createNodesFromArea(std::vector> } } -void TreeSupport::generateBranchAreas(std::vector>& linear_data, std::vector>& layer_tree_polygons, const std::map& inverse_tree_order) +void TreeSupport::generateBranchAreas( + std::vector>& linear_data, + std::vector>& layer_tree_polygons, + const std::map& inverse_tree_order) { double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC; constexpr int progress_report_steps = 10; @@ -1580,136 +1657,135 @@ void TreeSupport::generateBranchAreas(std::vector - ( - 0, - linear_data.size(), - [&](const size_t idx) - { - TreeSupportElement* elem = linear_data[idx].second; - coord_t radius = config.getRadius(*elem); - bool parent_uses_min = false; - TreeSupportElement* child_elem = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr; + cura::parallel_for( + 0, + linear_data.size(), + [&](const size_t idx) + { + TreeSupportElement* elem = linear_data[idx].second; + coord_t radius = config.getRadius(*elem); + bool parent_uses_min = false; + TreeSupportElement* child_elem = inverse_tree_order.count(elem) ? inverse_tree_order.at(elem) : nullptr; - // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. - std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; - if (! elem->skip_ovalisation) + // Calculate multiple ovalized circles, to connect with every parent and child. Also generate regular circle for the current layer. Merge all these into one area. + std::vector> movement_directions{ std::pair(Point(0, 0), radius) }; + if (! elem->skip_ovalisation) + { + if (child_elem != nullptr) { - if (child_elem != nullptr) - { - Point movement = (child_elem->result_on_layer - elem->result_on_layer); - movement_directions.emplace_back(movement, radius); - } - for (TreeSupportElement* parent : elem->parents) - { - Point movement = (parent->result_on_layer - elem->result_on_layer); - movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); - parent_uses_min |= parent->use_min_xy_dist; - } - - for (Point target: elem->additional_ovalization_targets) - { - Point movement = (target - elem->result_on_layer); - movement_directions.emplace_back(movement, std::max(radius, config.support_line_width)); - } - + Point movement = (child_elem->result_on_layer - elem->result_on_layer); + movement_directions.emplace_back(movement, radius); } - - coord_t max_speed_sqd = 0; - std::function generateArea = - [&](coord_t offset) + for (TreeSupportElement* parent : elem->parents) { - Polygons poly; + Point movement = (parent->result_on_layer - elem->result_on_layer); + movement_directions.emplace_back(movement, std::max(config.getRadius(parent), config.support_line_width)); + parent_uses_min |= parent->use_min_xy_dist; + } - for (std::pair movement : movement_directions) - { - max_speed_sqd = std::max(max_speed_sqd, vSize2(movement.first)); + for (Point target : elem->additional_ovalization_targets) + { + Point movement = (target - elem->result_on_layer); + movement_directions.emplace_back(movement, std::max(radius, config.support_line_width)); + } + } - // Visualization: https://jsfiddle.net/0zvcq39L/2/ - // Ovalizes the circle to an ellipse, that contains both old center and new target position. - double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); - Point center_position = elem->result_on_layer + movement.first / 2; - const double moveX = movement.first.X / (used_scale * config.branch_radius); - const double moveY = movement.first.Y / (used_scale * config.branch_radius); - const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); + coord_t max_speed_sqd = 0; + std::function generateArea = [&](coord_t offset) + { + Polygons poly; - std::array matrix = - { - used_scale * (1 + moveX * moveX * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (0 + moveX * moveY * vsize_inv), - used_scale * (1 + moveY * moveY * vsize_inv), - }; - Polygon circle; - for (Point vertex : branch_circle) - { - vertex = Point(matrix[0] * vertex.X + matrix[1] * vertex.Y, matrix[2] * vertex.X + matrix[3] * vertex.Y); - circle.add(center_position + vertex); - } - poly.add(circle.offset(0)); + for (std::pair movement : movement_directions) + { + max_speed_sqd = std::max(max_speed_sqd, vSize2(movement.first)); + + // Visualization: https://jsfiddle.net/0zvcq39L/2/ + // Ovalizes the circle to an ellipse, that contains both old center and new target position. + double used_scale = (movement.second + offset) / (1.0 * config.branch_radius); + Point center_position = elem->result_on_layer + movement.first / 2; + const double moveX = movement.first.X / (used_scale * config.branch_radius); + const double moveY = movement.first.Y / (used_scale * config.branch_radius); + const double vsize_inv = 0.5 / (0.01 + std::sqrt(moveX * moveX + moveY * moveY)); + + std::array matrix = { + used_scale * (1 + moveX * moveX * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (0 + moveX * moveY * vsize_inv), + used_scale * (1 + moveY * moveY * vsize_inv), + }; + Polygon circle; + for (Point vertex : branch_circle) + { + vertex = Point(matrix[0] * vertex.X + matrix[1] * vertex.Y, matrix[2] * vertex.X + matrix[3] * vertex.Y); + circle.add(center_position + vertex); } + poly.add(circle.offset(0)); + } - poly = poly.unionPolygons().offset(std::min(static_cast(FUDGE_LENGTH), config.support_line_width / 4)).difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); - // ^^^ There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly further away front the overhang (x/y wise) than optimal. - // This fixes it, and for every other part, 0.05mm will not be noticed. - return poly; - }; + poly = poly.unionPolygons() + .offset(std::min(static_cast(FUDGE_LENGTH), config.support_line_width / 4)) + .difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)); + // ^^^ There seem to be some rounding errors, causing a branch to be a tiny bit further away from the model that it has to be. This can cause the tip to be slightly + // further away front the overhang (x/y wise) than optimal. + // This fixes it, and for every other part, 0.05mm will not be noticed. + return poly; + }; - constexpr auto three_quarters_sqd = 0.75 * 0.75; - const bool fast_relative_movement = max_speed_sqd > (radius * radius * three_quarters_sqd); + constexpr auto three_quarters_sqd = 0.75 * 0.75; + const bool fast_relative_movement = max_speed_sqd > (radius * radius * three_quarters_sqd); - // Ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. - linear_inserts[idx] = generateArea(0); + // Ensure branch area will not overlap with model/collision. This can happen because of e.g. ovalization or increase_until_radius. + linear_inserts[idx] = generateArea(0); - if (fast_relative_movement || config.getRadius(*elem) - config.getCollisionRadius(*elem) > config.support_line_width) + if (fast_relative_movement || config.getRadius(*elem) - config.getCollisionRadius(*elem) > config.support_line_width) + { + // Simulate the path the nozzle will take on the outermost wall. + // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. + Polygons nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); + if (nozzle_path.splitIntoParts(false).size() > 1) { - // Simulate the path the nozzle will take on the outermost wall. - // If multiple parts exist, the outer line will not go all around the support part potentially causing support material to be printed mid air. - Polygons nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); + // Just try to make the area a tiny bit larger. + linear_inserts[idx] = generateArea(config.support_line_width / 2); + nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); + + // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best if (nozzle_path.splitIntoParts(false).size() > 1) { - // Just try to make the area a tiny bit larger. - linear_inserts[idx] = generateArea(config.support_line_width / 2); - nozzle_path = linear_inserts[idx].offset(-config.support_line_width / 2); - - // if larger area did not fix the problem, all parts off the nozzle path that do not contain the center point are removed, hoping for the best - if (nozzle_path.splitIntoParts(false).size() > 1) + Polygons polygons_with_correct_center; + for (PolygonsPart part : nozzle_path.splitIntoParts(false)) { - Polygons polygons_with_correct_center; - for (PolygonsPart part : nozzle_path.splitIntoParts(false)) + if (part.inside(elem->result_on_layer, true)) { - if (part.inside(elem->result_on_layer, true)) + polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); + } + else + { + // Try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... + Point from = elem->result_on_layer; + PolygonUtils::moveInside(part, from, 0); + if (vSize2(elem->result_on_layer - from) < (FUDGE_LENGTH * FUDGE_LENGTH) / 4) { polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); } - else - { - // Try a fuzzy inside as sometimes the point should be on the border, but is not because of rounding errors... - Point from = elem->result_on_layer; - PolygonUtils::moveInside(part, from, 0); - if (vSize2(elem->result_on_layer - from) < (FUDGE_LENGTH * FUDGE_LENGTH) / 4) - { - polygons_with_correct_center = polygons_with_correct_center.unionPolygons(part); - } - } } - // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. - linear_inserts[idx] = polygons_with_correct_center.offset(config.support_line_width / 2).unionPolygons(); - linear_inserts[idx] = linear_inserts[idx].difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)).unionPolygons(); } + // Increase the area again, to ensure the nozzle path when calculated later is very similar to the one assumed above. + linear_inserts[idx] = polygons_with_correct_center.offset(config.support_line_width / 2).unionPolygons(); + linear_inserts[idx] + = linear_inserts[idx].difference(volumes_.getCollision(0, linear_data[idx].first, parent_uses_min || elem->use_min_xy_dist)).unionPolygons(); } } + } - if (idx % progress_inserts_check_interval == 0) + if (idx % progress_inserts_check_interval == 0) + { { - { - std::lock_guard critical_section_progress(critical_sections); - progress_total += TREE_PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; - Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); - } + std::lock_guard critical_section_progress(critical_sections); + progress_total += TREE_PROGRESS_GENERATE_BRANCH_AREAS / progress_report_steps; + Progress::messageProgress(Progress::Stage::SUPPORT, progress_total * progress_multiplier + progress_offset, TREE_PROGRESS_TOTAL); } } - ); + }); // Single threaded combining all elements to the right layers. Only copies data! for (const coord_t i : ranges::views::iota(0UL, linear_data.size())) @@ -1729,8 +1805,7 @@ void TreeSupport::smoothBranchAreas(std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); std::vector>> update_next(processing.size()); // With this a lock can be avoided. - cura::parallel_for - ( + cura::parallel_for( 0, processing.size(), [&](const size_t processing_idx) @@ -1744,10 +1819,13 @@ void TreeSupport::smoothBranchAreas(std::vectorresult_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); + max_outer_wall_distance = std::max( + max_outer_wall_distance, + vSize(data_pair.first->result_on_layer - parent->result_on_layer) - (config.getRadius(*data_pair.first) - config.getRadius(*parent))); } } - max_outer_wall_distance += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. + max_outer_wall_distance + += max_radius_change_per_layer; // As this change is a bit larger than what usually appears, lost radius can be slowly reclaimed over the layers. if (do_something) { Polygons max_allowed_area = data_pair.second.offset(max_outer_wall_distance); @@ -1755,12 +1833,12 @@ void TreeSupport::smoothBranchAreas(std::vector(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); + update_next[processing_idx].emplace_back( + std::pair(parent, layer_tree_polygons[layer_idx + 1][parent].intersection(max_allowed_area))); } } } - } - ); + }); for (std::vector> data_vector : update_next) { @@ -1782,10 +1860,11 @@ void TreeSupport::smoothBranchAreas(std::vector> processing; processing.insert(processing.end(), layer_tree_polygons[layer_idx].begin(), layer_tree_polygons[layer_idx].end()); - std::vector> update_next(processing.size(), std::pair(nullptr, Polygons())); // With this a lock can be avoided. + std::vector> update_next( + processing.size(), + std::pair(nullptr, Polygons())); // With this a lock can be avoided. - cura::parallel_for - ( + cura::parallel_for( 0, processing.size(), [&](const size_t processing_idx) @@ -1819,8 +1898,7 @@ void TreeSupport::smoothBranchAreas(std::vector(data_pair.first, result); } } - } - ); + }); updated_last_iteration.clear(); for (std::pair data_pair : update_next) @@ -1837,22 +1915,21 @@ void TreeSupport::smoothBranchAreas(std::vector>& layer_tree_polygons, const std::vector>& linear_data, std::vector>>& dropped_down_areas, - const std::map& inverse_tree_order -) + const std::map& inverse_tree_order) { - cura::parallel_for - ( + cura::parallel_for( 0, linear_data.size(), [&](const size_t idx) { TreeSupportElement* elem = linear_data[idx].second; - bool non_gracious_model_contact = ! elem->to_model_gracious && ! inverse_tree_order.count(elem); // If an element has no child, it connects to whatever is below as no support further down for it will exist. + bool non_gracious_model_contact + = ! elem->to_model_gracious + && ! inverse_tree_order.count(elem); // If an element has no child, it connects to whatever is below as no support further down for it will exist. if (non_gracious_model_contact) { Polygons rest_support = layer_tree_polygons[linear_data[idx].first][elem].intersection(volumes_.getAccumulatedPlaceable0(linear_data[idx].first)); @@ -1862,8 +1939,7 @@ void TreeSupport::dropNonGraciousAreas dropped_down_areas[idx].emplace_back(linear_data[idx].first - counter, rest_support); } } - } - ); + }); } @@ -1871,8 +1947,8 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora { const auto t_start = std::chrono::high_resolution_clock::now(); - const coord_t closing_dist=config.support_line_width*config.support_wall_count; - const coord_t open_close_distance = config.fill_outline_gaps ? config.min_feature_size/ 2 - 5 : config.min_wall_line_width/ 2 - 5; // based on calculation in WallToolPath + const coord_t closing_dist = config.support_line_width * config.support_wall_count; + const coord_t open_close_distance = config.fill_outline_gaps ? config.min_feature_size / 2 - 5 : config.min_wall_line_width / 2 - 5; // based on calculation in WallToolPath const double small_area_length = INT2MM(static_cast(config.support_line_width) / 2); std::function reversePolygon = [&](Polygons& poly) @@ -1884,107 +1960,103 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora }; - std::vector support_holes(support_layer_storage.size(),Polygons()); - //Extract all holes as polygon objects - cura::parallel_for - ( - 0, - support_layer_storage.size(), - [&](const LayerIndex layer_idx) - { - - - support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx].smooth(FUDGE_LENGTH))).offset(-open_close_distance).offset(open_close_distance * 2).offset(-open_close_distance); - support_layer_storage[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); + std::vector support_holes(support_layer_storage.size(), Polygons()); + // Extract all holes as polygon objects + cura::parallel_for( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx].smooth(FUDGE_LENGTH))) + .offset(-open_close_distance) + .offset(open_close_distance * 2) + .offset(-open_close_distance); + support_layer_storage[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); - std::vector parts = support_layer_storage[layer_idx].sortByNesting(); + std::vector parts = support_layer_storage[layer_idx].sortByNesting(); - if (parts.size() <= 1) - { - return; - } + if (parts.size() <= 1) + { + return; + } - Polygons holes_original; - for (const size_t idx : ranges::views::iota(1UL, parts.size())) - { - Polygons area = parts[idx]; - reversePolygon(area); - holes_original.add(area); - } - support_holes[layer_idx] = holes_original; + Polygons holes_original; + for (const size_t idx : ranges::views::iota(1UL, parts.size())) + { + Polygons area = parts[idx]; + reversePolygon(area); + holes_original.add(area); } - ); + support_holes[layer_idx] = holes_original; + }); const auto t_union = std::chrono::high_resolution_clock::now(); std::vector> holeparts(support_layer_storage.size()); - //Split all holes into parts - cura::parallel_for - ( - 0, - support_layer_storage.size(), - [&](const LayerIndex layer_idx) + // Split all holes into parts + cura::parallel_for( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + for (Polygons hole : support_holes[layer_idx].splitIntoParts()) { - for (Polygons hole:support_holes[layer_idx].splitIntoParts()) - { - holeparts[layer_idx].emplace_back(hole); - } + holeparts[layer_idx].emplace_back(hole); } - ); - std::vector>> hole_rest_map (holeparts.size()); - std::vector> holes_resting_outside (holeparts.size()); + }); + std::vector>> hole_rest_map(holeparts.size()); + std::vector> holes_resting_outside(holeparts.size()); - //Figure out which hole rests on which other hole - cura::parallel_for - ( - 1, - support_layer_storage.size(), - [&](const LayerIndex layer_idx) + // Figure out which hole rests on which other hole + cura::parallel_for( + 1, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + if (holeparts[layer_idx].empty()) { - if (holeparts[layer_idx].empty()) - { - return; - } + return; + } - Polygons outer_walls = - TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).tubeShape(closing_dist,0);//.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + Polygons outer_walls + = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) + .tubeShape(closing_dist, 0); //.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); - Polygons holes_below; + Polygons holes_below; - for (auto poly: holeparts[layer_idx - 1]) + for (auto poly : holeparts[layer_idx - 1]) + { + holes_below.add(poly); + } + + for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) + { + AABB hole_aabb = AABB(hole); + hole_aabb.expand(EPSILON); + if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) { - holes_below.add(poly); + holes_resting_outside[layer_idx].emplace(idx); } - - for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) + else { - AABB hole_aabb = AABB(hole); - hole_aabb.expand(EPSILON); - if (!hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls,hole_aabb)).empty()) + for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { - holes_resting_outside[layer_idx].emplace(idx); - } - else - { - for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) + if (hole_aabb.hit(AABB(hole2)) + && ! hole.intersection(hole2).empty()) // TODO should technically be outline: Check if this is fine either way as it would save an offset { - - if (hole_aabb.hit(AABB(hole2)) && ! hole.intersection(hole2).empty() ) // TODO should technically be outline: Check if this is fine either way as it would save an offset - { - hole_rest_map[layer_idx][idx].emplace_back(idx2); - } + hole_rest_map[layer_idx][idx].emplace_back(idx2); } } } } - ); + }); const auto t_hole_rest_ordering = std::chrono::high_resolution_clock::now(); std::unordered_set removed_holes_by_idx; std::vector valid_holes(support_holes.size(), Polygons()); - //Check which holes have to be removed as they do not rest on anything. Only keep holes that have to be removed + // Check which holes have to be removed as they do not rest on anything. Only keep holes that have to be removed for (const size_t layer_idx : ranges::views::iota(1UL, support_holes.size())) { std::unordered_set next_removed_holes_by_idx; @@ -1998,7 +2070,8 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } else { - if(hole_rest_map[layer_idx].contains(idx)){ + if (hole_rest_map[layer_idx].contains(idx)) + { for (size_t resting_idx : hole_rest_map[layer_idx][idx]) { if (! removed_holes_by_idx.contains(resting_idx)) @@ -2023,24 +2096,22 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } const auto t_hole_removal_tagging = std::chrono::high_resolution_clock::now(); - //Check if holes are so close to each other that two lines will be printed directly next to each other, which is assumed stable (as otherwise the simulated support pattern will not work correctly) and remove all remaining, invalid holes - cura::parallel_for - ( - 1, - support_layer_storage.size(), - [&](const LayerIndex layer_idx) + // Check if holes are so close to each other that two lines will be printed directly next to each other, which is assumed stable (as otherwise the simulated support pattern + // will not work correctly) and remove all remaining, invalid holes + cura::parallel_for( + 1, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + if (holeparts[layer_idx].empty()) { - if (holeparts[layer_idx].empty()) - { - return; - } - - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].getOutsidePolygons(); - reversePolygon(valid_holes[layer_idx]); - support_layer_storage[layer_idx].add(valid_holes[layer_idx]); + return; } - ); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].getOutsidePolygons(); + reversePolygon(valid_holes[layer_idx]); + support_layer_storage[layer_idx].add(valid_holes[layer_idx]); + }); const auto t_end = std::chrono::high_resolution_clock::now(); @@ -2050,19 +2121,24 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora const auto dur_hole_removal_tagging = 0.001 * std::chrono::duration_cast(t_hole_removal_tagging - t_hole_rest_ordering).count(); const auto dur_hole_removal = 0.001 * std::chrono::duration_cast(t_end - t_hole_removal_tagging).count(); - spdlog::debug("Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes that are invalid and not close enough to a valid hole: {} ms", dur_union,dur_hole_rest_ordering,dur_hole_removal_tagging, dur_hole_removal); - + spdlog::debug( + "Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes " + "that are invalid and not close enough to a valid hole: {} ms", + dur_union, + dur_hole_rest_ordering, + dur_hole_removal_tagging, + dur_hole_removal); } void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage) { InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; - double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; + double progress_total = TREE_PROGRESS_PRECALC_AVO + TREE_PROGRESS_PRECALC_COLL + TREE_PROGRESS_GENERATE_NODES + TREE_PROGRESS_AREA_CALC + TREE_PROGRESS_GENERATE_BRANCH_AREAS + + TREE_PROGRESS_SMOOTH_BRANCH_AREAS; // Iterate over the generated circles in parallel and clean them up. Also add support floor. std::mutex critical_sections; - cura::parallel_for - ( + cura::parallel_for( 0, support_layer_storage.size(), [&](const LayerIndex layer_idx) @@ -2071,43 +2147,53 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor // Subtract support lines of the branches from the roof storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); - if (!storage.support.supportLayers[layer_idx].support_roof.empty() && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) + if (! storage.support.supportLayers[layer_idx].support_roof.empty() + && support_layer_storage[layer_idx].intersection(storage.support.supportLayers[layer_idx].support_roof).area() > 1) { switch (interface_pref) { - case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(storage.support.supportLayers[layer_idx].support_roof); - break; - - case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(support_layer_storage[layer_idx]); - break; - - case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: - { - Polygons interface_lines = - TreeSupportUtils::generateSupportInfillLines(storage.support.supportLayers[layer_idx].support_roof, config, true, layer_idx, config.support_roof_line_distance, storage.support.cross_fill_provider, true) - .offsetPolyLine(config.support_roof_line_width / 2); - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(interface_lines); - } + case InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT: + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(storage.support.supportLayers[layer_idx].support_roof); break; - case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: - { - Polygons tree_lines; - tree_lines = - tree_lines.unionPolygons - ( - TreeSupportUtils::generateSupportInfillLines(support_layer_storage[layer_idx], config, false, layer_idx, config.support_line_distance, storage.support.cross_fill_provider, true) - .offsetPolyLine(config.support_line_width / 2) - ); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); - // Do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. - } + case InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE: + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(support_layer_storage[layer_idx]); break; - case InterfacePreference::NOTHING: - break; + case InterfacePreference::INTERFACE_LINES_OVERWRITE_SUPPORT: + { + Polygons interface_lines = TreeSupportUtils::generateSupportInfillLines( + storage.support.supportLayers[layer_idx].support_roof, + config, + true, + layer_idx, + config.support_roof_line_distance, + storage.support.cross_fill_provider, + true) + .offsetPolyLine(config.support_roof_line_width / 2); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(interface_lines); + } + break; + + case InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE: + { + Polygons tree_lines; + tree_lines = tree_lines.unionPolygons(TreeSupportUtils::generateSupportInfillLines( + support_layer_storage[layer_idx], + config, + false, + layer_idx, + config.support_line_distance, + storage.support.cross_fill_provider, + true) + .offsetPolyLine(config.support_line_width / 2)); + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.difference(tree_lines); + // Do not draw roof where the tree is. I prefer it this way as otherwise the roof may cut of a branch from its support below. + } + break; + + case InterfacePreference::NOTHING: + break; } } @@ -2119,8 +2205,10 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor size_t layers_below = 0; while (layers_below <= config.support_bottom_layers) { - // One sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each other. - const size_t sample_layer = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); + // One sample at 0 layers below, another at config.support_bottom_layers. In-between samples at config.performance_interface_skip_layers distance from each + // other. + const size_t sample_layer + = static_cast(std::max(0, (static_cast(layer_idx) - static_cast(layers_below)) - static_cast(config.z_distance_bottom_layers))); constexpr bool no_support = false; constexpr bool no_prime_tower = false; floor_layer.add(layer_outset.intersection(storage.getLayerOutlines(sample_layer, no_support, no_prime_tower))); @@ -2138,10 +2226,9 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. } - for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. - { - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); - } + constexpr bool convert_every_part = true; // Convert every part into a PolygonsPart for the support. + storage.support.supportLayers[layer_idx] + .fillInfillParts(layer_idx, support_layer_storage, config.support_line_width, config.support_wall_count, config.maximum_move_distance, convert_every_part); { std::lock_guard critical_section_progress(critical_sections); @@ -2151,32 +2238,31 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor { std::lock_guard critical_section_storage(critical_sections); - if (!storage.support.supportLayers[layer_idx].support_infill_parts.empty() || !storage.support.supportLayers[layer_idx].support_roof.empty()) + if (! storage.support.supportLayers[layer_idx].support_infill_parts.empty() || ! storage.support.supportLayers[layer_idx].support_roof.empty()) { storage.support.layer_nr_max_filled_layer = std::max(storage.support.layer_nr_max_filled_layer, static_cast(layer_idx)); } } - } - ); + }); } void TreeSupport::drawAreas(std::vector>& move_bounds, SliceDataStorage& storage) { std::vector support_layer_storage(move_bounds.size()); std::vector support_roof_storage(move_bounds.size()); - std::map inverse_tree_order; // In the tree structure only the parents can be accessed. Inverse this to be able to access the children. - std::vector> linear_data; // All SupportElements are put into a layer independent storage to improve parallelization. Was added at a point in time where this function had performance issues. - // These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. + std::map + inverse_tree_order; // In the tree structure only the parents can be accessed. Inverse this to be able to access the children. + std::vector> + linear_data; // All SupportElements are put into a layer independent storage to improve parallelization. Was added at a point in time where this function had performance + // issues. These were fixed by creating less initial points, but i do not see a good reason to remove a working performance optimization. for (const auto layer_idx : ranges::views::iota(0UL, move_bounds.size())) { for (TreeSupportElement* elem : move_bounds[layer_idx]) { // (Check if) We either come from nowhere at the final layer or we had invalid parents 2. should never happen but just to be sure: - if - ( - (layer_idx > 0 && ((!inverse_tree_order.count(elem) && elem->target_height == layer_idx && config.min_dtt_to_model > 0 && !elem->to_buildplate) || - (inverse_tree_order.count(elem) && inverse_tree_order[elem]->result_on_layer == Point(-1, -1)))) - ) + if ((layer_idx > 0 + && ((! inverse_tree_order.count(elem) && elem->target_height == layer_idx && config.min_dtt_to_model > 0 && ! elem->to_buildplate) + || (inverse_tree_order.count(elem) && inverse_tree_order[elem]->result_on_layer == Point(-1, -1))))) { continue; } @@ -2202,7 +2288,8 @@ void TreeSupport::drawAreas(std::vector>& move_bou generateBranchAreas(linear_data, layer_tree_polygons, inverse_tree_order); const auto t_generate = std::chrono::high_resolution_clock::now(); - // In some edge-cases a branch may go through a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be caught and smoothed out. + // In some edge-cases a branch may go through a hole, where the regular radius does not fit. This can result in an apparent jump in branch radius. As such this cases need to be + // caught and smoothed out. smoothBranchAreas(layer_tree_polygons); const auto t_smooth = std::chrono::high_resolution_clock::now(); @@ -2221,19 +2308,15 @@ void TreeSupport::drawAreas(std::vector>& move_bou } // ensure all branch areas added as roof actually cause a roofline to generate. Else disable turning the branch to roof going down - cura::parallel_for - ( + cura::parallel_for( 0, layer_tree_polygons.size(), [&](const size_t layer_idx) { for (std::pair data_pair : layer_tree_polygons[layer_idx]) { - if - ( - data_pair.first->missing_roof_layers > data_pair.first->distance_to_top && - TreeSupportUtils::generateSupportInfillLines(data_pair.second, config, true, layer_idx, config.support_roof_line_distance, nullptr, true).empty() - ) + if (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top + && TreeSupportUtils::generateSupportInfillLines(data_pair.second, config, true, layer_idx, config.support_roof_line_distance, nullptr, true).empty()) { std::vector to_disable_roofs; to_disable_roofs.emplace_back(data_pair.first); @@ -2252,8 +2335,7 @@ void TreeSupport::drawAreas(std::vector>& move_bou } } } - } - ); + }); // Single threaded combining all support areas to the right layers. // Only copies data! @@ -2261,15 +2343,13 @@ void TreeSupport::drawAreas(std::vector>& move_bou { for (std::pair data_pair : layer_tree_polygons[layer_idx]) { - ( - (data_pair.first->missing_roof_layers > data_pair.first->distance_to_top) ? support_roof_storage : support_layer_storage - )[layer_idx].add(data_pair.second); + ((data_pair.first->missing_roof_layers > data_pair.first->distance_to_top) ? support_roof_storage : support_layer_storage)[layer_idx].add(data_pair.second); } } for (const auto layer_idx : ranges::views::iota(0UL, additional_required_support_area.size())) { - if(support_layer_storage.size() > layer_idx) + if (support_layer_storage.size() > layer_idx) { support_layer_storage[layer_idx].add(additional_required_support_area[layer_idx]); } @@ -2287,7 +2367,14 @@ void TreeSupport::drawAreas(std::vector>& move_bou const auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); const auto dur_filter = 0.001 * std::chrono::duration_cast(t_filter - t_drop).count(); const auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_filter).count(); - spdlog::info("Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms filterFloatingLines: {} ms finalizeInterfaceAndSupportAreas {} ms", dur_gen_tips, dur_smooth, dur_drop, dur_filter, dur_finalize); + spdlog::info( + "Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms filterFloatingLines: {} ms " + "finalizeInterfaceAndSupportAreas {} ms", + dur_gen_tips, + dur_smooth, + dur_drop, + dur_filter, + dur_finalize); } } // namespace cura diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index ec9adffd1f..094a819544 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -1,53 +1,65 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + #include "TreeSupportTipGenerator.h" -#include "Application.h" //To get settings. -#include "infill/SierpinskiFillProvider.h" -#include "settings/EnumSettings.h" -#include "utils/algorithm.h" -#include "utils/Simplify.h" -#include "utils/math.h" //For round_up_divide and PI. -#include "utils/polygonUtils.h" //For moveInside. -#include "utils/ThreadPool.h" -#include "TreeSupportUtils.h" #include #include -#include #include #include + #include #include #include #include +#include +#include "Application.h" //To get settings. +#include "TreeSupportUtils.h" +#include "infill/SierpinskiFillProvider.h" +#include "settings/EnumSettings.h" +#include "utils/Simplify.h" +#include "utils/ThreadPool.h" +#include "utils/algorithm.h" +#include "utils/math.h" //For round_up_divide and PI. +#include "utils/polygonUtils.h" //For moveInside. namespace cura { -TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage, const SliceMeshStorage& mesh, TreeModelVolumes& volumes_s): - config(mesh.settings), - use_fake_roof(!mesh.settings.get("support_roof_enable")), - minimum_roof_area(!use_fake_roof? mesh.settings.get("minimum_roof_area"): SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA), - minimum_support_area(mesh.settings.get("minimum_support_area")), - support_roof_layers(mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), config.layer_height) : use_fake_roof ? SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS : 0), - connect_length((config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + std::max(2 * config.min_radius - 1.0 * config.support_line_width, 0.0)), - support_tree_branch_distance((config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length), - support_roof_line_distance( use_fake_roof ? (config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) * (config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) : mesh.settings.get("support_roof_line_distance")), //todo propper - support_outset(mesh.settings.get("support_offset")), - roof_outset(use_fake_roof ? support_outset: mesh.settings.get("support_roof_offset")), - force_tip_to_roof((config.min_radius * config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && support_roof_layers && !use_fake_roof), - support_tree_limit_branch_reach(mesh.settings.get("support_tree_limit_branch_reach")), - support_tree_branch_reach_limit(support_tree_limit_branch_reach ? mesh.settings.get("support_tree_branch_reach_limit") : 0), - z_distance_delta(std::min(config.z_distance_top_layers+1,mesh.overhang_areas.size())), - xy_overrides(config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z), - tip_roof_size(force_tip_to_roof?config.min_radius * config.min_radius * M_PI:0), - already_inserted(mesh.overhang_areas.size()), - support_roof_drawn(mesh.overhang_areas.size(),Polygons()), - roof_tips_drawn(mesh.overhang_areas.size(),Polygons()), - volumes_(volumes_s), - force_minimum_roof_area(use_fake_roof || SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT) +TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage, const SliceMeshStorage& mesh, TreeModelVolumes& volumes_s) + : config(mesh.settings) + , use_fake_roof(! mesh.settings.get("support_roof_enable")) + , minimum_support_area(mesh.settings.get("minimum_support_area")) + , minimum_roof_area(! use_fake_roof ? mesh.settings.get("minimum_roof_area") : std::max(SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA, minimum_support_area)) + , support_roof_layers( + mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), config.layer_height) + : use_fake_roof ? SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS + : 0) + , connect_length( + (config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + std::max(2 * config.min_radius - 1.0 * config.support_line_width, 0.0)) + , support_tree_branch_distance((config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length) + , support_roof_line_distance( + use_fake_roof ? (config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) + * (config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + : mesh.settings.get("support_roof_line_distance")) + , // todo propper + support_outset(0) + , // Since we disable support offset when tree support is enabled we use an offset of 0 rather than the setting value mesh.settings.get("support_offset") + roof_outset(use_fake_roof ? support_outset : mesh.settings.get("support_roof_offset")) + , force_tip_to_roof((config.min_radius * config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && support_roof_layers && ! use_fake_roof) + , support_tree_limit_branch_reach(mesh.settings.get("support_tree_limit_branch_reach")) + , support_tree_branch_reach_limit(support_tree_limit_branch_reach ? mesh.settings.get("support_tree_branch_reach_limit") : 0) + , z_distance_delta(std::min(config.z_distance_top_layers + 1, mesh.overhang_areas.size())) + , xy_overrides(config.support_overrides == SupportDistPriority::XY_OVERRIDES_Z) + , tip_roof_size(force_tip_to_roof ? config.min_radius * config.min_radius * M_PI : 0) + , already_inserted(mesh.overhang_areas.size()) + , support_roof_drawn(mesh.overhang_areas.size(), Polygons()) + , roof_tips_drawn(mesh.overhang_areas.size(), Polygons()) + , volumes_(volumes_s) + , force_minimum_roof_area(use_fake_roof || SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT) { - const double support_overhang_angle = mesh.settings.get("support_angle"); const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * config.layer_height) : std::numeric_limits::max(); @@ -58,9 +70,10 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage else { max_overhang_insert_lag = std::max((size_t)round_up_divide(config.xy_distance, max_overhang_speed / 2), 2 * config.z_distance_top_layers); - // ^^^ Cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. - // Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. - // The 2*z_distance_delta is only a catch for when the support angle is very high. + // ^^^ Cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and + // time cost. + // Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle + // would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high. } cross_fill_provider = generateCrossFillProvider(mesh, support_tree_branch_distance, config.support_line_width); @@ -76,9 +89,9 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage coord_t dtt_when_tips_can_merge = 1; - if(config.branch_radius * config.diameter_angle_scale_factor < 2 * config.maximum_move_distance_slow) + if (config.branch_radius * config.diameter_angle_scale_factor < 2 * config.maximum_move_distance_slow) { - while((2 * config.maximum_move_distance_slow * dtt_when_tips_can_merge - config.support_line_width) < config.getRadius(dtt_when_tips_can_merge)) + while ((2 * config.maximum_move_distance_slow * dtt_when_tips_can_merge - config.support_line_width) < config.getRadius(dtt_when_tips_can_merge)) { dtt_when_tips_can_merge++; } @@ -94,8 +107,10 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage std::vector TreeSupportTipGenerator::convertLinesToInternal(Polygons polylines, LayerIndex layer_idx) { // NOTE: The volumes below (on which '.inside(p, true)' is called each time below) are the same each time. The values being calculated here are strictly local as well. - // So they could in theory be pre-calculated here (outside of the loop). However, when I refatored it to be that way, it seemed to cause deadlocks each time for some settings. - // NOTE2: When refactoring ensure that avoidance to buildplate is only requested when support_rest_preference == RestPreference::BUILDPLATE as otherwise it has not been precalculated (causing long delays while it is calculated when requested here). + // So they could in theory be pre-calculated here (outside of the loop). However, when I refatored it to be that way, it seemed to cause deadlocks each time for some + // settings. + // NOTE2: When refactoring ensure that avoidance to buildplate is only requested when support_rest_preference == RestPreference::BUILDPLATE as otherwise it has not been + // precalculated (causing long delays while it is calculated when requested here). std::vector result; // Also checks if the position is valid, if it is NOT, it deletes that point @@ -104,23 +119,26 @@ std::vector TreeSupportTipGenerator::c LineInformation res_line; for (const Point& p : line) { - if (config.support_rest_preference == RestPreference::BUILDPLATE && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, false, !xy_overrides).inside(p, true)) + if (config.support_rest_preference == RestPreference::BUILDPLATE + && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, false, ! xy_overrides).inside(p, true)) { res_line.emplace_back(p, LineStatus::TO_BP_SAFE); } - else if (config.support_rest_preference == RestPreference::BUILDPLATE && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, false, !xy_overrides).inside(p, true)) + else if ( + config.support_rest_preference == RestPreference::BUILDPLATE + && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, false, ! xy_overrides).inside(p, true)) { res_line.emplace_back(p, LineStatus::TO_BP); } - else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, true, !xy_overrides).inside(p, true)) + else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST_SAFE, true, ! xy_overrides).inside(p, true)) { res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS_SAFE); } - else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, true, !xy_overrides).inside(p, true)) + else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::FAST, true, ! xy_overrides).inside(p, true)) { res_line.emplace_back(p, LineStatus::TO_MODEL_GRACIOUS); } - else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::COLLISION, true, !xy_overrides).inside(p, true)) + else if (config.support_rests_on_model && ! volumes_.getAvoidance(config.getRadius(0), layer_idx, AvoidanceType::COLLISION, true, ! xy_overrides).inside(p, true)) { res_line.emplace_back(p, LineStatus::TO_MODEL); } @@ -130,7 +148,7 @@ std::vector TreeSupportTipGenerator::c res_line.clear(); } } - if (!res_line.empty()) + if (! res_line.empty()) { result.emplace_back(res_line); res_line.clear(); @@ -157,10 +175,17 @@ Polygons TreeSupportTipGenerator::convertInternalToLines(std::vector)> TreeSupportTipGenerator::getEvaluatePointForNextLayerFunction(size_t current_layer) { - std::function)> evaluatePoint = - [=](std::pair p) + std::function)> evaluatePoint = [=](std::pair p) { - if (config.support_rest_preference != RestPreference::GRACEFUL && ! volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, false, !xy_overrides).inside(p.first, true)) + if (config.support_rest_preference != RestPreference::GRACEFUL + && ! volumes_ + .getAvoidance( + config.getRadius(0), + current_layer - 1, + p.second == LineStatus::TO_BP_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, + false, + ! xy_overrides) + .inside(p.first, true)) { return true; } @@ -168,11 +193,18 @@ std::function)> TreeS { if (p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE) { - return ! volumes_.getAvoidance(config.getRadius(0), current_layer - 1, p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, true, !xy_overrides).inside(p.first, true); + return ! volumes_ + .getAvoidance( + config.getRadius(0), + current_layer - 1, + p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE ? AvoidanceType::FAST_SAFE : AvoidanceType::FAST, + true, + ! xy_overrides) + .inside(p.first, true); } else { - return ! volumes_.getAvoidance(config.getRadius(0), current_layer - 1, AvoidanceType::COLLISION, true, !xy_overrides).inside(p.first, true); + return ! volumes_.getAvoidance(config.getRadius(0), current_layer - 1, AvoidanceType::COLLISION, true, ! xy_overrides).inside(p.first, true); } } return false; @@ -180,7 +212,9 @@ std::function)> TreeS return evaluatePoint; } -std::pair, std::vector> TreeSupportTipGenerator::splitLines(std::vector lines, std::function)> evaluatePoint) +std::pair, std::vector> TreeSupportTipGenerator::splitLines( + std::vector lines, + std::function)> evaluatePoint) { // Assumes all Points on the current line are valid. @@ -202,7 +236,7 @@ std::pair, std::vector, std::vector>>, std::vector>>>(keep, set_free); + return std::pair< + std::vector>>, + std::vector>>>(keep, set_free); } -Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points, bool enforce_distance) const +Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points, bool enforce_distance) const { Polygons result; for (auto part : input) @@ -239,9 +275,9 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& if (part.front() == part.back()) { size_t optimal_start_index = 0; - // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60 degree tend to be very thin areas, so lets get the beginning and end of them and ensure that they are supported. - // The first point of the line will always be supported, so rotate the order of points in this polyline that one of the two corresponding points that are furthest from each other is in the beginning. - // The other will be manually added (optimal_end_index) + // If the polyline was a polygon, there is a high chance it was an overhang. Overhangs that are <60 degree tend to be very thin areas, so lets get the beginning and + // end of them and ensure that they are supported. The first point of the line will always be supported, so rotate the order of points in this polyline that one of + // the two corresponding points that are furthest from each other is in the beginning. The other will be manually added (optimal_end_index) coord_t max_dist2_between_vertices = 0; for (auto [idx, p_a] : part | ranges::views::enumerate | ranges::views::drop_last(1)) { @@ -267,13 +303,14 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& line.add(part[0]); bool should_add_endpoint = min_points > 1 || vSize2(part[0] - part[optimal_end_index]) > (current_distance * current_distance); - bool added_endpoint = !should_add_endpoint; // If no endpoint should be added all endpoints are already added. + bool added_endpoint = ! should_add_endpoint; // If no endpoint should be added all endpoints are already added. size_t current_index = 0; GivenDistPoint next_point; coord_t next_distance = current_distance; // Get points so that at least min_points are added and they each are current_distance away from each other. If that is impossible, decrease current_distance a bit. - // (Regarding the while-loop) The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! + // (Regarding the while-loop) The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are + // on this line! while (PolygonUtils::getNextPointWithDistance(current_point, next_distance, part, current_index, 0, next_point) && next_point.pos < coord_t(part.size()) - 1) { if (! added_endpoint && next_point.pos >= optimal_end_index) @@ -289,14 +326,14 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& // So this ensures that the points are actually a certain distance from each other. // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. coord_t min_distance_to_existing_point_sqd = std::numeric_limits::max(); - if(enforce_distance) + if (enforce_distance) { for (Point p : line) { min_distance_to_existing_point_sqd = std::min(min_distance_to_existing_point_sqd, vSize2(p - next_point.location)); } } - if (!enforce_distance || min_distance_to_existing_point_sqd >= (current_distance * current_distance)) + if (! enforce_distance || min_distance_to_existing_point_sqd >= (current_distance * current_distance)) { // viable point was found. Add to possible result. line.add(next_point.location); @@ -309,7 +346,9 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& if (current_point == next_point.location) { // In case a fixpoint is encountered, better aggressively overcompensate so the code does not become stuck here... - spdlog::warn("Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently {}) is smaller than 100", next_distance); + spdlog::warn( + "Tree Support: Encountered a fixpoint in getNextPointWithDistance. This is expected to happen if the distance (currently {}) is smaller than 100", + next_distance); if (next_distance > 2 * current_distance) { // This case should never happen, but better safe than sorry. @@ -339,9 +378,8 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& } -SierpinskiFillProvider* TreeSupportTipGenerator::generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const +std::shared_ptr TreeSupportTipGenerator::generateCrossFillProvider(const SliceMeshStorage& mesh, coord_t line_distance, coord_t line_width) const { - if (config.support_pattern == EFillMethod::CROSS || config.support_pattern == EFillMethod::CROSS_3D) { AABB3D aabb; @@ -361,210 +399,238 @@ SierpinskiFillProvider* TreeSupportTipGenerator::generateCrossFillProvider(const std::ifstream cross_fs(cross_subdisivion_spec_image_file.c_str()); if (cross_subdisivion_spec_image_file != "" && cross_fs.good()) { - return new SierpinskiFillProvider(aabb, line_distance, line_width, cross_subdisivion_spec_image_file); - } - else - { - return new SierpinskiFillProvider(aabb, line_distance, line_width); + return std::make_shared(aabb, line_distance, line_width, cross_subdisivion_spec_image_file); } + return std::make_shared(aabb, line_distance, line_width); } return nullptr; } -void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, std::vector& result, bool roof ) +void TreeSupportTipGenerator::dropOverhangAreas(const SliceMeshStorage& mesh, std::vector& result, bool roof) { - std::mutex critical; - //this is ugly, but as far as i can see there is not way to ensure a parallel_for loop is calculated for each iteration up to a certain point before it continues; - cura::parallel_for - ( - 1, - mesh.overhang_areas.size() - z_distance_delta, - [&](const LayerIndex layer_idx) + // this is ugly, but as far as i can see there is not way to ensure a parallel_for loop is calculated for each iteration up to a certain point before it continues; + cura::parallel_for( + 1, + mesh.overhang_areas.size() - z_distance_delta, + [&](const LayerIndex layer_idx) + { + if (mesh.overhang_areas[layer_idx + z_distance_delta].empty() || result.size() < layer_idx) { - if (mesh.overhang_areas[layer_idx + z_distance_delta].empty() || result.size()= 1 && !remaining_overhang.empty(); lag_ctr++) + Polygons relevant_forbidden = volumes_.getCollision(roof ? 0 : config.getRadius(0), layer_idx, ! xy_overrides); + // ^^^ Take the least restrictive avoidance possible + + // Technically this also makes support blocker smaller, which is wrong as they do not have a xy_distance, but it should be good enough. + Polygons model_outline = volumes_.getCollision(0, layer_idx, ! xy_overrides).offset(-config.xy_min_distance, ClipperLib::jtRound); + + Polygons overhang_regular = TreeSupportUtils::safeOffsetInc( + mesh.overhang_areas[layer_idx + z_distance_delta], + roof ? roof_outset : support_outset, + relevant_forbidden, + config.min_radius * 1.75 + config.xy_min_distance, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + Polygons remaining_overhang = mesh.overhang_areas[layer_idx + z_distance_delta] + .offset(roof ? roof_outset : support_outset) + .difference(overhang_regular) + .intersection(relevant_forbidden) + .difference(model_outline); + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && layer_idx - coord_t(lag_ctr) >= 1 && ! remaining_overhang.empty(); lag_ctr++) + { { - { - std::lock_guard critical_section_storage(critical); - result[layer_idx-lag_ctr].add(remaining_overhang); - } - - Polygons relevant_forbidden_below = - volumes_.getCollision(roof ? 0 : config.getRadius(0), layer_idx - lag_ctr, ! xy_overrides).offset(EPSILON); - remaining_overhang=remaining_overhang.intersection(relevant_forbidden_below).unionPolygons().difference(model_outline); + std::lock_guard critical_section_storage(critical); + result[layer_idx - lag_ctr].add(remaining_overhang); } - } - ); - cura::parallel_for - ( - 0, - result.size(), - [&](const LayerIndex layer_idx) - { - result[layer_idx]=result[layer_idx].unionPolygons(); + Polygons relevant_forbidden_below = volumes_.getCollision(roof ? 0 : config.getRadius(0), layer_idx - lag_ctr, ! xy_overrides).offset(EPSILON); + remaining_overhang = remaining_overhang.intersection(relevant_forbidden_below).unionPolygons().difference(model_outline); } - ); - - + }); + cura::parallel_for( + 0, + result.size(), + [&](const LayerIndex layer_idx) + { + result[layer_idx] = result[layer_idx].unionPolygons(); + }); } void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& mesh) { - std::vector potential_support_roofs(mesh.overhang_areas.size(),Polygons()); + std::vector potential_support_roofs(mesh.overhang_areas.size(), Polygons()); std::mutex critical_potential_support_roofs; - std::vector dropped_overhangs(mesh.overhang_areas.size(),Polygons()); + std::vector dropped_overhangs(mesh.overhang_areas.size(), Polygons()); if (xy_overrides) { dropOverhangAreas(mesh, dropped_overhangs, true); } - cura::parallel_for - ( - 0, - mesh.overhang_areas.size() - z_distance_delta, - [&](const LayerIndex layer_idx) + cura::parallel_for( + 0, + mesh.overhang_areas.size() - z_distance_delta, + [&](const LayerIndex layer_idx) + { + if (mesh.overhang_areas[layer_idx + z_distance_delta].empty()) + { + return; // This is a continue if imagined in a loop context. + } + + // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with + // -radius. This is intentional here, as support roof is still valid if only a part of the tip may reach it. + Polygons forbidden_here = volumes_ + .getAvoidance( + config.getRadius(0), + layer_idx, + (only_gracious || ! config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config.support_rests_on_model, + ! xy_overrides) + .offset(-config.getRadius(0), ClipperLib::jtRound); + + // todo Since arachnea the assumption that an area smaller then line_width is not printed is no longer true all such safeOffset should have config.support_line_width + // replaced with another setting. It should still work in most cases, but it should be possible to create a situation where a overhang outset lags though a wall. I will + // take a look at this later. + Polygons full_overhang_area = TreeSupportUtils::safeOffsetInc( + mesh.full_overhang_areas[layer_idx + z_distance_delta].unionPolygons(dropped_overhangs[layer_idx]), + roof_outset, + forbidden_here, + config.support_line_width, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + + for (LayerIndex dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) { - if (mesh.overhang_areas[layer_idx + z_distance_delta].empty()) + const Polygons forbidden_next = volumes_ + .getAvoidance( + config.getRadius(0), + layer_idx - (dtt_roof + 1), + (only_gracious || ! config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config.support_rests_on_model, + ! xy_overrides) + .offset(-config.getRadius(0), ClipperLib::jtRound); + + full_overhang_area = full_overhang_area.difference(forbidden_next); + + if (force_minimum_roof_area) { - return; // This is a continue if imagined in a loop context. + full_overhang_area.removeSmallAreas(minimum_roof_area); } - // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with -radius. - // This is intentional here, as support roof is still valid if only a part of the tip may reach it. - Polygons forbidden_here = - volumes_.getAvoidance(config.getRadius(0), layer_idx, (only_gracious||!config.support_rests_on_model)? AvoidanceType::FAST : AvoidanceType::COLLISION, config.support_rests_on_model, ! xy_overrides).offset(-config.getRadius(0),ClipperLib::jtRound); - - // todo Since arachnea the assumption that an area smaller then line_width is not printed is no longer true all such safeOffset should have config.support_line_width replaced with another setting. It should still work in most cases, but it should be possible to create a situation where a overhang outset lags though a wall. - // I will take a look at this later. - Polygons full_overhang_area = TreeSupportUtils::safeOffsetInc(mesh.full_overhang_areas[layer_idx + z_distance_delta].unionPolygons(dropped_overhangs[layer_idx]),roof_outset,forbidden_here, config.support_line_width, 0, 1, config.support_line_distance / 2, &config.simplifier); - - for (LayerIndex dtt_roof = 0; dtt_roof < support_roof_layers && layer_idx - dtt_roof >= 1; dtt_roof++) + if (full_overhang_area.area() > EPSILON) { - const Polygons forbidden_next = - volumes_.getAvoidance(config.getRadius(0), layer_idx - (dtt_roof + 1), (only_gracious||!config.support_rests_on_model)? AvoidanceType::FAST : AvoidanceType::COLLISION, config.support_rests_on_model, ! xy_overrides).offset(-config.getRadius(0),ClipperLib::jtRound); - - full_overhang_area = full_overhang_area.difference(forbidden_next); - - if(force_minimum_roof_area) - { - full_overhang_area.removeSmallAreas(minimum_roof_area); - } - - if (full_overhang_area.area()>EPSILON) - { - std::lock_guard critical_section_potential_support_roofs(critical_potential_support_roofs); - potential_support_roofs[layer_idx-dtt_roof].add((full_overhang_area)); - } - else - { - break; - } + std::lock_guard critical_section_potential_support_roofs(critical_potential_support_roofs); + potential_support_roofs[layer_idx - dtt_roof].add((full_overhang_area)); + } + else + { + break; } - } - ); + }); - cura::parallel_for - ( + cura::parallel_for( 0, potential_support_roofs.size(), [&](const LayerIndex layer_idx) { - // Now, because the avoidance/collision was subtracted above, the overhang parts that are of xy distance were removed, so to merge areas that should have been one offset by xy_min_distance and then undo it. - // In a perfect world the offset here would be of a mode that makes sure that area.offset(config.xy_min_distance).unionPolygons().offset(-config.xy_min_distance) = area if there is only one polygon in said area. - // I have not encountered issues with using the default mitered here. Could be that i just have not encountered an issue with it yet though. - potential_support_roofs[layer_idx]=potential_support_roofs[layer_idx].unionPolygons().offset(config.xy_min_distance).unionPolygons().offset(-config.xy_min_distance).unionPolygons(potential_support_roofs[layer_idx]); - - } - ); + // Now, because the avoidance/collision was subtracted above, the overhang parts that are of xy distance were removed, so to merge areas that should have been one + // offset by xy_min_distance and then undo it. In a perfect world the offset here would be of a mode that makes sure that + // area.offset(config.xy_min_distance).unionPolygons().offset(-config.xy_min_distance) = area if there is only one polygon in said area. I have not encountered issues + // with using the default mitered here. Could be that i just have not encountered an issue with it yet though. + potential_support_roofs[layer_idx] = potential_support_roofs[layer_idx] + .unionPolygons() + .offset(config.xy_min_distance) + .unionPolygons() + .offset(-config.xy_min_distance) + .unionPolygons(potential_support_roofs[layer_idx]); + }); - std::vector additional_support_roofs(mesh.overhang_areas.size(),Polygons()); + std::vector additional_support_roofs(mesh.overhang_areas.size(), Polygons()); - cura::parallel_for - ( - 0, - potential_support_roofs.size(), - [&](const LayerIndex layer_idx) + cura::parallel_for( + 0, + potential_support_roofs.size(), + [&](const LayerIndex layer_idx) + { + if (! potential_support_roofs[layer_idx].empty()) { - if (!potential_support_roofs[layer_idx].empty()) + // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with + // -radius. This is intentional here, as support roof is still valid if only a part of the tip may reach it. + Polygons forbidden_here = volumes_ + .getAvoidance( + config.getRadius(0), + layer_idx, + (only_gracious || ! config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config.support_rests_on_model, + ! xy_overrides) + .offset(-(config.getRadius(0)), ClipperLib::jtRound); + + if (! force_minimum_roof_area) { - // Roof does not have a radius, so remove it using offset. Note that there is no 0 radius avoidance, and it would not be identical with the avoidance offset with -radius. - // This is intentional here, as support roof is still valid if only a part of the tip may reach it. - Polygons forbidden_here = - volumes_.getAvoidance(config.getRadius(0), layer_idx, (only_gracious||!config.support_rests_on_model)? AvoidanceType::FAST : AvoidanceType::COLLISION, config.support_rests_on_model, ! xy_overrides).offset(-(config.getRadius(0)),ClipperLib::jtRound); + Polygons fuzzy_area = Polygons(); - if (!force_minimum_roof_area) + // the roof will be combined with roof above and below, to see if a part of this roof may be part of a valid roof further up/down. + // This prevents the situation where a roof gets removed even tough its area would contribute to a (better) printable roof area further down. + for (const LayerIndex layer_offset : ranges::views::iota( + -LayerIndex{ std::min(layer_idx, LayerIndex{ support_roof_layers }) }, + LayerIndex{ std::min(LayerIndex{ potential_support_roofs.size() - layer_idx }, LayerIndex{ support_roof_layers + 1 }) })) { - Polygons fuzzy_area = Polygons(); + fuzzy_area.add(support_roof_drawn[layer_idx + layer_offset]); + fuzzy_area.add(potential_support_roofs[layer_idx + layer_offset]); + } + fuzzy_area = fuzzy_area.unionPolygons(); + fuzzy_area.removeSmallAreas(std::max(minimum_roof_area, tip_roof_size)); - // the roof will be combined with roof above and below, to see if a part of this roof may be part of a valid roof further up/down. - // This prevents the situation where a roof gets removed even tough its area would contribute to a (better) printable roof area further down. - for (const LayerIndex layer_offset : ranges::views::iota(-LayerIndex(std::min(size_t(layer_idx),support_roof_layers)), LayerIndex(std::min(size_t(potential_support_roofs.size()-layer_idx),support_roof_layers+1)))) + for (Polygons potential_roof : potential_support_roofs[layer_idx].difference(forbidden_here).splitIntoParts()) + { + if (! potential_roof.intersection(fuzzy_area).empty()) { - fuzzy_area.add(support_roof_drawn[layer_idx+layer_offset]); - fuzzy_area.add(potential_support_roofs[layer_idx+layer_offset]); + additional_support_roofs[layer_idx].add(potential_roof); } - fuzzy_area=fuzzy_area.unionPolygons(); - fuzzy_area.removeSmallAreas(std::max(minimum_roof_area,tip_roof_size)); - - for (Polygons potential_roof:potential_support_roofs[layer_idx].difference(forbidden_here).splitIntoParts()) - { - - if (!potential_roof.intersection(fuzzy_area).empty()) - { - additional_support_roofs[layer_idx].add(potential_roof); - } - - } - } - else - { - Polygons valid_roof = potential_support_roofs[layer_idx].difference(forbidden_here); - valid_roof.removeSmallAreas(std::max(minimum_roof_area,tip_roof_size)); - additional_support_roofs[layer_idx].add(valid_roof); } } + else + { + Polygons valid_roof = potential_support_roofs[layer_idx].difference(forbidden_here); + valid_roof.removeSmallAreas(std::max(minimum_roof_area, tip_roof_size)); + additional_support_roofs[layer_idx].add(valid_roof); + } } - ); - - cura::parallel_for - ( - 0, - additional_support_roofs.size(), - [&](const LayerIndex layer_idx) - { - support_roof_drawn[layer_idx] = support_roof_drawn[layer_idx].unionPolygons(additional_support_roofs[layer_idx]); - } - ); + }); + cura::parallel_for( + 0, + additional_support_roofs.size(), + [&](const LayerIndex layer_idx) + { + support_roof_drawn[layer_idx] = support_roof_drawn[layer_idx].unionPolygons(additional_support_roofs[layer_idx]); + }); } -void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation, std::vector additional_ovalization_targets) +void TreeSupportTipGenerator::addPointAsInfluenceArea( + std::vector>& move_bounds, + std::pair p, + size_t dtt, + LayerIndex insert_layer, + size_t dont_move_until, + bool roof, + bool skip_ovalisation, + std::vector additional_ovalization_targets) { const bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; const bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; const bool safe_radius = p.second == LineStatus::TO_BP_SAFE || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; - if (! config.support_rests_on_model && !to_bp) + if (! config.support_rests_on_model && ! to_bp) { spdlog::warn("Tried to add an invalid support point"); return; @@ -582,23 +648,20 @@ void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vectorarea = new Polygons(area); for (Point p : additional_ovalization_targets) @@ -612,9 +675,17 @@ void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until, bool connect_points) +void TreeSupportTipGenerator::addLinesAsInfluenceAreas( + std::vector>& move_bounds, + std::vector lines, + size_t roof_tip_layers, + LayerIndex insert_layer_idx, + bool supports_roof, + size_t dont_move_until, + bool connect_points) { - // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible. This is required as there is no guarantee that if support_roof_wall_count == 0 that a certain roof area will actually have lines. + // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible. This is required as there is no guarantee that if support_roof_wall_count == 0 that a + // certain roof area will actually have lines. size_t dtt_roof_tip = 0; if (config.support_roof_wall_count == 0) { @@ -628,11 +699,12 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector, std::vector> split = - splitLines(lines, getEvaluatePointForNextLayerFunction(insert_layer_idx - dtt_roof_tip)); // Keep all lines that are still valid on the next layer. + std::pair, std::vector> split + = splitLines(lines, getEvaluatePointForNextLayerFunction(insert_layer_idx - dtt_roof_tip)); // Keep all lines that are still valid on the next layer. for (LineInformation line : split.second) // Add all points that would not be valid. { @@ -680,8 +752,9 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector additional_ovalization_targets; @@ -696,305 +769,385 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, - dtt_roof_tip != 0 || supports_roof, disable_ovalization, - additional_ovalization_targets - ); + addPointAsInfluenceArea( + move_bounds, + point_data, + 0, + insert_layer_idx - dtt_roof_tip, + dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, + dtt_roof_tip != 0 || supports_roof, + disable_ovalization, + additional_ovalization_targets); } } } -void TreeSupportTipGenerator::removeUselessAddedPoints(std::vector>& move_bounds,SliceDataStorage& storage, std::vector& additional_support_areas) +void TreeSupportTipGenerator::removeUselessAddedPoints( + std::vector>& move_bounds, + SliceDataStorage& storage, + std::vector& additional_support_areas) { - - cura::parallel_for - ( - 0, - move_bounds.size(), - [&](const LayerIndex layer_idx) + cura::parallel_for( + 0, + move_bounds.size(), + [&](const LayerIndex layer_idx) + { + if (layer_idx + 1 < storage.support.supportLayers.size()) { + std::vector to_be_removed; + Polygons roof_on_layer_above = use_fake_roof ? support_roof_drawn[layer_idx + 1] + : storage.support.supportLayers[layer_idx + 1].support_roof.unionPolygons(additional_support_areas[layer_idx + 1]); + Polygons roof_on_layer + = use_fake_roof ? support_roof_drawn[layer_idx] : storage.support.supportLayers[layer_idx].support_roof.unionPolygons(additional_support_areas[layer_idx]); - if(layer_idx+1 < storage.support.supportLayers.size()) + for (TreeSupportElement* elem : move_bounds[layer_idx]) { - std::vector to_be_removed; - Polygons roof_on_layer_above = use_fake_roof ? support_roof_drawn[layer_idx+1] : storage.support.supportLayers[layer_idx+1].support_roof.unionPolygons(additional_support_areas[layer_idx+1]); - Polygons roof_on_layer = use_fake_roof ? support_roof_drawn[layer_idx] : storage.support.supportLayers[layer_idx].support_roof.unionPolygons(additional_support_areas[layer_idx]); - - for (TreeSupportElement* elem : move_bounds[layer_idx]) + if (roof_on_layer.inside(elem->result_on_layer)) // Remove branches that start inside of support interface + { + to_be_removed.emplace_back(elem); + } + else if (elem->supports_roof) { - if (roof_on_layer.inside(elem->result_on_layer)) // Remove branches that start inside of support interface + Point from = elem->result_on_layer; + PolygonUtils::moveInside(roof_on_layer_above, from); + // Remove branches should have interface above them, but dont. Should never happen. + if (roof_on_layer_above.empty() + || (! roof_on_layer_above.inside(elem->result_on_layer) + && vSize2(from - elem->result_on_layer) > config.getRadius(0) * config.getRadius(0) + FUDGE_LENGTH * FUDGE_LENGTH)) { to_be_removed.emplace_back(elem); + spdlog::warn("Removing already placed tip that should have roof above it?"); } - else if(elem->supports_roof) - { - Point from = elem->result_on_layer; - PolygonUtils::moveInside(roof_on_layer_above,from); - // Remove branches should have interface above them, but dont. Should never happen. - if (roof_on_layer_above.empty() || - (!roof_on_layer_above.inside(elem->result_on_layer) && vSize2(from-elem->result_on_layer)>config.getRadius(0)*config.getRadius(0) + FUDGE_LENGTH * FUDGE_LENGTH)) - { - to_be_removed.emplace_back(elem); - spdlog::warn("Removing already placed tip that should have roof above it?"); - } - } - } - - for (auto elem : to_be_removed) - { - move_bounds[layer_idx].erase(elem); - delete elem->area; - delete elem; } + } + for (auto elem : to_be_removed) + { + move_bounds[layer_idx].erase(elem); + delete elem->area; + delete elem; } } - ); + }); } -void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const SliceMeshStorage& mesh, std::vector>& move_bounds, std::vector& additional_support_areas, std::vector& placed_support_lines_support_areas) +void TreeSupportTipGenerator::generateTips( + SliceDataStorage& storage, + const SliceMeshStorage& mesh, + std::vector>& move_bounds, + std::vector& additional_support_areas, + std::vector& placed_support_lines_support_areas) { std::vector> new_tips(move_bounds.size()); - const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? config.min_radius / 2 : sqrt(square(config.min_radius) - square(config.min_radius - config.support_line_width / 2)); - // ^^^ As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. - // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. As a circle is round this length is identical for every axis as long as the 90� angle between both remains. + const coord_t circle_length_to_half_linewidth_change + = config.min_radius < config.support_line_width ? config.min_radius / 2 : sqrt(square(config.min_radius) - square(config.min_radius - config.support_line_width / 2)); + // ^^^ As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. This calculates how far one has to move on the x-axis so + // that y=r-support_line_width/2. + // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. As a circle is round this length is identical for every + // axis as long as the 90� angle between both remains. const coord_t extra_outset = std::max(coord_t(0), config.min_radius - config.support_line_width / 2) + (xy_overrides ? 0 : config.support_line_width / 2); - // ^^^ Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is better than not supporting it at all. + // ^^^ Extra support offset to compensate for larger tip radiis. Also outset a bit more when z overwrites xy, because supporting something with a part of a support line is + // better than not supporting it at all. if (support_roof_layers) { calculateRoofAreas(mesh); } - cura::parallel_for - ( - 1, - mesh.overhang_areas.size() - z_distance_delta, - [&](const LayerIndex layer_idx) + cura::parallel_for( + 1, + mesh.overhang_areas.size() - z_distance_delta, + [&](const LayerIndex layer_idx) + { + if (mesh.overhang_areas[layer_idx + z_distance_delta].empty() && (layer_idx + 1 >= support_roof_drawn.size() || support_roof_drawn[layer_idx + 1].empty())) { - if (mesh.overhang_areas[layer_idx + z_distance_delta].empty() && (layer_idx+1 >= support_roof_drawn.size() || support_roof_drawn[layer_idx+1].empty())) - { - return; // This is a continue if imagined in a loop context. - } - - Polygons relevant_forbidden = - volumes_.getAvoidance(config.getRadius(0), layer_idx, (only_gracious || !config.support_rests_on_model)? AvoidanceType::FAST : AvoidanceType::COLLISION , config.support_rests_on_model, ! xy_overrides); - // ^^^ Take the least restrictive avoidance possible - relevant_forbidden = relevant_forbidden.offset(EPSILON).unionPolygons(); // Prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. - - std::function generateLines = - [&](const Polygons& area, bool roof, LayerIndex layer_idx) - { - - coord_t upper_line_distance = support_supporting_branch_distance; - coord_t line_distance = std::max(roof ? support_roof_line_distance : support_tree_branch_distance, upper_line_distance ); - + return; // This is a continue if imagined in a loop context. + } - return TreeSupportUtils::generateSupportInfillLines(area, config, roof && !use_fake_roof, layer_idx, line_distance , cross_fill_provider, roof && !use_fake_roof, line_distance == upper_line_distance); - }; + Polygons relevant_forbidden = volumes_.getAvoidance( + config.getRadius(0), + layer_idx, + (only_gracious || ! config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config.support_rests_on_model, + ! xy_overrides); + // ^^^ Take the least restrictive avoidance possible + relevant_forbidden + = relevant_forbidden.offset(EPSILON) + .unionPolygons(); // Prevent rounding errors down the line, points placed directly on the line of the forbidden area may not be added otherwise. + + std::function generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) + { + coord_t upper_line_distance = support_supporting_branch_distance; + coord_t line_distance = std::max(roof ? support_roof_line_distance : support_tree_branch_distance, upper_line_distance); + + + return TreeSupportUtils::generateSupportInfillLines( + area, + config, + roof && ! use_fake_roof, + layer_idx, + line_distance, + cross_fill_provider, + roof && ! use_fake_roof, + line_distance == upper_line_distance); + }; - std::vector> overhang_processing; - // ^^^ Every overhang has saved if a roof should be generated for it. - // This can NOT be done in the for loop as an area may NOT have a roof even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and it would not have a roof if the overhang is offset by support roof horizontal expansion instead. - // (At least this is the current behavior of the regular support) + std::vector> overhang_processing; + // ^^^ Every overhang has saved if a roof should be generated for it. + // This can NOT be done in the for loop as an area may NOT have a roof even if it is larger than the minimum_roof_area when it is only larger because of the support + // horizontal expansion and it would not have a roof if the overhang is offset by support roof horizontal expansion instead. (At least this is the current behavior + // of the regular support) - Polygons core_overhang=mesh.overhang_areas[layer_idx + z_distance_delta]; + Polygons core_overhang = mesh.overhang_areas[layer_idx + z_distance_delta]; - if (support_roof_layers && layer_idx+1 < support_roof_drawn.size()) + if (support_roof_layers && layer_idx + 1 < support_roof_drawn.size()) + { + core_overhang = core_overhang.difference(support_roof_drawn[layer_idx]); + for (Polygons roof_part : support_roof_drawn[layer_idx + 1] + .difference(support_roof_drawn[layer_idx]) + .splitIntoParts(true)) // If there is a roof, the roof will be one layer above the tips. { - core_overhang = core_overhang.difference(support_roof_drawn[layer_idx]); - for (Polygons roof_part : support_roof_drawn[layer_idx+1].difference(support_roof_drawn[layer_idx]).splitIntoParts(true)) //If there is a roof, the roof will be one layer above the tips. - { - //^^^Technically one should also subtract the avoidance of radius 0 (similarly how calculated in calculateRoofArea), as there can be some rounding errors introduced since then. But this does not fully prevent some rounding errors either way, so just handle the error later. - overhang_processing.emplace_back(roof_part, true); - } + //^^^Technically one should also subtract the avoidance of radius 0 (similarly how calculated in calculateRoofArea), as there can be some rounding errors + // introduced since then. But this does not fully prevent some rounding errors either way, so just handle the error later. + overhang_processing.emplace_back(roof_part, true); } + } - Polygons overhang_regular = - TreeSupportUtils::safeOffsetInc(core_overhang, support_outset, relevant_forbidden, config.min_radius * 1.75 + config.xy_min_distance, 0, 1, config.support_line_distance / 2, &config.simplifier); - Polygons remaining_overhang = - core_overhang.offset(support_outset).difference(overhang_regular.offset(config.support_line_width * 0.5)).intersection(relevant_forbidden); - - - // Offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang - coord_t extra_total_offset_acc = 0; - - // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. - while (extra_total_offset_acc + config.support_line_width / 8 < extra_outset) //+mesh_config.support_line_width / 8 to avoid calculating very small (useless) offsets because of rounding errors. - { - coord_t offset_current_step = - extra_total_offset_acc + 2 * config.support_line_width > config.min_radius ? - std::min(config.support_line_width / 8, extra_outset - extra_total_offset_acc) : - std::min(circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); - extra_total_offset_acc += offset_current_step; - Polygons overhang_offset = TreeSupportUtils::safeOffsetInc(overhang_regular, 1.5 * extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), config.xy_min_distance + config.support_line_width, 0, 1, config.support_line_distance / 2, &config.simplifier); - remaining_overhang = remaining_overhang.difference(overhang_offset.unionPolygons(support_roof_drawn[layer_idx].offset(1.5 * extra_total_offset_acc))).unionPolygons(); //overhang_offset is combined with roof, as all area that has a roof, is already supported by said roof. - Polygons next_overhang = TreeSupportUtils::safeOffsetInc(remaining_overhang, extra_total_offset_acc, volumes_.getCollision(0, layer_idx, true), config.xy_min_distance + config.support_line_width, 0, 1, config.support_line_distance / 2, &config.simplifier); - overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); - } + Polygons overhang_regular = TreeSupportUtils::safeOffsetInc( + core_overhang, + support_outset, + relevant_forbidden, + config.min_radius * 1.75 + config.xy_min_distance, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + Polygons remaining_overhang + = core_overhang.offset(support_outset).difference(overhang_regular.offset(config.support_line_width * 0.5)).intersection(relevant_forbidden); + + + // Offset ensures that areas that could be supported by a part of a support line, are not considered unsupported overhang + coord_t extra_total_offset_acc = 0; + + // Offset the area to compensate for large tip radiis. Offset happens in multiple steps to ensure the tip is as close to the original overhang as possible. + while (extra_total_offset_acc + config.support_line_width / 8 + < extra_outset) //+mesh_config.support_line_width / 8 to avoid calculating very small (useless) offsets because of rounding errors. + { + coord_t offset_current_step = extra_total_offset_acc + 2 * config.support_line_width > config.min_radius + ? std::min(config.support_line_width / 8, extra_outset - extra_total_offset_acc) + : std::min(circle_length_to_half_linewidth_change, extra_outset - extra_total_offset_acc); + extra_total_offset_acc += offset_current_step; + Polygons overhang_offset = TreeSupportUtils::safeOffsetInc( + overhang_regular, + 1.5 * extra_total_offset_acc, + volumes_.getCollision(0, layer_idx, true), + config.xy_min_distance + config.support_line_width, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + remaining_overhang = remaining_overhang.difference(overhang_offset.unionPolygons(support_roof_drawn[layer_idx].offset(1.5 * extra_total_offset_acc))) + .unionPolygons(); // overhang_offset is combined with roof, as all area that has a roof, is already supported by said roof. + Polygons next_overhang = TreeSupportUtils::safeOffsetInc( + remaining_overhang, + extra_total_offset_acc, + volumes_.getCollision(0, layer_idx, true), + config.xy_min_distance + config.support_line_width, + 0, + 1, + config.support_line_distance / 2, + &config.simplifier); + overhang_regular = overhang_regular.unionPolygons(next_overhang.difference(relevant_forbidden)); + } - // If the xy distance overrides the z distance, some support needs to be inserted further down. - //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down - // (while adding them an infinite amount of layers down would technically be closer the setting description, it would not produce reasonable results. ) - if (xy_overrides) + // If the xy distance overrides the z distance, some support needs to be inserted further down. + //=> Analyze which support points do not fit on this layer and check if they will fit a few layers down + // (while adding them an infinite amount of layers down would technically be closer the setting description, it would not produce reasonable results. ) + if (xy_overrides) + { + for (Polygons& remaining_overhang_part : remaining_overhang.splitIntoParts(false)) { + if (remaining_overhang_part.area() <= MM2_2INT(minimum_support_area)) + { + continue; + } - for (Polygons& remaining_overhang_part:remaining_overhang.splitIntoParts(false)) + std::vector overhang_lines; + Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), config.min_radius, 1, false); + // ^^^ Support_line_width to form a line here as otherwise most will be unsupported. + // Technically this violates branch distance, but not only is this the only reasonable choice, + // but it ensures consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. + // Also it is assumed that the area that is valid a layer below is to small for support roof. + if (polylines.pointCount() <= 3) { + // Add the outer wall to ensure it is correct supported instead. + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length, 3, true); + } - std::vector overhang_lines; - Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), config.min_radius, 1, false); - // ^^^ Support_line_width to form a line here as otherwise most will be unsupported. - // Technically this violates branch distance, but not only is this the only reasonable choice, - // but it ensures consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. - // Also it is assumed that the area that is valid a layer below is to small for support roof. - if (polylines.pointCount() <= 3) + for (auto line : polylines) + { + LineInformation res_line; + for (Point p : line) { - // Add the outer wall to ensure it is correct supported instead. - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length, 3, true); + res_line.emplace_back(p, LineStatus::INVALID); } + overhang_lines.emplace_back(res_line); + } - for (auto line : polylines) + for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && ! overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) + { + // get least restricted avoidance for layer_idx-lag_ctr + Polygons relevant_forbidden_below = volumes_.getAvoidance( + config.getRadius(0), + layer_idx - lag_ctr, + (only_gracious || ! config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, + config.support_rests_on_model, + ! xy_overrides); + // It is not required to offset the forbidden area here as the points won't change: + // If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. + std::function)> evaluatePoint = [&](std::pair p) { - LineInformation res_line; - for (Point p : line) - { - res_line.emplace_back(p, LineStatus::INVALID); - } - overhang_lines.emplace_back(res_line); - } + return relevant_forbidden_below.inside(p.first, true); + }; - for (size_t lag_ctr = 1; lag_ctr <= max_overhang_insert_lag && !overhang_lines.empty() && layer_idx - coord_t(lag_ctr) >= 1; lag_ctr++) + if (support_roof_layers) { - // get least restricted avoidance for layer_idx-lag_ctr - Polygons relevant_forbidden_below = - volumes_.getAvoidance(config.getRadius(0), layer_idx - lag_ctr, (only_gracious||!config.support_rests_on_model) ? AvoidanceType::FAST : AvoidanceType::COLLISION, config.support_rests_on_model, ! xy_overrides); - // It is not required to offset the forbidden area here as the points won't change: - // If points here are not inside the forbidden area neither will they be later when placing these points, as these are the same points. - std::function)> evaluatePoint = - [&](std::pair p) { return relevant_forbidden_below.inside(p.first, true); }; - - if (support_roof_layers) + // Remove all points that are for some reason part of a roof area, as the point is already supported by roof + std::function)> evaluatePartOfRoof = [&](std::pair p) { - //Remove all points that are for some reason part of a roof area, as the point is already supported by roof - std::function)> evaluatePartOfRoof = - [&](std::pair p) { return support_roof_drawn[layer_idx-lag_ctr].inside(p.first, true); }; - - overhang_lines = splitLines(overhang_lines, evaluatePartOfRoof).second; - } - std::pair, std::vector> split = splitLines(overhang_lines, evaluatePoint); // Keep all lines that are invalid. - overhang_lines = split.first; - std::vector fresh_valid_points = convertLinesToInternal(convertInternalToLines(split.second), layer_idx - lag_ctr); - // ^^^ Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + return support_roof_drawn[layer_idx - lag_ctr].inside(p.first, true); + }; - addLinesAsInfluenceAreas(new_tips,fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, support_roof_layers, false); + overhang_lines = splitLines(overhang_lines, evaluatePartOfRoof).second; } + std::pair, std::vector> split + = splitLines(overhang_lines, evaluatePoint); // Keep all lines that are invalid. + overhang_lines = split.first; + std::vector fresh_valid_points = convertLinesToInternal(convertInternalToLines(split.second), layer_idx - lag_ctr); + // ^^^ Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. + + addLinesAsInfluenceAreas( + new_tips, + fresh_valid_points, + (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, + layer_idx - lag_ctr, + false, + support_roof_layers, + false); } } + } + + overhang_regular.removeSmallAreas(minimum_support_area); - overhang_regular.removeSmallAreas(minimum_support_area); + for (Polygons support_part : overhang_regular.splitIntoParts(true)) + { + overhang_processing.emplace_back(support_part, false); + } - for (Polygons support_part : overhang_regular.splitIntoParts(true)) + for (std::pair overhang_pair : overhang_processing) + { + const bool roof_allowed_for_this_part = overhang_pair.second; + Polygons overhang_outset = overhang_pair.first; + const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(EPSILON), overhang_outset.polygonLength() / connect_length)); + std::vector overhang_lines; + + bool only_lines = true; + + // The tip positions are determined here. + // todo can cause inconsistent support density if a line exactly aligns with the model + Polygons polylines = ensureMaximumDistancePolyline( + generateLines(overhang_outset, roof_allowed_for_this_part, layer_idx + roof_allowed_for_this_part), + ! roof_allowed_for_this_part ? config.min_radius * 2 + : use_fake_roof ? support_supporting_branch_distance + : connect_length, + 1, + false); + + + // support_line_width to form a line here as otherwise most will be unsupported. + // Technically this violates branch distance, but not only is this the only reasonable choice, + // but it ensures consistent behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. + // This is not done when a roof is above as the roof will support the model and the trees only need to support the roof + + if (polylines.pointCount() <= min_support_points) { - overhang_processing.emplace_back(support_part, false); + only_lines = false; + // Add the outer wall (of the overhang) to ensure it is correct supported instead. + // Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width. + Polygons reduced_overhang_outset = overhang_outset.offset(-config.support_line_width / 2.2); + // ^^^ It's assumed that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area + // generated from them will support the overhang. + // (If this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, as some support is + // better than none.) + if (! reduced_overhang_outset.empty() + && overhang_outset.difference(reduced_overhang_outset.offset(std::max(config.support_line_width, connect_length))).area() < 1) + { + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length, min_support_points, true); + } + else + { + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length, min_support_points, true); + } } - for (std::pair overhang_pair : overhang_processing) + if (roof_allowed_for_this_part) // Some roof may only be supported by a part of a tip { - const bool roof_allowed_for_this_part = overhang_pair.second; - Polygons overhang_outset = overhang_pair.first; - const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(EPSILON), overhang_outset.polygonLength() / connect_length)); - std::vector overhang_lines; - - bool only_lines = true; - - // The tip positions are determined here. - // todo can cause inconsistent support density if a line exactly aligns with the model - Polygons polylines = - ensureMaximumDistancePolyline - ( - generateLines(overhang_outset, roof_allowed_for_this_part, layer_idx + roof_allowed_for_this_part), !roof_allowed_for_this_part ? config.min_radius * 2 : use_fake_roof ? support_supporting_branch_distance : connect_length , 1, false - ); - + polylines = TreeSupportUtils::movePointsOutside(polylines, relevant_forbidden, config.getRadius(0) + FUDGE_LENGTH / 2); + } - // support_line_width to form a line here as otherwise most will be unsupported. - // Technically this violates branch distance, but not only is this the only reasonable choice, - // but it ensures consistent behaviour as some infill patterns generate each line segment as its own polyline part causing a similar line forming behaviour. - // This is not done when a roof is above as the roof will support the model and the trees only need to support the roof + overhang_lines = convertLinesToInternal(polylines, layer_idx); - if (polylines.pointCount() <= min_support_points) - { - only_lines = false; - // Add the outer wall (of the overhang) to ensure it is correct supported instead. - // Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width. - Polygons reduced_overhang_outset = overhang_outset.offset(-config.support_line_width / 2.2); - // ^^^ It's assumed that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them will support the overhang. - // (If this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, as some support is better than none.) - if (! reduced_overhang_outset.empty() && overhang_outset.difference(reduced_overhang_outset.offset(std::max(config.support_line_width, connect_length))).area() < 1) - { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length, min_support_points, true); - } - else - { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length, min_support_points, true); - } - } + if (overhang_lines.empty()) // some error handling and logging + { + Polygons enlarged_overhang_outset = overhang_outset.offset(config.getRadius(0) + FUDGE_LENGTH / 2, ClipperLib::jtRound).difference(relevant_forbidden); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length, min_support_points, true); + overhang_lines = convertLinesToInternal(polylines, layer_idx); - if (roof_allowed_for_this_part) //Some roof may only be supported by a part of a tip + if (! overhang_lines.empty()) { - polylines = TreeSupportUtils::movePointsOutside(polylines,relevant_forbidden,config.getRadius(0)+FUDGE_LENGTH/2); + spdlog::debug("Compensated for overhang area that had no valid tips. Now has a tip."); } - - overhang_lines = convertLinesToInternal(polylines, layer_idx ); - - if(overhang_lines.empty()) //some error handling and logging + else { - Polygons enlarged_overhang_outset = overhang_outset.offset(config.getRadius(0)+FUDGE_LENGTH/2,ClipperLib::jtRound).difference(relevant_forbidden); - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length, min_support_points, true); - overhang_lines = convertLinesToInternal(polylines, layer_idx ); - - if(!overhang_lines.empty()) - { - spdlog::debug("Compensated for overhang area that had no valid tips. Now has a tip."); - } - else - { - spdlog::warn("Overhang area has no valid tips! Was roof: {} On Layer: {}", roof_allowed_for_this_part,layer_idx); - } + spdlog::warn("Overhang area has no valid tips! Was roof: {} On Layer: {}", roof_allowed_for_this_part, layer_idx); } - - size_t dont_move_for_layers = support_roof_layers ? (force_tip_to_roof ? support_roof_layers : (roof_allowed_for_this_part ? 0 : support_roof_layers)) : 0; - addLinesAsInfluenceAreas(new_tips, overhang_lines, force_tip_to_roof ? support_roof_layers : 0, layer_idx, roof_allowed_for_this_part, dont_move_for_layers, only_lines); } + + size_t dont_move_for_layers = support_roof_layers ? (force_tip_to_roof ? support_roof_layers : (roof_allowed_for_this_part ? 0 : support_roof_layers)) : 0; + addLinesAsInfluenceAreas( + new_tips, + overhang_lines, + force_tip_to_roof ? support_roof_layers : 0, + layer_idx, + roof_allowed_for_this_part, + dont_move_for_layers, + only_lines); } - ); + }); - cura::parallel_for - ( + cura::parallel_for( 0, support_roof_drawn.size(), [&](const LayerIndex layer_idx) { // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough. - // If there is a roof could have zero lines in its area (as it has no wall), rand a support area would very likely be printed (because there are walls for the support areas), replace non printable roofs with support + // If there is a roof could have zero lines in its area (as it has no wall), rand a support area would very likely be printed (because there are walls for the support + // areas), replace non printable roofs with support if (! use_fake_roof && config.support_wall_count > 0 && config.support_roof_wall_count == 0) { for (auto roof_area : support_roof_drawn[layer_idx].unionPolygons(roof_tips_drawn[layer_idx]).splitIntoParts()) { - // technically there is no guarantee that a drawn roof tip has lines, as it could be unioned with another roof area that has, but this has to be enough hopefully. - if (layer_idx < additional_support_areas.size() && TreeSupportUtils::generateSupportInfillLines(roof_area, config, true, layer_idx, support_roof_line_distance, cross_fill_provider, false).empty()) + // technically there is no guarantee that a drawn roof tip has lines, as it could be unioned with another roof area that has, but this has to be enough + // hopefully. + if (layer_idx < additional_support_areas.size() + && TreeSupportUtils::generateSupportInfillLines(roof_area, config, true, layer_idx, support_roof_line_distance, cross_fill_provider, false).empty()) { additional_support_areas[layer_idx].add(roof_area); } @@ -1011,12 +1164,17 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice { if (use_fake_roof) { - for (auto part : support_roof_drawn[layer_idx].splitIntoParts()) - { - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, 0, support_roof_line_distance); - } - placed_support_lines_support_areas[layer_idx].add( - TreeSupportUtils::generateSupportInfillLines(support_roof_drawn[layer_idx], config, false, layer_idx, support_roof_line_distance, cross_fill_provider, false).offsetPolyLine(config.support_line_width / 2)); + storage.support.supportLayers[layer_idx] + .fillInfillParts(layer_idx, support_roof_drawn, config.support_line_width, support_roof_line_distance, config.maximum_move_distance); + placed_support_lines_support_areas[layer_idx].add(TreeSupportUtils::generateSupportInfillLines( + support_roof_drawn[layer_idx], + config, + false, + layer_idx, + support_roof_line_distance, + cross_fill_provider, + false) + .offsetPolyLine(config.support_line_width / 2)); } else { @@ -1026,7 +1184,19 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice } }); - removeUselessAddedPoints(new_tips ,storage , additional_support_areas); + cura::parallel_for( + 1, + mesh.overhang_areas.size() - z_distance_delta, + [&](const LayerIndex layer_idx) + { + if (layer_idx > 0) + { + storage.support.supportLayers[layer_idx].support_fractional_roof.add( + storage.support.supportLayers[layer_idx].support_roof.difference(storage.support.supportLayers[layer_idx + 1].support_roof)); + } + }); + + removeUselessAddedPoints(new_tips, storage, additional_support_areas); for (auto [layer_idx, tips_on_layer] : new_tips | ranges::views::enumerate) { @@ -1035,6 +1205,4 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice } - -}// namespace cura - +} // namespace cura diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index 74b4cf502e..b140b25b4e 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -1,28 +1,35 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include //For std::partition_copy and std::min_element. -#include - -#include -#include - #include "WallToolPaths.h" +#include "ExtruderTrain.h" #include "SkeletalTrapezoidation.h" +#include "utils/PolylineStitcher.h" +#include "utils/Simplify.h" #include "utils/SparsePointGrid.h" //To stitch the inner contour. +#include "utils/actions/smooth.h" #include "utils/polygonUtils.h" -#include "ExtruderTrain.h" -#include "utils/PolylineStitcher.h" -#include "utils/BoundedAreaHierarchy.h" -#include "utils/Simplify.h" +#include +#include +#include +#include + +#include //For std::partition_copy and std::min_element. +#include namespace cura { -WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t nominal_bead_width, const size_t inset_count, const coord_t wall_0_inset, - const Settings& settings, const int layer_idx, SectionType section_type) +WallToolPaths::WallToolPaths( + const Polygons& outline, + const coord_t nominal_bead_width, + const size_t inset_count, + const coord_t wall_0_inset, + const Settings& settings, + const int layer_idx, + SectionType section_type) : outline(outline) , bead_width_0(nominal_bead_width) , bead_width_x(nominal_bead_width) @@ -39,8 +46,15 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t nominal_bead { } -WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x, - const size_t inset_count, const coord_t wall_0_inset, const Settings& settings, const int layer_idx, SectionType section_type ) +WallToolPaths::WallToolPaths( + const Polygons& outline, + const coord_t bead_width_0, + const coord_t bead_width_x, + const size_t inset_count, + const coord_t wall_0_inset, + const Settings& settings, + const int layer_idx, + SectionType section_type) : outline(outline) , bead_width_0(bead_width_0) , bead_width_x(bead_width_x) @@ -53,7 +67,7 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0 , toolpaths_generated(false) , settings(settings) , layer_idx(layer_idx) - , section_type( section_type ) + , section_type(section_type) { } @@ -64,42 +78,36 @@ const std::vector& WallToolPaths::generate() // with half the minimum printable feature size or minimum line width, these slivers are removed, while still // keeping enough information to not degrade the print quality; // These features can't be printed anyhow. See PR CuraEngine#1811 for some screenshots - const coord_t close_distance = settings.get("fill_outline_gaps") ? settings.get("min_feature_size") / 2 - 3 : settings.get("min_wall_line_width") / 2 - 3; - const coord_t open_distance = close_distance; - const AngleRadians transitioning_angle = settings.get("wall_transition_angle"); + const coord_t open_close_distance + = settings.get("fill_outline_gaps") ? settings.get("min_feature_size") / 2 - 5 : settings.get("min_wall_line_width") / 2 - 5; + const coord_t epsilon_offset = (allowed_distance / 2) - 1; + const auto transitioning_angle = settings.get("wall_transition_angle"); constexpr coord_t discretization_step_size = MM2INT(0.8); + // Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed: + // TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons? + Polygons prepared_outline = outline.offset(-open_close_distance).offset(open_close_distance * 2).offset(-open_close_distance); + scripta::log("prepared_outline_0", prepared_outline, section_type, layer_idx); + prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); + prepared_outline = Simplify(settings).polygon(prepared_outline); + if (settings.get("meshfix_fluid_motion_enabled") && section_type != SectionType::SUPPORT) { - AABB aabb; - aabb.include(outline); - aabb.expand(1000); - SVG svg("output.svg", aabb); - svg.writePolygons(outline); - } - - // Simplify outline for boost::voronoi consumption. Absolutely no - // - self intersections, - // - near-self intersections or - // - collinear points - // allowed: - cura::Polygons prepared_outline = outline.offset(open_distance); - -// cura::Polygons prepared_outline = outline -// .offset(-close_distance) -// .offset(open_distance + close_distance) -// .offset(-open_distance); -// prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); -// prepared_outline.removeSmallEdges(10); -// prepared_outline.removeCollinearPoints(cura::AngleRadians(0.01)); - - { - AABB aabb; - aabb.include(prepared_outline); - aabb.expand(1000); - SVG svg("output2.svg", aabb); - svg.writePolygons(prepared_outline); + // No need to smooth support walls + auto smoother = actions::smooth(settings); + for (auto& polygon : prepared_outline) + { + polygon = smoother(polygon); + } } + PolygonUtils::fixSelfIntersections(epsilon_offset, prepared_outline); + prepared_outline.removeDegenerateVerts(); + prepared_outline.removeColinearEdges(AngleRadians(0.005)); + // Removing collinear edges may introduce self intersections, so we need to fix them again + PolygonUtils::fixSelfIntersections(epsilon_offset, prepared_outline); + prepared_outline.removeDegenerateVerts(); + prepared_outline = prepared_outline.unionPolygons(); + prepared_outline = Simplify(settings).polygon(prepared_outline); if (prepared_outline.area() <= 0) { @@ -121,25 +129,22 @@ const std::vector& WallToolPaths::generate() const int wall_distribution_count = settings.get("wall_distribution_count"); const size_t max_bead_count = (inset_count < std::numeric_limits::max() / 2) ? 2 * inset_count : std::numeric_limits::max(); - const auto beading_strat = BeadingStrategyFactory::makeStrategy - ( - bead_width_0, - bead_width_x, - wall_transition_length, - transitioning_angle, - print_thin_walls, - min_bead_width, - min_feature_size, - wall_split_middle_threshold, - wall_add_middle_threshold, - max_bead_count, - wall_0_inset, - wall_distribution_count - ); - const coord_t transition_filter_dist = settings.get("wall_transition_filter_distance"); - const coord_t allowed_filter_deviation = settings.get("wall_transition_filter_deviation"); - SkeletalTrapezoidation wall_maker - ( + const auto beading_strat = BeadingStrategyFactory::makeStrategy( + bead_width_0, + bead_width_x, + wall_transition_length, + transitioning_angle, + print_thin_walls, + min_bead_width, + min_feature_size, + wall_split_middle_threshold, + wall_add_middle_threshold, + max_bead_count, + wall_0_inset, + wall_distribution_count); + const auto transition_filter_dist = settings.get("wall_transition_filter_distance"); + const auto allowed_filter_deviation = settings.get("wall_transition_filter_deviation"); + SkeletalTrapezoidation wall_maker( prepared_outline, *beading_strat, beading_strat->getTransitioningAngle(), @@ -148,73 +153,101 @@ const std::vector& WallToolPaths::generate() allowed_filter_deviation, wall_transition_length, layer_idx, - section_type - ); + section_type); wall_maker.generateToolpaths(toolpaths); - scripta::log("toolpaths_0", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "toolpaths_0", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); stitchToolPaths(toolpaths, settings); - scripta::log("toolpaths_1", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); - + scripta::log( + "toolpaths_1", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); + removeSmallLines(toolpaths); - scripta::log("toolpaths_2", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "toolpaths_2", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); simplifyToolPaths(toolpaths, settings); - scripta::log("toolpaths_3", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "toolpaths_3", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); separateOutInnerContour(); removeEmptyToolPaths(toolpaths); - scripta::log("toolpaths_4", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); - assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(), - [](const VariableWidthLines& l, const VariableWidthLines& r) - { - return l.front().inset_idx < r.front().inset_idx; - }) && "WallToolPaths should be sorted from the outer 0th to inner_walls"); + scripta::log( + "toolpaths_4", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); + assert( + std::is_sorted( + toolpaths.cbegin(), + toolpaths.cend(), + [](const VariableWidthLines& l, const VariableWidthLines& r) + { + return l.front().inset_idx < r.front().inset_idx; + }) + && "WallToolPaths should be sorted from the outer 0th to inner_walls"); toolpaths_generated = true; - scripta::log("toolpaths_5", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "toolpaths_5", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); return toolpaths; } void WallToolPaths::stitchToolPaths(std::vector& toolpaths, const Settings& settings) { - const coord_t stitch_distance = settings.get("wall_line_width_x") - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width. + const coord_t stitch_distance + = settings.get("wall_line_width_x") - 1; // In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width. for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) { VariableWidthLines& wall_lines = toolpaths[wall_idx]; - + VariableWidthLines stitched_polylines; VariableWidthLines closed_polygons; PolylineStitcher::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance); @@ -265,21 +298,30 @@ void WallToolPaths::simplifyToolPaths(std::vector& toolpaths const Simplify simplifier(settings); for (auto& toolpath : toolpaths) { - for (auto& line : toolpath) - { - line = line.is_closed ? simplifier.polygon(line) : simplifier.polyline(line); - - if (line.is_closed && line.size() >= 2 && line.front() != line.back()) - { - line.emplace_back(line.front()); - } - } + toolpath = toolpath + | ranges::views::transform( + [&simplifier](auto& line) + { + auto line_ = line.is_closed ? simplifier.polygon(line) : simplifier.polyline(line); + + if (line_.is_closed && line_.size() >= 2 && line_.front() != line_.back()) + { + line_.emplace_back(line_.front()); + } + return line_; + }) + | ranges::views::filter( + [](const auto& line) + { + return ! line.empty(); + }) + | ranges::to_vector; } } const std::vector& WallToolPaths::getToolPaths() { - if (!toolpaths_generated) + if (! toolpaths_generated) { return generate(); } @@ -297,9 +339,9 @@ void WallToolPaths::pushToolPaths(std::vector& paths) void WallToolPaths::separateOutInnerContour() { - //We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. + // We'll remove all 0-width paths from the original toolpaths and store them separately as polygons. std::vector actual_toolpaths; - actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude. + actual_toolpaths.reserve(toolpaths.size()); // A bit too much, but the correct order of magnitude. std::vector contour_paths; contour_paths.reserve(toolpaths.size() / inset_count); inner_contour.clear(); @@ -325,8 +367,8 @@ void WallToolPaths::separateOutInnerContour() break; } } - - + + if (is_contour) { #ifdef DEBUG @@ -357,28 +399,28 @@ void WallToolPaths::separateOutInnerContour() } if (! actual_toolpaths.empty()) { - toolpaths = std::move(actual_toolpaths); //Filtered out the 0-width paths. + toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths. } else { toolpaths.clear(); } - //The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines. - //They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative. - //To get a correct shape, we need to make the outside contour positive and any holes inside negative. - //This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. - //The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. + // The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines. + // They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative. + // To get a correct shape, we need to make the outside contour positive and any holes inside negative. + // This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon. + // The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation. inner_contour = inner_contour.processEvenOdd(); } const Polygons& WallToolPaths::getInnerContour() { - if (!toolpaths_generated && inset_count > 0) + if (! toolpaths_generated && inset_count > 0) { generate(); } - else if(inset_count == 0) + else if (inset_count == 0) { return outline; } @@ -387,10 +429,15 @@ const Polygons& WallToolPaths::getInnerContour() bool WallToolPaths::removeEmptyToolPaths(std::vector& toolpaths) { - toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines) - { - return lines.empty(); - }), toolpaths.end()); + toolpaths.erase( + std::remove_if( + toolpaths.begin(), + toolpaths.end(), + [](const VariableWidthLines& lines) + { + return lines.empty(); + }), + toolpaths.end()); return toolpaths.empty(); } diff --git a/src/WallsComputation.cpp b/src/WallsComputation.cpp index b08e74d841..635a9e37c7 100644 --- a/src/WallsComputation.cpp +++ b/src/WallsComputation.cpp @@ -75,6 +75,7 @@ void WallsComputation::generateWalls(SliceLayerPart* part, SectionType section_t part->wall_toolpaths = wall_tool_paths.getToolPaths(); part->inner_area = wall_tool_paths.getInnerContour(); } + part->outline = PolygonsPart { Simplify(settings).polygon(part->outline) }; part->print_outline = part->outline; } diff --git a/src/bridge.cpp b/src/bridge.cpp index f6f4b41f04..75e42889e2 100644 --- a/src/bridge.cpp +++ b/src/bridge.cpp @@ -1,22 +1,30 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2018 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "bridge.h" -#include "sliceDataStorage.h" + #include "settings/types/Ratio.h" +#include "sliceDataStorage.h" #include "utils/AABB.h" #include "utils/polygon.h" namespace cura { -int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const SliceDataStorage& storage, const unsigned layer_nr, const unsigned bridge_layer, const SupportLayer* support_layer, Polygons& supported_regions) +int bridgeAngle( + const Settings& settings, + const Polygons& skin_outline, + const SliceDataStorage& storage, + const unsigned layer_nr, + const unsigned bridge_layer, + const SupportLayer* support_layer, + Polygons& supported_regions) { assert(! skin_outline.empty()); AABB boundary_box(skin_outline); - //To detect if we have a bridge, first calculate the intersection of the current layer with the previous layer. - // This gives us the islands that the layer rests on. + // To detect if we have a bridge, first calculate the intersection of the current layer with the previous layer. + // This gives us the islands that the layer rests on. Polygons islands; Polygons prev_layer_outline; // we also want the complete outline of the previous layer @@ -24,8 +32,9 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl const Ratio sparse_infill_max_density = settings.get("bridge_sparse_infill_max_density"); // include parts from all meshes - for (const SliceMeshStorage& mesh : storage.meshes) + for (const std::shared_ptr& mesh_ptr : storage.meshes) { + const auto& mesh = *mesh_ptr; if (mesh.isPrinted()) { const coord_t infill_line_distance = mesh.settings.get("infill_line_distance"); @@ -41,7 +50,7 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl } prev_layer_outline.add(solid_below); // not intersected with skin - if (!boundary_box.hit(prev_layer_part.boundaryBox)) + if (! boundary_box.hit(prev_layer_part.boundaryBox)) continue; islands.add(skin_outline.intersection(solid_below)); @@ -58,7 +67,7 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl // the model on one side but the remainder of the skin is above support would look like // a bridge because it would have two islands) - FIXME more work required here? - if (!support_layer->support_roof.empty()) + if (! support_layer->support_roof.empty()) { AABB support_roof_bb(support_layer->support_roof); if (boundary_box.hit(support_roof_bb)) @@ -66,7 +75,7 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl prev_layer_outline.add(support_layer->support_roof); // not intersected with skin Polygons supported_skin(skin_outline.intersection(support_layer->support_roof)); - if (!supported_skin.empty()) + if (! supported_skin.empty()) { supported_regions.add(supported_skin); } @@ -82,7 +91,7 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl prev_layer_outline.add(support_part.getInfillArea()); // not intersected with skin Polygons supported_skin(skin_outline.intersection(support_part.getInfillArea())); - if (!supported_skin.empty()) + if (! supported_skin.empty()) { supported_regions.add(supported_skin); } @@ -114,7 +123,8 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl Polygons skin_perimeter_lines; for (ConstPolygonRef poly : skin_outline) { - if (poly.empty()) continue; + if (poly.empty()) + continue; skin_perimeter_lines.add(poly); skin_perimeter_lines.back().emplace_back(poly.front()); } @@ -156,15 +166,15 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl return -1; } - //Next find the 2 largest islands that we rest on. + // Next find the 2 largest islands that we rest on. double area1 = 0; double area2 = 0; int idx1 = -1; int idx2 = -1; - for(unsigned int n=0; n area1) @@ -183,15 +193,14 @@ int bridgeAngle(const Settings& settings, const Polygons& skin_outline, const Sl idx2 = n; } } - + if (idx1 < 0 || idx2 < 0) return -1; - + Point center1 = islands[idx1].centerOfMass(); Point center2 = islands[idx2].centerOfMass(); return angle(center2 - center1); } -}//namespace cura - +} // namespace cura diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 30786e2cce..44cc58cbe2 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -1,27 +1,31 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher #ifdef ARCUS -#include //The socket to communicate to. -#include //To sleep while waiting for the connection. -#include //To map settings to their extruder numbers for limit_to_extruder. - -#include +#include "communication/ArcusCommunication.h" #include "Application.h" //To get and set the current slice command. #include "ExtruderTrain.h" #include "FffProcessor.h" //To start a slice. #include "PrintFeature.h" #include "Slice.h" //To process slices. -#include "communication/ArcusCommunication.h" #include "communication/ArcusCommunicationPrivate.h" //Our PIMPL. #include "communication/Listener.h" //To listen to the Arcus socket. #include "communication/SliceDataStruct.h" //To store sliced layer data. +#include "plugins/slots.h" #include "settings/types/LayerIndex.h" //To point to layers. #include "settings/types/Velocity.h" //To send to layer view how fast stuff is printing. +#include "utils/channel.h" #include "utils/polygon.h" +#include //The socket to communicate to. +#include +#include + +#include //To sleep while waiting for the connection. +#include //To map settings to their extruder numbers for limit_to_extruder. + namespace cura { @@ -47,7 +51,8 @@ class ArcusCommunication::PathCompiler std::vector line_widths; //!< Line widths for the line segments stored, the size of this vector is N. std::vector line_thicknesses; //!< Line thicknesses for the line segments stored, the size of this vector is N. std::vector line_velocities; //!< Line feedrates for the line segments stored, the size of this vector is N. - std::vector points; //!< The points used to define the line segments, the size of this vector is D*(N+1) as each line segment is defined from one point to the next. D is the dimensionality of the point. + std::vector points; //!< The points used to define the line segments, the size of this vector is D*(N+1) as each line segment is defined from one point to the next. D is + //!< the dimensionality of the point. Point last_point; @@ -133,7 +138,7 @@ class ArcusCommunication::PathCompiler } else if (initial_point != last_point) { - addLineSegment(PrintFeatureType::NoneType, initial_point, 1, 0, 0); + addLineSegment(PrintFeatureType::NoneType, initial_point, 1, 0, 0.0); } } @@ -281,7 +286,9 @@ class ArcusCommunication::PathCompiler } }; -ArcusCommunication::ArcusCommunication() : private_data(new Private), path_compiler(new PathCompiler(*private_data)) +ArcusCommunication::ArcusCommunication() + : private_data(new Private) + , path_compiler(new PathCompiler(*private_data)) { } @@ -316,7 +323,7 @@ void ArcusCommunication::connect(const std::string& ip, const uint16_t port) std::this_thread::sleep_for(std::chrono::milliseconds(private_data->millisecUntilNextTry)); // Wait until we're connected. Check every XXXms. socket_state = private_data->socket->getState(); } - if (socket_state != Arcus::SocketState::Connected) + if (socket_state == Arcus::SocketState::Connected) { spdlog::info("Connected to {}:{}", ip, port); } @@ -335,7 +342,8 @@ void ArcusCommunication::beginGCode() void ArcusCommunication::flushGCode() { - const std::string& message_str = private_data->gcode_output_stream.str(); + std::string gcode_output_stream = private_data->gcode_output_stream.str(); + auto message_str = slots::instance().modify(gcode_output_stream); if (message_str.size() == 0) { return; @@ -368,7 +376,8 @@ void ArcusCommunication::sendCurrentPosition(const Point& position) void ArcusCommunication::sendGCodePrefix(const std::string& prefix) const { std::shared_ptr message = std::make_shared(); - message->set_data(prefix); + std::string message_str = prefix; + message->set_data(slots::instance().modify(message_str)); private_data->socket->sendMessage(message); } @@ -386,7 +395,7 @@ void ArcusCommunication::sendFinishedSlicing() const spdlog::debug("Sent slicing finished message."); } -void ArcusCommunication::sendLayerComplete(const LayerIndex& layer_nr, const coord_t& z, const coord_t& thickness) +void ArcusCommunication::sendLayerComplete(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness) { std::shared_ptr layer = private_data->getOptimizedLayerById(layer_nr); layer->set_height(z); @@ -411,7 +420,7 @@ void ArcusCommunication::sendOptimizedLayerData() } spdlog::info("Sending {} layers.", data.current_layer_count); - for (std::pair> entry : data.slice_data) // Note: This is in no particular order! + for (const auto& entry : data.slice_data) // Note: This is in no particular order! { spdlog::debug("Sending layer data for layer {} of {}.", entry.first, data.slice_data.size()); private_data->socket->sendMessage(entry.second); // Send the actual layers. @@ -422,7 +431,12 @@ void ArcusCommunication::sendOptimizedLayerData() data.slice_data.clear(); } -void ArcusCommunication::sendPolygon(const PrintFeatureType& type, const ConstPolygonRef& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) +void ArcusCommunication::sendPolygon( + const PrintFeatureType& type, + const ConstPolygonRef& polygon, + const coord_t& line_width, + const coord_t& line_thickness, + const Velocity& velocity) { path_compiler->sendPolygon(type, polygon, line_width, line_thickness, velocity); } @@ -482,7 +496,7 @@ void ArcusCommunication::sendProgress(const float& progress) const private_data->last_sent_progress = rounded_amount; } -void ArcusCommunication::setLayerForSend(const LayerIndex& layer_nr) +void ArcusCommunication::setLayerForSend(const LayerIndex::value_type& layer_nr) { path_compiler->setLayer(layer_nr); } @@ -504,11 +518,22 @@ void ArcusCommunication::sliceNext() } spdlog::debug("Received a Slice message."); +#ifdef ENABLE_PLUGINS + for (const auto& plugin : slice_message->engine_plugins()) + { + const auto slot_id = static_cast(plugin.id()); + slots::instance().connect(slot_id, plugin.plugin_name(), plugin.plugin_version(), utils::createChannel({ plugin.address(), plugin.port() })); + } +#endif // ENABLE_PLUGINS + Slice slice(slice_message->object_lists().size()); Application::getInstance().current_slice = &slice; private_data->readGlobalSettingsMessage(slice_message->global_settings()); private_data->readExtruderSettingsMessage(slice_message->extruders()); + + // Broadcast the settings to the plugins + slots::instance().broadcast(*slice_message); const size_t extruder_count = slice.scene.extruders.size(); // For each setting, register what extruder it should be obtained from (if this is limited to an extruder). diff --git a/src/communication/ArcusCommunicationPrivate.cpp b/src/communication/ArcusCommunicationPrivate.cpp index 9de1b6e2d3..90b23c1e64 100644 --- a/src/communication/ArcusCommunicationPrivate.cpp +++ b/src/communication/ArcusCommunicationPrivate.cpp @@ -3,24 +3,30 @@ #ifdef ARCUS -#include +#include "communication/ArcusCommunicationPrivate.h" #include "Application.h" #include "ExtruderTrain.h" #include "Slice.h" -#include "communication/ArcusCommunicationPrivate.h" #include "settings/types/LayerIndex.h" #include "utils/FMatrix4x3.h" //To convert vertices to integer-points. #include "utils/floatpoint.h" //To accept vertices (which are provided in floating point). +#include + namespace cura { -ArcusCommunication::Private::Private() : socket(nullptr), object_count(0), last_sent_progress(-1), slice_count(0), millisecUntilNextTry(100) +ArcusCommunication::Private::Private() + : socket(nullptr) + , object_count(0) + , last_sent_progress(-1) + , slice_count(0) + , millisecUntilNextTry(100) { } -std::shared_ptr ArcusCommunication::Private::getOptimizedLayerById(LayerIndex layer_nr) +std::shared_ptr ArcusCommunication::Private::getOptimizedLayerById(LayerIndex::value_type layer_nr) { layer_nr += optimized_layers.current_layer_offset; std::unordered_map>::iterator find_result = optimized_layers.slice_data.find(layer_nr); @@ -67,7 +73,9 @@ void ArcusCommunication::Private::readExtruderSettingsMessage(const google::prot spdlog::warn("Received extruder index that is out of range: {}", extruder_nr); continue; } - ExtruderTrain& extruder = slice->scene.extruders[extruder_nr]; // Extruder messages may arrive out of order, so don't iteratively get the next extruder but take the extruder_nr from this message. + ExtruderTrain& extruder + = slice->scene + .extruders[extruder_nr]; // Extruder messages may arrive out of order, so don't iteratively get the next extruder but take the extruder_nr from this message. for (const cura::proto::Setting& setting_message : extruder_message.settings().settings()) { extruder.settings.add(setting_message.name(), setting_message.value()); diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index cac3c7da6e..7b7c0f7163 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -1,29 +1,32 @@ // Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher +#include "communication/CommandLine.h" + +#include "Application.h" //To get the extruders for material estimates. +#include "ExtruderTrain.h" +#include "FffProcessor.h" //To start a slice and get time estimates. +#include "Slice.h" +#include "utils/FMatrix4x3.h" //For the mesh_rotation_matrix setting. + +#include + #include //For strtok and strcopy. #include // error number when trying to read file #include #include //To check if files exist. #include //For std::accumulate. -#include - #include //Loading JSON documents to get settings from them. #include #include -#include - -#include "Application.h" //To get the extruders for material estimates. -#include "ExtruderTrain.h" -#include "FffProcessor.h" //To start a slice and get time estimates. -#include "Slice.h" -#include "communication/CommandLine.h" -#include "utils/FMatrix4x3.h" //For the mesh_rotation_matrix setting. +#include namespace cura { -CommandLine::CommandLine(const std::vector& arguments) : arguments(arguments), last_shown_progress(0) +CommandLine::CommandLine(const std::vector& arguments) + : arguments(arguments) + , last_shown_progress(0) { } @@ -40,7 +43,7 @@ void CommandLine::sendCurrentPosition(const Point&) void CommandLine::sendFinishedSlicing() const { } -void CommandLine::sendLayerComplete(const LayerIndex&, const coord_t&, const coord_t&) +void CommandLine::sendLayerComplete(const LayerIndex::value_type&, const coord_t&, const coord_t&) { } void CommandLine::sendLineTo(const PrintFeatureType&, const Point&, const coord_t&, const coord_t&, const Velocity&) @@ -58,7 +61,7 @@ void CommandLine::sendPolygons(const PrintFeatureType&, const Polygons&, const c void CommandLine::setExtruderForSend(const ExtruderTrain&) { } -void CommandLine::setLayerForSend(const LayerIndex&) +void CommandLine::setLayerForSend(const LayerIndex::value_type&) { } @@ -165,7 +168,8 @@ void CommandLine::sliceNext() } else if (argument.find("--force-read-nondefault") == 0 || argument.find("--force_read_nondefault") == 0) { - spdlog::info("From this point on, if 'default_value' is not available, force the parser to read 'value' (instead of dropping it) to fill the used setting-values."); + spdlog::info( + "From this point on, if 'default_value' is not available, force the parser to read 'value' (instead of dropping it) to fill the used setting-values."); force_read_nondefault = true; } else if (argument.find("--end-force-read") == 0 || argument.find("--end_force_read") == 0) @@ -407,14 +411,12 @@ std::unordered_set CommandLine::defaultSearchDirectories() return result; } -int CommandLine::loadJSON -( +int CommandLine::loadJSON( const rapidjson::Document& document, const std::unordered_set& search_directories, Settings& settings, bool force_read_parent, - bool force_read_nondefault -) + bool force_read_nondefault) { // Inheritance from other JSON documents. if (document.HasMember("inherits") && document["inherits"].IsString()) @@ -433,7 +435,8 @@ int CommandLine::loadJSON } // Extruders defined from here, if any. - // Note that this always puts the extruder settings in the slice of the current extruder. It doesn't keep the nested structure of the JSON files, if extruders would have their own sub-extruders. + // Note that this always puts the extruder settings in the slice of the current extruder. It doesn't keep the nested structure of the JSON files, if extruders would have their + // own sub-extruders. Scene& scene = Application::getInstance().current_slice->scene; if (document.HasMember("metadata") && document["metadata"].IsObject()) { @@ -502,20 +505,17 @@ bool jsonValue2Str(const rapidjson::Value& value, std::string& value_string) } std::string temp; jsonValue2Str(value[0], temp); - value_string = - std::string("[") + - std::accumulate - ( - std::next(value.Begin()), - value.End(), - temp, - [&temp](std::string converted, const rapidjson::Value& next) - { - jsonValue2Str(next, temp); - return std::move(converted) + "," + temp; - } - ) + - std::string("]"); + value_string = std::string("[") + + std::accumulate( + std::next(value.Begin()), + value.End(), + temp, + [&temp](std::string converted, const rapidjson::Value& next) + { + jsonValue2Str(next, temp); + return std::move(converted) + "," + temp; + }) + + std::string("]"); } else { diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 9b2f60b600..2ef08a0474 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1,12 +1,7 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include -#include -#include -#include - -#include +#include "gcodeExport.h" #include "Application.h" //To send layer view data. #include "ExtruderTrain.h" @@ -15,11 +10,17 @@ #include "Slice.h" #include "WipeScriptConfig.h" #include "communication/Communication.h" //To send layer view data. -#include "gcodeExport.h" #include "settings/types/LayerIndex.h" #include "utils/Date.h" #include "utils/string.h" // MMtoStream, PrecisionedDouble +#include + +#include +#include +#include +#include + namespace cura { @@ -35,7 +36,11 @@ std::string transliterate(const std::string& text) return stream.str(); } -GCodeExport::GCodeExport() : output_stream(&std::cout), currentPosition(0, 0, MM2INT(20)), layer_nr(0), relative_extrusion(false) +GCodeExport::GCodeExport() + : output_stream(&std::cout) + , currentPosition(0, 0, MM2INT(20)) + , layer_nr(0) + , relative_extrusion(false) { *output_stream << std::fixed; @@ -45,10 +50,10 @@ GCodeExport::GCodeExport() : output_stream(&std::cout), currentPosition(0, 0, MM total_print_times = std::vector(static_cast(PrintFeatureType::NumPrintFeatureTypes), 0.0); - currentSpeed = 1; - current_print_acceleration = -1; - current_travel_acceleration = -1; - current_jerk = -1; + currentSpeed = 1.0; + current_print_acceleration = -1.0; + current_travel_acceleration = -1.0; + current_jerk = -1.0; is_z_hopped = 0; setFlavor(EGCodeFlavor::MARLIN); @@ -85,8 +90,21 @@ void GCodeExport::preSetup(const size_t start_extruder) const ExtruderTrain& train = scene.extruders[extruder_nr]; setFilamentDiameter(extruder_nr, train.settings.get("material_diameter")); - extruder_attr[extruder_nr].last_retraction_prime_speed = train.settings.get("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured... + extruder_attr[extruder_nr].last_retraction_prime_speed + = train.settings.get("retraction_prime_speed"); // the alternative would be switch_extruder_prime_speed, but dual extrusion might not even be configured... extruder_attr[extruder_nr].fan_number = train.settings.get("machine_extruder_cooling_fan_number"); + + // Cache some settings that we use frequently. + const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; + if (use_extruder_offset_to_offset_coords) + { + extruder_attr[extruder_nr].nozzle_offset = Point(extruder_settings.get("machine_nozzle_offset_x"), extruder_settings.get("machine_nozzle_offset_y")); + } + else + { + extruder_attr[extruder_nr].nozzle_offset = Point(0, 0); + } + extruder_attr[extruder_nr].machine_firmware_retract = extruder_settings.get("machine_firmware_retract"); } machine_name = mesh_group->settings.get("machine_name"); @@ -104,6 +122,24 @@ void GCodeExport::preSetup(const size_t start_extruder) } estimateCalculator.setFirmwareDefaults(mesh_group->settings); + + if (mesh_group == scene.mesh_groups.begin()) + { + if (! scene.current_mesh_group->settings.get("material_bed_temp_prepend")) + { + // Current bed temperature is the one of the first layer (has already been set in header) + bed_temperature = scene.current_mesh_group->settings.get("material_bed_temperature_layer_0"); + } + else + { + // Bed temperature has not been set yet + } + } + else + { + // Current bed temperature is the one of the previous group + bed_temperature = (scene.current_mesh_group - 1)->settings.get("material_bed_temperature"); + } } void GCodeExport::setInitialAndBuildVolumeTemps(const unsigned int start_extruder_nr) @@ -160,7 +196,11 @@ const std::string GCodeExport::flavorToString(const EGCodeFlavor& flavor) const } } -std::string GCodeExport::getFileHeader(const std::vector& extruder_is_used, const Duration* print_time, const std::vector& filament_used, const std::vector& mat_ids) +std::string GCodeExport::getFileHeader( + const std::vector& extruder_is_used, + const Duration* print_time, + const std::vector& filament_used, + const std::vector& mat_ids) { std::ostringstream prefix; @@ -268,13 +308,14 @@ std::string GCodeExport::getFileHeader(const std::vector& extruder_is_used prefix << ";MAXX:" << INT2MM(total_bounding_box.max.x) << new_line; prefix << ";MAXY:" << INT2MM(total_bounding_box.max.y) << new_line; prefix << ";MAXZ:" << INT2MM(total_bounding_box.max.z) << new_line; + prefix << ";TARGET_MACHINE.NAME:" << transliterate(machine_name) << new_line; } return prefix.str(); } -void GCodeExport::setLayerNr(unsigned int layer_nr_) +void GCodeExport::setLayerNr(const LayerIndex& layer_nr_) { layer_nr = layer_nr_; } @@ -294,18 +335,9 @@ bool GCodeExport::getExtruderIsUsed(const int extruder_nr) const Point GCodeExport::getGcodePos(const coord_t x, const coord_t y, const int extruder_train) const { - if (use_extruder_offset_to_offset_coords) - { - const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_train].settings; - return Point(x - extruder_settings.get("machine_nozzle_offset_x"), y - extruder_settings.get("machine_nozzle_offset_y")); - } - else - { - return Point(x, y); - } + return Point(x, y) - extruder_attr[extruder_train].nozzle_offset; } - void GCodeExport::setFlavor(EGCodeFlavor flavor) { this->flavor = flavor; @@ -383,11 +415,12 @@ void GCodeExport::setFilamentDiameter(const size_t extruder, const coord_t diame double GCodeExport::getCurrentExtrudedVolume() const { double extrusion_amount = current_e_value; - const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings; - if (! extruder_settings.get("machine_firmware_retract")) + if (! extruder_attr[current_extruder].machine_firmware_retract) { // no E values are changed to perform a retraction - extrusion_amount -= extruder_attr[current_extruder].retraction_e_amount_at_e_start; // subtract the increment in E which was used for the first unretraction instead of extrusion - extrusion_amount += extruder_attr[current_extruder].retraction_e_amount_current; // add the decrement in E which the filament is behind on extrusion due to the last retraction + extrusion_amount + -= extruder_attr[current_extruder].retraction_e_amount_at_e_start; // subtract the increment in E which was used for the first unretraction instead of extrusion + extrusion_amount + += extruder_attr[current_extruder].retraction_e_amount_current; // add the decrement in E which the filament is behind on extrusion due to the last retraction } if (is_volumetric) { @@ -615,7 +648,8 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons bool should_prime_extruder = true; const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - if (Application::getInstance().communication->isSequential()) // If we must output the g-code sequentially, we must already place the g-code header here even if we don't know the exact time/material usages yet. + if (Application::getInstance().communication->isSequential()) // If we must output the g-code sequentially, we must already place the g-code header here even if we don't know + // the exact time/material usages yet. { std::string prefix = getFileHeader(storage.getExtrudersUsed()); writeCode(prefix.c_str()); @@ -692,6 +726,17 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons return should_prime_extruder; } +void GCodeExport::processInitialLayerBedTemperature() +{ + const Scene& scene = Application::getInstance().current_slice->scene; + const bool heated = scene.current_mesh_group->settings.get("machine_heated_bed"); + const Temperature bed_temp = scene.current_mesh_group->settings.get("material_bed_temperature_layer_0"); + if (heated && bed_temp != 0) + { + writeBedTemperatureCommand(bed_temp, scene.current_mesh_group->settings.get("material_bed_temp_wait")); + } +} + void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage, const size_t start_extruder_nr) { Scene& scene = Application::getInstance().current_slice->scene; @@ -699,6 +744,8 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage if (getFlavor() == EGCodeFlavor::GRIFFIN) { + processInitialLayerBedTemperature(); + ExtruderTrain& train = scene.extruders[start_extruder_nr]; constexpr bool wait = true; const Temperature print_temp_0 = train.settings.get("material_print_temperature_layer_0"); @@ -714,20 +761,9 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage writeLine(tmp.str().c_str()); } - if (scene.current_mesh_group->settings.get("material_bed_temp_prepend") && scene.current_mesh_group->settings.get("machine_heated_bed")) - { - const Temperature bed_temp = scene.current_mesh_group->settings.get("material_bed_temperature_layer_0"); - if (scene.current_mesh_group == scene.mesh_groups.begin() // Always write bed temperature for first mesh group. - || bed_temp != (scene.current_mesh_group - 1)->settings.get("material_bed_temperature")) // Don't write bed temperature if identical to temperature of previous group. - { - if (bed_temp != 0) - { - writeBedTemperatureCommand(bed_temp, scene.current_mesh_group->settings.get("material_bed_temp_wait")); - } - } - } + processInitialLayerBedTemperature(); - if (scene.current_mesh_group->settings.get("material_print_temp_prepend")) + if (scene.current_mesh_group->settings.get("material_print_temp_prepend") || (scene.current_mesh_group != scene.mesh_groups.begin())) { for (unsigned extruder_nr = 0; extruder_nr < num_extruders; extruder_nr++) { @@ -873,14 +909,18 @@ void GCodeExport::writeMoveBFB(const int x, const int y, const int z, const Velo if (! extruder_attr[current_extruder].retraction_e_amount_current) { *output_stream << "M103" << new_line; - extruder_attr[current_extruder].retraction_e_amount_current = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically + extruder_attr[current_extruder].retraction_e_amount_current + = 1.0; // 1.0 used as stub; BFB doesn't use the actual retraction amount; it performs retraction on the firmware automatically } } *output_stream << "G1 X" << MMtoStream{ gcode_pos.X } << " Y" << MMtoStream{ gcode_pos.Y } << " Z" << MMtoStream{ z }; *output_stream << " F" << PrecisionedDouble{ 1, fspeed } << new_line; currentPosition = Point3(x, y, z); - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), speed, feature); + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), + speed, + feature); } void GCodeExport::writeTravel(const coord_t x, const coord_t y, const coord_t z, const Velocity& speed) @@ -906,7 +946,14 @@ void GCodeExport::writeTravel(const coord_t x, const coord_t y, const coord_t z, writeFXYZE(speed, x, y, z, current_e_value, travel_move_type); } -void GCodeExport::writeExtrusion(const coord_t x, const coord_t y, const coord_t z, const Velocity& speed, const double extrusion_mm3_per_mm, const PrintFeatureType& feature, const bool update_extrusion_offset) +void GCodeExport::writeExtrusion( + const coord_t x, + const coord_t y, + const coord_t z, + const Velocity& speed, + const double extrusion_mm3_per_mm, + const PrintFeatureType& feature, + const bool update_extrusion_offset) { if (currentPosition.x == x && currentPosition.y == y && currentPosition.z == z) { @@ -1012,41 +1059,52 @@ void GCodeExport::writeUnretractionAndPrime() current_e_value += prime_volume_e; if (extruder_attr[current_extruder].retraction_e_amount_current) { - const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings; - if (extruder_settings.get("machine_firmware_retract")) + if (extruder_attr[current_extruder].machine_firmware_retract) { // note that BFB is handled differently *output_stream << "G11" << new_line; // Assume default UM2 retraction settings. if (prime_volume != 0) { const double output_e = (relative_extrusion) ? prime_volume_e : current_e_value; - *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{ 5, output_e } - << new_line; + *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " + << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{ 5, output_e } << new_line; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; } - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), 25.0, PrintFeatureType::MoveRetraction); + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), + 25.0, + PrintFeatureType::MoveRetraction); } else { current_e_value += extruder_attr[current_extruder].retraction_e_amount_current; const double output_e = (relative_extrusion) ? extruder_attr[current_extruder].retraction_e_amount_current + prime_volume_e : current_e_value; - *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{ 5, output_e } << new_line; + *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " + << extruder_attr[current_extruder].extruderCharacter << PrecisionedDouble{ 5, output_e } << new_line; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::MoveRetraction); + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), + currentSpeed, + PrintFeatureType::MoveRetraction); } } else if (prime_volume != 0.0) { const double output_e = (relative_extrusion) ? prime_volume_e : current_e_value; - *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " << extruder_attr[current_extruder].extruderCharacter; + *output_stream << "G1 F" << PrecisionedDouble{ 1, extruder_attr[current_extruder].last_retraction_prime_speed * 60 } << " " + << extruder_attr[current_extruder].extruderCharacter; *output_stream << PrecisionedDouble{ 5, output_e } << new_line; currentSpeed = extruder_attr[current_extruder].last_retraction_prime_speed; - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::NoneType); + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), + currentSpeed, + PrintFeatureType::NoneType); } extruder_attr[current_extruder].prime_volume = 0.0; if (getCurrentExtrudedVolume() > 10000.0 && flavor != EGCodeFlavor::BFB - && flavor != EGCodeFlavor::MAKERBOT) // According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it every 10m, just to be sure. + && flavor != EGCodeFlavor::MAKERBOT) // According to https://github.com/Ultimaker/CuraEngine/issues/14 having more then 21m of extrusion causes inaccuracies. So reset it + // every 10m, just to be sure. { resetExtrusionValue(); } @@ -1106,8 +1164,7 @@ void GCodeExport::writeRetraction(const RetractionConfig& config, bool force, bo } } - const Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[current_extruder].settings; - if (extruder_settings.get("machine_firmware_retract")) + if (extruder_attr[current_extruder].machine_firmware_retract) { if (extruder_switch && extr_attr.retraction_e_amount_current) { @@ -1120,9 +1177,10 @@ void GCodeExport::writeRetraction(const RetractionConfig& config, bool force, bo } *output_stream << new_line; // Assume default UM2 retraction settings. - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value + retraction_diff_e_amount)), - 25, - PrintFeatureType::MoveRetraction); // TODO: hardcoded values! + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value + retraction_diff_e_amount)), + 25.0, + PrintFeatureType::MoveRetraction); // TODO: hardcoded values! } else { @@ -1131,7 +1189,10 @@ void GCodeExport::writeRetraction(const RetractionConfig& config, bool force, bo const double output_e = (relative_extrusion) ? retraction_diff_e_amount : current_e_value; *output_stream << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " " << extr_attr.extruderCharacter << PrecisionedDouble{ 5, output_e } << new_line; currentSpeed = speed; - estimateCalculator.plan(TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), currentSpeed, PrintFeatureType::MoveRetraction); + estimateCalculator.plan( + TimeEstimateCalculator::Position(INT2MM(currentPosition.x), INT2MM(currentPosition.y), INT2MM(currentPosition.z), eToMm(current_e_value)), + currentSpeed, + PrintFeatureType::MoveRetraction); extr_attr.last_retraction_prime_speed = config.primeSpeed; } @@ -1285,7 +1346,10 @@ void GCodeExport::writePrimeTrain(const Velocity& travel_speed) // ideally the prime position would be respected whether we do a blob or not, // but the frontend currently doesn't support a value function of an extruder setting depending on an fdmprinter setting, // which is needed to automatically ignore the prime position for the printer when blob is disabled - Point3 prime_pos(extruder_settings.get("extruder_prime_pos_x"), extruder_settings.get("extruder_prime_pos_y"), extruder_settings.get("extruder_prime_pos_z")); + Point3 prime_pos( + extruder_settings.get("extruder_prime_pos_x"), + extruder_settings.get("extruder_prime_pos_y"), + extruder_settings.get("extruder_prime_pos_z")); if (! extruder_settings.get("extruder_prime_pos_abs")) { // currentPosition.z can be already z hopped @@ -1456,10 +1520,10 @@ void GCodeExport::writeBedTemperatureCommand(const Temperature& temperature, con { // The UM2 family doesn't support temperature commands (they are fixed in the firmware) return; } - bool wrote_command = false; - if (wait) + + if (bed_temperature != temperature) // Not already at the desired temperature. { - if (bed_temperature != temperature) // Not already at the desired temperature. + if (wait) { if (flavor == EGCodeFlavor::MARLIN) { @@ -1467,20 +1531,17 @@ void GCodeExport::writeBedTemperatureCommand(const Temperature& temperature, con *output_stream << PrecisionedDouble{ 1, temperature } << new_line; *output_stream << "M105" << new_line; } + *output_stream << "M190 S"; } - *output_stream << "M190 S"; - wrote_command = true; - } - else if (bed_temperature != temperature) - { - *output_stream << "M140 S"; - wrote_command = true; - } - if (wrote_command) - { + else + { + *output_stream << "M140 S"; + } + *output_stream << PrecisionedDouble{ 1, temperature } << new_line; + + bed_temperature = temperature; } - bed_temperature = temperature; } void GCodeExport::writeBuildVolumeTemperatureCommand(const Temperature& temperature, const bool wait) diff --git a/src/infill.cpp b/src/infill.cpp index 26911124dc..6da3c25c4f 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -1,15 +1,9 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include //For std::sort. -#include -#include - -#include -#include +#include "infill.h" #include "WallToolPaths.h" -#include "infill.h" #include "infill/GyroidInfill.h" #include "infill/ImageBasedDensityProvider.h" #include "infill/LightningGenerator.h" @@ -18,13 +12,22 @@ #include "infill/SierpinskiFillProvider.h" #include "infill/SubDivCube.h" #include "infill/UniformDensityProvider.h" +#include "plugins/slots.h" #include "sliceDataStorage.h" #include "utils/PolygonConnector.h" #include "utils/PolylineStitcher.h" #include "utils/Simplify.h" #include "utils/UnionFind.h" +#include "utils/linearAlg2D.h" #include "utils/polygonUtils.h" +#include +#include + +#include //For std::sort. +#include +#include + /*! * Function which returns the scanline_idx for a given x coordinate * @@ -49,7 +52,15 @@ static inline int computeScanSegmentIdx(int x, int line_width) namespace cura { -Polygons Infill::generateWallToolPaths(std::vector& toolpaths, Polygons& outer_contour, const size_t wall_line_count, const coord_t line_width, const coord_t infill_overlap, const Settings& settings, int layer_idx, SectionType section_type) +Polygons Infill::generateWallToolPaths( + std::vector& toolpaths, + Polygons& outer_contour, + const size_t wall_line_count, + const coord_t line_width, + const coord_t infill_overlap, + const Settings& settings, + int layer_idx, + SectionType section_type) { outer_contour = outer_contour.offset(infill_overlap); scripta::log("infill_outer_contour", outer_contour, section_type, layer_idx, scripta::CellVDI{ "infill_overlap", infill_overlap }); @@ -69,15 +80,17 @@ Polygons Infill::generateWallToolPaths(std::vector& toolpath return inner_contour; } -void Infill::generate(std::vector& toolpaths, - Polygons& result_polygons, - Polygons& result_lines, - const Settings& settings, - int layer_idx, - SectionType section_type, - const SierpinskiFillProvider* cross_fill_provider, - const LightningLayer* lightning_trees, - const SliceMeshStorage* mesh) +void Infill::generate( + std::vector& toolpaths, + Polygons& result_polygons, + Polygons& result_lines, + const Settings& settings, + int layer_idx, + SectionType section_type, + const std::shared_ptr& cross_fill_provider, + const std::shared_ptr& lightning_trees, + const SliceMeshStorage* mesh, + const Polygons& prevent_small_exposed_to_air) { if (outer_contour.empty()) { @@ -93,21 +106,24 @@ void Infill::generate(std::vector& toolpaths, // infill pattern is concentric or if the small_area_width is zero. if (pattern != EFillMethod::CONCENTRIC && small_area_width > 0) { + const auto too_small_length = INT2MM(static_cast(infill_line_width) / 2.0); + // Split the infill region in a narrow region and the normal region. Polygons small_infill = inner_contour; - inner_contour = inner_contour.offset(-small_area_width / 2).offset(small_area_width / 2); + inner_contour = inner_contour.offset(-small_area_width / 2); + inner_contour.removeSmallAreas(too_small_length * too_small_length, true); + inner_contour = inner_contour.offset(small_area_width / 2); + inner_contour = inner_contour.unionPolygons(prevent_small_exposed_to_air).intersection(small_infill); + inner_contour = Simplify(max_resolution, max_deviation, 0).polygon(inner_contour); small_infill = small_infill.difference(inner_contour); - small_infill = Simplify(max_resolution, max_deviation, 0).polygon(small_infill); // Small corners of a bigger area should not be considered narrow and are therefore added to the bigger area again. auto small_infill_parts = small_infill.splitIntoParts(); small_infill.clear(); for (const auto& small_infill_part : small_infill_parts) { - if ( - small_infill_part.offset(-infill_line_width / 2).offset(infill_line_width / 2).area() < infill_line_width * infill_line_width * 10 - && ! inner_contour.intersection(small_infill_part.offset(infill_line_width / 4)).empty() - ) + if (small_infill_part.offset(-infill_line_width / 2).offset(infill_line_width / 2).area() < infill_line_width * infill_line_width * 10 + && ! inner_contour.intersection(small_infill_part.offset(infill_line_width / 4)).empty()) { inner_contour.add(small_infill_part); } @@ -123,12 +139,16 @@ void Infill::generate(std::vector& toolpaths, const size_t narrow_wall_count = small_area_width / infill_line_width + 1; WallToolPaths wall_toolpaths(small_infill, infill_line_width, narrow_wall_count, 0, settings, layer_idx, section_type); std::vector small_infill_paths = wall_toolpaths.getToolPaths(); - scripta::log("infill_small_infill_paths_0", small_infill_paths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "infill_small_infill_paths_0", + small_infill_paths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); for (const auto& small_infill_path : small_infill_paths) { toolpaths.emplace_back(small_infill_path); @@ -140,9 +160,11 @@ void Infill::generate(std::vector& toolpaths, if (pattern == EFillMethod::ZIG_ZAG // Zig-zag prints the zags along the walls. || (zig_zaggify && (pattern == EFillMethod::LINES // Zig-zaggified infill patterns print their zags along the walls. - || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::GRID || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::TRIHEXAGON - || pattern == EFillMethod::GYROID || pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D)) - || infill_multiplier % 2 == 0) // Multiplied infill prints loops of infill, partly along the walls, if even. For odd multipliers >1 it gets offset by the multiply algorithm itself. + || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::GRID || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL + || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::TRIHEXAGON || pattern == EFillMethod::GYROID || pattern == EFillMethod::CROSS + || pattern == EFillMethod::CROSS_3D)) + || infill_multiplier % 2 + == 0) // Multiplied infill prints loops of infill, partly along the walls, if even. For odd multipliers >1 it gets offset by the multiply algorithm itself. { inner_contour = inner_contour.offset(-infill_line_width / 2); inner_contour = Simplify(max_resolution, max_deviation, 0).polygon(inner_contour); @@ -160,6 +182,7 @@ void Infill::generate(std::vector& toolpaths, Polygons generated_result_lines; _generate(toolpaths, generated_result_polygons, generated_result_lines, settings, cross_fill_provider, lightning_trees, mesh); + zig_zaggify = zig_zaggify_real; multiplyInfill(generated_result_polygons, generated_result_lines); result_polygons.add(generated_result_polygons); @@ -171,23 +194,35 @@ void Infill::generate(std::vector& toolpaths, // So make sure we provide it with a Polygons that is safe to clear and only add stuff to result_lines. Polygons generated_result_polygons; Polygons generated_result_lines; + _generate(toolpaths, generated_result_polygons, generated_result_lines, settings, cross_fill_provider, lightning_trees, mesh); + result_polygons.add(generated_result_polygons); result_lines.add(generated_result_lines); } scripta::log("infill_result_polygons_0", result_polygons, section_type, layer_idx); scripta::log("infill_result_lines_0", result_lines, section_type, layer_idx); - scripta::log("infill_toolpaths_0", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "infill_toolpaths_0", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); if (connect_polygons) { // remove too small polygons coord_t snap_distance = infill_line_width * 2; // polygons with a span of max 1 * nozzle_size are too small - auto it = std::remove_if(result_polygons.begin(), result_polygons.end(), [snap_distance](PolygonRef poly) { return poly.shorterThan(snap_distance); }); + auto it = std::remove_if( + result_polygons.begin(), + result_polygons.end(), + [snap_distance](PolygonRef poly) + { + return poly.shorterThan(snap_distance); + }); result_polygons.erase(it, result_polygons.end()); PolygonConnector connector(infill_line_width); @@ -200,22 +235,27 @@ void Infill::generate(std::vector& toolpaths, toolpaths = connected_paths; scripta::log("infill_result_polygons_1", result_polygons, section_type, layer_idx); scripta::log("infill_result_lines_1", result_lines, section_type, layer_idx); - scripta::log("infill_toolpaths_1", toolpaths, section_type, layer_idx, - scripta::CellVDI{"is_closed", &ExtrusionLine::is_closed }, - scripta::CellVDI{"is_odd", &ExtrusionLine::is_odd }, - scripta::CellVDI{"inset_idx", &ExtrusionLine::inset_idx }, - scripta::PointVDI{"width", &ExtrusionJunction::w }, - scripta::PointVDI{"perimeter_index", &ExtrusionJunction::perimeter_index }); + scripta::log( + "infill_toolpaths_1", + toolpaths, + section_type, + layer_idx, + scripta::CellVDI{ "is_closed", &ExtrusionLine::is_closed }, + scripta::CellVDI{ "is_odd", &ExtrusionLine::is_odd }, + scripta::CellVDI{ "inset_idx", &ExtrusionLine::inset_idx }, + scripta::PointVDI{ "width", &ExtrusionJunction::w }, + scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index }); } } -void Infill::_generate(std::vector& toolpaths, - Polygons& result_polygons, - Polygons& result_lines, - const Settings& settings, - const SierpinskiFillProvider* cross_fill_provider, - const LightningLayer* lightning_trees, - const SliceMeshStorage* mesh) +void Infill::_generate( + std::vector& toolpaths, + Polygons& result_polygons, + Polygons& result_lines, + const Settings& settings, + const std::shared_ptr& cross_fill_provider, + const std::shared_ptr& lightning_trees, + const SliceMeshStorage* mesh) { if (inner_contour.empty()) return; @@ -275,6 +315,17 @@ void Infill::_generate(std::vector& toolpaths, assert(lightning_trees); // "Cannot generate Lightning infill without a generator!\n" generateLightningInfill(lightning_trees, result_lines); break; + case EFillMethod::PLUGIN: + { + auto [toolpaths_, generated_result_polygons_, generated_result_lines_] = slots::instance().generate( + inner_contour, + mesh ? mesh->settings.get("infill_pattern") : settings.get("infill_pattern"), + mesh ? mesh->settings : settings); + toolpaths.insert(toolpaths.end(), toolpaths_.begin(), toolpaths_.end()); + result_polygons.add(generated_result_polygons_); + result_lines.add(generated_result_lines_); + break; + } default: spdlog::error("Fill pattern has unknown value.\n"); break; @@ -282,7 +333,7 @@ void Infill::_generate(std::vector& toolpaths, if (connect_lines) { - // The list should be empty because it will be again filled completely. Otherwise might have double lines. + // The list should be empty because it will be again filled completely. Otherwise, might have double lines. assert(result_lines.empty()); result_lines.clear(); connectLines(result_lines); @@ -291,7 +342,9 @@ void Infill::_generate(std::vector& toolpaths, Simplify simplifier(max_resolution, max_deviation, 0); result_polygons = simplifier.polygon(result_polygons); - if (! skip_line_stitching && (zig_zaggify || pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::GYROID || pattern == EFillMethod::ZIG_ZAG)) + if (! skip_line_stitching + && (zig_zaggify || pattern == EFillMethod::CROSS || pattern == EFillMethod::CROSS_3D || pattern == EFillMethod::CUBICSUBDIV || pattern == EFillMethod::GYROID + || pattern == EFillMethod::ZIG_ZAG)) { // don't stich for non-zig-zagged line infill types Polygons stitched_lines; PolylineStitcher::stitch(result_lines, stitched_lines, result_polygons, infill_line_width); @@ -318,7 +371,8 @@ void Infill::multiplyInfill(Polygons& result_polygons, Polygons& result_lines) const Polygons first_offset_polygons_inward = result_polygons.offset(-offset); // make lines on the inside of the input polygons const Polygons first_offset_polygons_outward = result_polygons.offset(offset); // make lines on the other side of the input polygons const Polygons first_offset_polygons = first_offset_polygons_outward.difference(first_offset_polygons_inward); - first_offset = first_offset_lines.unionPolygons(first_offset_polygons); // usually we only have either lines or polygons, but this code also handles an infill pattern which generates both + first_offset = first_offset_lines.unionPolygons( + first_offset_polygons); // usually we only have either lines or polygons, but this code also handles an infill pattern which generates both if (zig_zaggify) { first_offset = inner_contour.difference(first_offset); @@ -376,7 +430,7 @@ void Infill::generateGyroidInfill(Polygons& result_lines, Polygons& result_polyg PolylineStitcher::stitch(line_segments, result_lines, result_polygons, infill_line_width); } -void Infill::generateLightningInfill(const LightningLayer* trees, Polygons& result_lines) +void Infill::generateLightningInfill(const std::shared_ptr& trees, Polygons& result_lines) { // Don't need to support areas smaller than line width, as they are always within radius: if (std::abs(inner_contour.area()) < infill_line_width || ! trees) @@ -405,7 +459,8 @@ void Infill::generateConcentricInfill(std::vector& toolpaths constexpr size_t inset_wall_count = 1; // 1 wall at a time. constexpr coord_t wall_0_inset = 0; // Don't apply any outer wall inset for these. That's just for the outer wall. - WallToolPaths wall_toolpaths(current_inset, infill_line_width, inset_wall_count, wall_0_inset, settings, 0, SectionType::CONCENTRIC_INFILL); // FIXME: @jellespijker pass the correct layer + WallToolPaths wall_toolpaths(current_inset, infill_line_width, inset_wall_count, wall_0_inset, settings, 0, SectionType::CONCENTRIC_INFILL); // FIXME: @jellespijker pass + // the correct layer const std::vector inset_paths = wall_toolpaths.getToolPaths(); toolpaths.insert(toolpaths.end(), inset_paths.begin(), inset_paths.end()); @@ -499,7 +554,14 @@ void Infill::generateCrossInfill(const SierpinskiFillProvider& cross_fill_provid } } -void Infill::addLineInfill(Polygons& result, const PointMatrix& rotation_matrix, const int scanline_min_idx, const int line_distance, const AABB boundary, std::vector>& cut_list, coord_t shift) +void Infill::addLineInfill( + Polygons& result, + const PointMatrix& rotation_matrix, + const int scanline_min_idx, + const int line_distance, + const AABB boundary, + std::vector>& cut_list, + coord_t shift) { assert(! connect_lines && "connectLines() should add the infill lines, not addLineInfill"); @@ -576,7 +638,13 @@ void Infill::generateZigZagInfill(Polygons& result, const coord_t line_distance, * Edit: the term scansegment is wrong, since I call a boundary segment leaving from an even scanline to the left as belonging to an even scansegment, * while I also call a boundary segment leaving from an even scanline toward the right as belonging to an even scansegment. */ -void Infill::generateLinearBasedInfill(Polygons& result, const int line_distance, const PointMatrix& rotation_matrix, ZigzagConnectorProcessor& zigzag_connector_processor, const bool connected_zigzags, coord_t extra_shift) +void Infill::generateLinearBasedInfill( + Polygons& result, + const int line_distance, + const PointMatrix& rotation_matrix, + ZigzagConnectorProcessor& zigzag_connector_processor, + const bool connected_zigzags, + coord_t extra_shift) { if (line_distance == 0 || inner_contour.empty()) // No infill to generate (0% density) or no area to generate it in. { @@ -607,7 +675,10 @@ void Infill::generateLinearBasedInfill(Polygons& result, const int line_distance // Then we can later join two crossings together to form lines and still know what polygon line segments that infill line connected to. struct Crossing { - Crossing(Point coordinate, size_t polygon_index, size_t vertex_index) : coordinate(coordinate), polygon_index(polygon_index), vertex_index(vertex_index){}; + Crossing(Point coordinate, size_t polygon_index, size_t vertex_index) + : coordinate(coordinate) + , polygon_index(polygon_index) + , vertex_index(vertex_index){}; Point coordinate; size_t polygon_index; size_t vertex_index; @@ -656,12 +727,14 @@ void Infill::generateLinearBasedInfill(Polygons& result, const int line_distance if (p0.X < p1.X) { scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment - scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance); // -1 cause the vertex point is handled in the next segment (or not in the case which looks like >) + scanline_idx1 + = computeScanSegmentIdx(p1.X - shift, line_distance); // -1 cause the vertex point is handled in the next segment (or not in the case which looks like >) } else { direction = -1; - scanline_idx0 = computeScanSegmentIdx(p0.X - shift, line_distance); // -1 cause the vertex point is handled in the previous segment (or not in the case which looks like >) + scanline_idx0 + = computeScanSegmentIdx(p0.X - shift, line_distance); // -1 cause the vertex point is handled in the previous segment (or not in the case which looks like >) scanline_idx1 = computeScanSegmentIdx(p1.X - shift, line_distance) + 1; // + 1 cause we don't cross the scanline of the first scan segment } @@ -701,7 +774,8 @@ void Infill::generateLinearBasedInfill(Polygons& result, const int line_distance { continue; } - InfillLineSegment* new_segment = new InfillLineSegment(unrotated_first, first.vertex_index, first.polygon_index, unrotated_second, second.vertex_index, second.polygon_index); + InfillLineSegment* new_segment + = new InfillLineSegment(unrotated_first, first.vertex_index, first.polygon_index, unrotated_second, second.vertex_index, second.polygon_index); // Put the same line segment in the data structure twice: Once for each of the polygon line segment that it crosses. crossings_on_line[first.polygon_index][first.vertex_index].push_back(new_segment); crossings_on_line[second.polygon_index][second.vertex_index].push_back(new_segment); @@ -724,6 +798,56 @@ void Infill::generateLinearBasedInfill(Polygons& result, const int line_distance } } +void Infill::resolveIntersection(const coord_t at_distance, const Point& intersect, Point& connect_start, Point& connect_end, InfillLineSegment* a, InfillLineSegment* b) +{ + // Select wich ends of the line need to 'bend'. + const bool forward_line_a = a->end == connect_start; + const bool forward_line_b = b->start == connect_end; + auto& bend_a = forward_line_a ? a->end_bend : a->start_bend; + auto& bend_b = forward_line_b ? b->start_bend : b->end_bend; + auto& end_a = forward_line_a ? a->altered_end : a->altered_start; + auto& end_b = forward_line_b ? b->altered_start : b->altered_end; + + // Set values ('pre existing' values are needed when feeding these as reference parameters to functions that need a value). + assert(! bend_a.has_value()); + assert(! bend_b.has_value()); + bend_a.emplace(0, 0); + bend_b.emplace(0, 0); + + // Find a bisector of the intersection; specifically, the one that crosses the connection & offset it by 1/2 distance to each side. + constexpr auto large_enough_vec_len = 0xFFFFFFFF; + const auto bisect = LinearAlg2D::getBisectorVector(intersect, connect_start, connect_end, large_enough_vec_len); + const auto offset = ((at_distance / 2) * Point(-bisect.Y, bisect.X)) / large_enough_vec_len; + const auto q = intersect + offset; + const auto r = q + bisect; + const auto s = intersect - offset; + const auto t = s + bisect; + + // In certain rare conditions, the lines do not actually intersect in a way that we can solve with the current algorithm. + bool is_resolved = true; + + // Use both of the resulting lines to place the 'bends' by intersecting with the original line-segments. + is_resolved &= LinearAlg2D::lineLineIntersection(q, r, a->start, a->end, bend_a.value()) && LinearAlg2D::pointIsProjectedBeyondLine(bend_a.value(), a->start, a->end) == 0; + is_resolved &= LinearAlg2D::lineLineIntersection(s, t, b->start, b->end, bend_b.value()) && LinearAlg2D::pointIsProjectedBeyondLine(bend_b.value(), b->start, b->end) == 0; + + // Also set the new end-points + is_resolved &= LinearAlg2D::lineLineIntersection(connect_start, connect_end, q, r, end_a) && LinearAlg2D::pointIsProjectedBeyondLine(end_a, connect_start, connect_end) == 0; + is_resolved &= LinearAlg2D::lineLineIntersection(connect_start, connect_end, s, t, end_b) && LinearAlg2D::pointIsProjectedBeyondLine(end_b, connect_start, connect_end) == 0; + + if (is_resolved) + { + // The connecting line will be made from the end-points. + connect_start = end_a; + connect_end = end_b; + } + else + { + // Put everything that has now potentially become messed up, back. + bend_a.reset(); + bend_b.reset(); + } +} + void Infill::connectLines(Polygons& result_lines) { UnionFind connected_lines; // Keeps track of which lines are connected to which. @@ -741,6 +865,7 @@ void Infill::connectLines(Polygons& result_lines) } } + const auto half_line_distance_squared = (line_distance * line_distance) / 4; for (size_t polygon_index = 0; polygon_index < inner_contour.size(); polygon_index++) { ConstPolygonRef inner_contour_polygon = inner_contour[polygon_index]; @@ -760,15 +885,18 @@ void Infill::connectLines(Polygons& result_lines) Point vertex_after = inner_contour_polygon[vertex_index]; // Sort crossings on every line by how far they are from their initial point. - std::sort(crossings_on_polygon_segment.begin(), - crossings_on_polygon_segment.end(), - [&vertex_before, polygon_index, vertex_index](InfillLineSegment* left_hand_side, InfillLineSegment* right_hand_side) - { - // Find the two endpoints that are relevant. - const Point left_hand_point = (left_hand_side->start_segment == vertex_index && left_hand_side->start_polygon == polygon_index) ? left_hand_side->start : left_hand_side->end; - const Point right_hand_point = (right_hand_side->start_segment == vertex_index && right_hand_side->start_polygon == polygon_index) ? right_hand_side->start : right_hand_side->end; - return vSize(left_hand_point - vertex_before) < vSize(right_hand_point - vertex_before); - }); + std::sort( + crossings_on_polygon_segment.begin(), + crossings_on_polygon_segment.end(), + [&vertex_before, polygon_index, vertex_index](InfillLineSegment* left_hand_side, InfillLineSegment* right_hand_side) + { + // Find the two endpoints that are relevant. + const bool choose_left = (left_hand_side->start_segment == vertex_index && left_hand_side->start_polygon == polygon_index); + const bool choose_right = (right_hand_side->start_segment == vertex_index && right_hand_side->start_polygon == polygon_index); + const Point left_hand_point = choose_left ? left_hand_side->start : left_hand_side->end; + const Point right_hand_point = choose_right ? right_hand_side->start : right_hand_side->end; + return vSize(left_hand_point - vertex_before) < vSize(right_hand_point - vertex_before); + }); for (InfillLineSegment* crossing : crossings_on_polygon_segment) { @@ -783,52 +911,48 @@ void Infill::connectLines(Polygons& result_lines) assert(crossing_handle != (size_t)-1); const size_t previous_crossing_handle = connected_lines.find(previous_crossing); assert(previous_crossing_handle != (size_t)-1); - if (crossing_handle == previous_crossing_handle) // These two infill lines are already connected. Don't create a loop now. Continue connecting with the next crossing. + if (crossing_handle == previous_crossing_handle) { + // These two infill lines are already connected. Don't create a loop now. Continue connecting with the next crossing. continue; } // Join two infill lines together with a connecting line. // Here the InfillLineSegments function as a linked list, so that they can easily be joined. - const Point previous_point = (previous_segment->start_segment == vertex_index && previous_segment->start_polygon == polygon_index) ? previous_segment->start : previous_segment->end; - const Point next_point = (crossing->start_segment == vertex_index && crossing->start_polygon == polygon_index) ? crossing->start : crossing->end; + const bool previous_forward = (previous_segment->start_segment == vertex_index && previous_segment->start_polygon == polygon_index); + const bool next_forward = (crossing->start_segment == vertex_index && crossing->start_polygon == polygon_index); + Point& previous_point = previous_forward ? previous_segment->start : previous_segment->end; + Point& next_point = next_forward ? crossing->start : crossing->end; + InfillLineSegment* new_segment; - // If the segment is zero length, we avoid creating it but still want to connect the crossing with the previous segment + // If the segment is near length, we avoid creating it but still want to connect the crossing with the previous segment. if (previous_point == next_point) { - if (previous_segment->start_segment == vertex_index && previous_segment->start_polygon == polygon_index) - { - previous_segment->previous = crossing; - } - else - { - previous_segment->next = crossing; - } + (previous_forward ? previous_segment->previous : previous_segment->next) = crossing; new_segment = previous_segment; } else { - new_segment = new InfillLineSegment(previous_point, vertex_index, polygon_index, next_point, vertex_index, polygon_index); // A connecting line between them. - new_segment->previous = previous_segment; - if (previous_segment->start_segment == vertex_index && previous_segment->start_polygon == polygon_index) - { - previous_segment->previous = new_segment; - } - else + // Resolve any intersections of the fill lines close to the boundary, by inserting extra points so the lines don't create a tiny 'loop'. + Point intersect; + if (vSize2(previous_point - next_point) < half_line_distance_squared + && LinearAlg2D::lineLineIntersection(previous_segment->start, previous_segment->end, crossing->start, crossing->end, intersect) + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, previous_segment->start, previous_segment->end) == 0 + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start, crossing->end) == 0) { - previous_segment->next = new_segment; + resolveIntersection(infill_line_width, intersect, previous_point, next_point, previous_segment, crossing); } + + // A connecting line between them. + new_segment = new InfillLineSegment(previous_point, vertex_index, polygon_index, next_point, vertex_index, polygon_index); + new_segment->altered_start = previous_point; + new_segment->altered_end = next_point; + new_segment->previous = previous_segment; + (previous_forward ? previous_segment->previous : previous_segment->next) = new_segment; new_segment->next = crossing; } - if (crossing->start_segment == vertex_index && crossing->start_polygon == polygon_index) - { - crossing->previous = new_segment; - } - else - { - crossing->next = new_segment; - } + (next_forward ? crossing->previous : crossing->next) = new_segment; connected_lines.unite(crossing_handle, previous_crossing_handle); previous_crossing = nullptr; previous_segment = nullptr; @@ -839,37 +963,22 @@ void Infill::connectLines(Polygons& result_lines) if (previous_crossing) { InfillLineSegment* new_segment; - if (vertex_index == previous_segment->start_segment && polygon_index == previous_segment->start_polygon) + + const bool choose_side = (vertex_index == previous_segment->start_segment && polygon_index == previous_segment->start_polygon); + const auto& previous_side = choose_side ? previous_segment->start : previous_segment->end; + if (previous_side == vertex_after) { - if (previous_segment->start == vertex_after) - { - // Edge case when an infill line ends directly on top of vertex_after: We skip the extra connecting line segment, as that would be 0-length. - previous_segment = nullptr; - previous_crossing = nullptr; - } - else - { - new_segment = new InfillLineSegment(previous_segment->start, vertex_index, polygon_index, vertex_after, (vertex_index + 1) % inner_contour[polygon_index].size(), polygon_index); - previous_segment->previous = new_segment; - new_segment->previous = previous_segment; - previous_segment = new_segment; - } + // Edge case when an infill line ends directly on top of vertex_after: We skip the extra connecting line segment, as that would be 0-length. + previous_segment = nullptr; + previous_crossing = nullptr; } else { - if (previous_segment->end == vertex_after) - { - // Edge case when an infill line ends directly on top of vertex_after: We skip the extra connecting line segment, as that would be 0-length. - previous_segment = nullptr; - previous_crossing = nullptr; - } - else - { - new_segment = new InfillLineSegment(previous_segment->end, vertex_index, polygon_index, vertex_after, (vertex_index + 1) % inner_contour[polygon_index].size(), polygon_index); - previous_segment->next = new_segment; - new_segment->previous = previous_segment; - previous_segment = new_segment; - } + new_segment + = new InfillLineSegment(previous_side, vertex_index, polygon_index, vertex_after, (vertex_index + 1) % inner_contour[polygon_index].size(), polygon_index); + (choose_side ? previous_segment->previous : previous_segment->next) = new_segment; + new_segment->previous = previous_segment; + previous_segment = new_segment; } } @@ -894,26 +1003,34 @@ void Infill::connectLines(Polygons& result_lines) InfillLineSegment* current_infill_line = infill_line; while (current_infill_line->next && current_infill_line->previous) // Until we reached an endpoint. { - const Point next_vertex = (previous_vertex == current_infill_line->start) ? current_infill_line->end : current_infill_line->start; - current_infill_line = (previous_vertex == current_infill_line->start) ? current_infill_line->next : current_infill_line->previous; + const bool choose_side = (previous_vertex == current_infill_line->start); + const Point next_vertex = choose_side ? current_infill_line->end : current_infill_line->start; + current_infill_line = choose_side ? current_infill_line->next : current_infill_line->previous; previous_vertex = next_vertex; } // Now go along the linked list of infill lines and output the infill lines to the actual result. - InfillLineSegment* old_line = current_infill_line; - const Point first_vertex = (! current_infill_line->previous) ? current_infill_line->start : current_infill_line->end; - previous_vertex = (! current_infill_line->previous) ? current_infill_line->end : current_infill_line->start; - current_infill_line = (first_vertex == current_infill_line->start) ? current_infill_line->next : current_infill_line->previous; PolygonRef result_line = result_lines.newPoly(); - result_line.add(first_vertex); - result_line.add(previous_vertex); + InfillLineSegment* old_line = current_infill_line; + if (current_infill_line->previous) + { + current_infill_line->swapDirection(); + } + current_infill_line->appendTo(result_line); + previous_vertex = current_infill_line->end; + current_infill_line = current_infill_line->next; delete old_line; while (current_infill_line) { old_line = current_infill_line; // We'll delete this after we've traversed to the next line. - const Point next_vertex = (previous_vertex == current_infill_line->start) ? current_infill_line->end : current_infill_line->start; // Opposite side of the line. - current_infill_line = (previous_vertex == current_infill_line->start) ? current_infill_line->next : current_infill_line->previous; - result_line.add(next_vertex); + if (previous_vertex != current_infill_line->start) + { + current_infill_line->swapDirection(); + } + const Point next_vertex = current_infill_line->end; // Opposite side of the line. + constexpr bool polyline_break = false; + current_infill_line->appendTo(result_line, polyline_break); + current_infill_line = current_infill_line->next; previous_vertex = next_vertex; delete old_line; } @@ -927,4 +1044,29 @@ bool Infill::InfillLineSegment::operator==(const InfillLineSegment& other) const return start == other.start && end == other.end; } +void Infill::InfillLineSegment::swapDirection() +{ + std::swap(start, end); + std::swap(altered_start, altered_end); + std::swap(start_bend, end_bend); + std::swap(next, previous); +} + +void Infill::InfillLineSegment::appendTo(PolygonRef& result_polyline, const bool include_start) +{ + if (include_start) + { + result_polyline.add(altered_start); + } + if (start_bend.has_value()) + { + result_polyline.add(start_bend.value()); + } + if (end_bend.has_value()) + { + result_polyline.add(end_bend.value()); + } + result_polyline.add(altered_end); +} + } // namespace cura diff --git a/src/infill/LightningTreeNode.cpp b/src/infill/LightningTreeNode.cpp index 33af29678c..cdde2d374d 100644 --- a/src/infill/LightningTreeNode.cpp +++ b/src/infill/LightningTreeNode.cpp @@ -1,5 +1,5 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "infill/LightningTreeNode.h" @@ -7,13 +7,15 @@ using namespace cura; +using LightningTreeNodeSPtr = std::shared_ptr; + coord_t LightningTreeNode::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const { constexpr coord_t min_valence_for_boost = 0; constexpr coord_t max_valence_for_boost = 4; constexpr coord_t valence_boost_multiplier = 4; - const size_t valence = (!is_root) + children.size(); + const size_t valence = (! is_root) + children.size(); const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0; const coord_t dist_here = vSize(getLocation() - unsupported_location); return dist_here - valence_boost; @@ -27,7 +29,8 @@ bool LightningTreeNode::hasOffspring(const LightningTreeNodeSPtr& to_be_checked) } for (auto& child_ptr : children) { - if (child_ptr->hasOffspring(to_be_checked)) return true; + if (child_ptr->hasOffspring(to_be_checked)) + return true; } return false; } @@ -52,22 +55,20 @@ LightningTreeNodeSPtr LightningTreeNode::addChild(const Point& child_loc) LightningTreeNodeSPtr LightningTreeNode::addChild(LightningTreeNodeSPtr& new_child) { assert(new_child != shared_from_this()); - //assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio. + // assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio. children.push_back(new_child); new_child->parent = shared_from_this(); new_child->is_root = false; return new_child; } -void LightningTreeNode::propagateToNextLayer -( +void LightningTreeNode::propagateToNextLayer( std::vector& next_trees, const Polygons& next_outlines, const LocToLineGrid& outline_locator, const coord_t prune_distance, const coord_t smooth_magnitude, - const coord_t max_remove_colinear_dist -) const + const coord_t max_remove_colinear_dist) const { auto tree_below = deepCopy(); @@ -103,10 +104,11 @@ void LightningTreeNode::visitNodes(const std::function& last_grounding_location /*= std::nullopt*/) -: is_root(true) -, p(p) -, last_grounding_location(last_grounding_location) -{} + : is_root(true) + , p(p) + , last_grounding_location(last_grounding_location) +{ +} LightningTreeNodeSPtr LightningTreeNode::deepCopy() const { @@ -167,12 +169,7 @@ LightningTreeNodeSPtr LightningTreeNode::closestNode(const Point& loc) return result; } -bool LightningTreeNode::realign -( - const Polygons& outlines, - const LocToLineGrid& outline_locator, - std::vector& rerooted_parts -) +bool LightningTreeNode::realign(const Polygons& outlines, const LocToLineGrid& outline_locator, std::vector& rerooted_parts) { if (outlines.empty()) { @@ -184,8 +181,7 @@ bool LightningTreeNode::realign // Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion: Point coll; bool reground_me = false; - const auto remove_unconnected_func - { + const auto remove_unconnected_func{ [&](const LightningTreeNodeSPtr& child) { bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts); @@ -231,13 +227,8 @@ void LightningTreeNode::straighten(const coord_t magnitude, const coord_t max_re straighten(magnitude, p, 0, max_remove_colinear_dist * max_remove_colinear_dist); } -LightningTreeNode::RectilinearJunction LightningTreeNode::straighten -( - const coord_t magnitude, - const Point& junction_above, - const coord_t accumulated_dist, - const coord_t max_remove_colinear_dist2 -) +LightningTreeNode::RectilinearJunction + LightningTreeNode::straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2) { constexpr coord_t junction_magnitude_factor_numerator = 3; constexpr coord_t junction_magnitude_factor_denominator = 4; @@ -267,14 +258,10 @@ LightningTreeNode::RectilinearJunction LightningTreeNode::straighten { // remove nodes on linear segments constexpr coord_t close_enough = 10; - child_p = children.front(); //recursive call to straighten might have removed the child + child_p = children.front(); // recursive call to straighten might have removed the child const LightningTreeNodeSPtr& parent_node = parent.lock(); - if - ( - parent_node && - vSize2(child_p->p - parent_node->p) < max_remove_colinear_dist2 && - LinearAlg2D::getDist2FromLineSegment(parent_node->p, p, child_p->p) < close_enough - ) + if (parent_node && vSize2(child_p->p - parent_node->p) < max_remove_colinear_dist2 + && LinearAlg2D::getDist2FromLineSegment(parent_node->p, p, child_p->p) < close_enough) { child_p->parent = parent; for (auto& sibling : parent_node->children) @@ -327,7 +314,7 @@ coord_t LightningTreeNode::prune(const coord_t& pruning_distance) } coord_t max_distance_pruned = 0; - for (auto child_it = children.begin(); child_it != children.end(); ) + for (auto child_it = children.begin(); child_it != children.end();) { auto& child = *child_it; coord_t dist_pruned_child = child->prune(pruning_distance); @@ -401,7 +388,7 @@ void LightningTreeNode::convertToPolylines(size_t long_line_idx, Polygons& outpu void LightningTreeNode::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const { const coord_t reduction = line_width / 2; // TODO make configurable? - for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) + for (auto poly_it = result_lines.begin(); poly_it != result_lines.end();) { PolygonRef polyline = *poly_it; if (polyline.size() <= 1) diff --git a/src/infill/SubDivCube.cpp b/src/infill/SubDivCube.cpp index 20e4a3f0e5..d81b92d0e0 100644 --- a/src/infill/SubDivCube.cpp +++ b/src/infill/SubDivCube.cpp @@ -1,19 +1,19 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "infill/SubDivCube.h" -#include - -#include "sliceDataStorage.h" #include "settings/types/Angle.h" //For the infill angle. +#include "sliceDataStorage.h" #include "utils/math.h" #include "utils/polygonUtils.h" -#define ONE_OVER_SQRT_2 0.7071067811865475244008443621048490392848359376884740 //1 / sqrt(2) -#define ONE_OVER_SQRT_3 0.577350269189625764509148780501957455647601751270126876018 //1 / sqrt(3) -#define ONE_OVER_SQRT_6 0.408248290463863016366214012450981898660991246776111688072 //1 / sqrt(6) -#define SQRT_TWO_THIRD 0.816496580927726032732428024901963797321982493552223376144 //sqrt(2 / 3) +#include + +#define ONE_OVER_SQRT_2 0.7071067811865475244008443621048490392848359376884740 // 1 / sqrt(2) +#define ONE_OVER_SQRT_3 0.577350269189625764509148780501957455647601751270126876018 // 1 / sqrt(3) +#define ONE_OVER_SQRT_6 0.408248290463863016366214012450981898660991246776111688072 // 1 / sqrt(6) +#define SQRT_TWO_THIRD 0.816496580927726032732428024901963797321982493552223376144 // sqrt(2 / 3) namespace cura { @@ -23,26 +23,16 @@ coord_t SubDivCube::radius_addition = 0; Point3Matrix SubDivCube::rotation_matrix; PointMatrix SubDivCube::infill_rotation_matrix; -SubDivCube::~SubDivCube() -{ - for (int child_idx = 0; child_idx < 8; child_idx++) - { - if (children[child_idx]) - { - delete children[child_idx]; - } - } -} - void SubDivCube::precomputeOctree(SliceMeshStorage& mesh, const Point& infill_origin) { radius_addition = mesh.settings.get("sub_div_rad_add"); // if infill_angles is not empty use the first value, otherwise use 0 const std::vector infill_angles = mesh.settings.get>("infill_angles"); - const AngleDegrees infill_angle = (!infill_angles.empty()) ? infill_angles[0] : AngleDegrees(0); + const AngleDegrees infill_angle = (! infill_angles.empty()) ? infill_angles[0] : AngleDegrees(0); - const coord_t furthest_dist_from_origin = std::sqrt(square(mesh.settings.get("machine_height")) + square(mesh.settings.get("machine_depth") / 2) + square(mesh.settings.get("machine_width") / 2)); + const coord_t furthest_dist_from_origin = std::sqrt( + square(mesh.settings.get("machine_height")) + square(mesh.settings.get("machine_depth") / 2) + square(mesh.settings.get("machine_width") / 2)); const coord_t max_side_length = furthest_dist_from_origin * 2; size_t curr_recursion_depth = 0; @@ -75,21 +65,27 @@ void SubDivCube::precomputeOctree(SliceMeshStorage& mesh, const Point& infill_or // / .O. \ | . // /.~' '~.\ O---->X . // X """"""""""" Y . - tilt.matrix[0] = -ONE_OVER_SQRT_2; tilt.matrix[1] = ONE_OVER_SQRT_2; tilt.matrix[2] = 0; - tilt.matrix[3] = -ONE_OVER_SQRT_6; tilt.matrix[4] = -ONE_OVER_SQRT_6; tilt.matrix[5] = SQRT_TWO_THIRD ; - tilt.matrix[6] = ONE_OVER_SQRT_3; tilt.matrix[7] = ONE_OVER_SQRT_3; tilt.matrix[8] = ONE_OVER_SQRT_3; + tilt.matrix[0] = -ONE_OVER_SQRT_2; + tilt.matrix[1] = ONE_OVER_SQRT_2; + tilt.matrix[2] = 0; + tilt.matrix[3] = -ONE_OVER_SQRT_6; + tilt.matrix[4] = -ONE_OVER_SQRT_6; + tilt.matrix[5] = SQRT_TWO_THIRD; + tilt.matrix[6] = ONE_OVER_SQRT_3; + tilt.matrix[7] = ONE_OVER_SQRT_3; + tilt.matrix[8] = ONE_OVER_SQRT_3; infill_rotation_matrix = PointMatrix(infill_angle); Point3Matrix infill_angle_mat(infill_rotation_matrix); rotation_matrix = infill_angle_mat.compose(tilt); - mesh.base_subdiv_cube = new SubDivCube(mesh, center, curr_recursion_depth - 1); + mesh.base_subdiv_cube = std::make_shared(mesh, center, curr_recursion_depth - 1); } void SubDivCube::generateSubdivisionLines(const coord_t z, Polygons& result) { - if (cube_properties_per_recursion_step.empty()) //Infill is set to 0%. + if (cube_properties_per_recursion_step.empty()) // Infill is set to 0%. { return; } @@ -126,7 +122,7 @@ void SubDivCube::generateSubdivisionLines(const coord_t z, Polygons (&directiona relative_b.Y = relative_a.Y; rotatePointInitial(relative_a); rotatePointInitial(relative_b); - for (int dir_idx = 0; dir_idx < 3; dir_idx++)//!< draw the line, then rotate 120 degrees. + for (int dir_idx = 0; dir_idx < 3; dir_idx++) //!< draw the line, then rotate 120 degrees. { a.X = center.x + relative_a.X; a.Y = center.y + relative_a.Y; @@ -158,7 +154,7 @@ SubDivCube::SubDivCube(SliceMeshStorage& mesh, Point3& center, size_t depth) { return; } - if (depth >= cube_properties_per_recursion_step.size()) //Depth is out of bounds of what we pre-computed. + if (depth >= cube_properties_per_recursion_step.size()) // Depth is out of bounds of what we pre-computed. { return; } @@ -182,7 +178,7 @@ SubDivCube::SubDivCube(SliceMeshStorage& mesh, Point3& center, size_t depth) child_center = center + rotation_matrix.apply(rel_child_center * int32_t(cube_properties.side_length / 4)); if (isValidSubdivision(mesh, child_center, radius)) { - children[child_nr] = new SubDivCube(mesh, child_center, depth - 1); + children[child_nr] = std::make_shared(mesh, child_center, depth - 1); child_nr++; } } @@ -191,17 +187,17 @@ SubDivCube::SubDivCube(SliceMeshStorage& mesh, Point3& center, size_t depth) bool SubDivCube::isValidSubdivision(SliceMeshStorage& mesh, Point3& center, coord_t radius) { coord_t distance2 = 0; - coord_t sphere_slice_radius2;//!< squared radius of bounding sphere slice on target layer + coord_t sphere_slice_radius2; //!< squared radius of bounding sphere slice on target layer bool inside_somewhere = false; bool outside_somewhere = false; int inside; - Ratio part_dist;//what percentage of the radius the target layer is away from the center along the z axis. 0 - 1 + Ratio part_dist; // what percentage of the radius the target layer is away from the center along the z axis. 0 - 1 const coord_t layer_height = mesh.settings.get("layer_height"); int bottom_layer = (center.z - radius) / layer_height; int top_layer = (center.z + radius) / layer_height; for (int test_layer = bottom_layer; test_layer <= top_layer; test_layer += 3) // steps of three. Low-hanging speed gain. { - part_dist = static_cast(test_layer * layer_height - center.z) / radius; + part_dist = Ratio{ static_cast(test_layer * layer_height - center.z) } / radius; sphere_slice_radius2 = radius * radius * (1.0 - (part_dist * part_dist)); Point loc(center.x, center.y); @@ -259,7 +255,7 @@ void SubDivCube::rotatePointInitial(Point& target) void SubDivCube::rotatePoint120(Point& target) { - //constexpr double sqrt_three_fourths = sqrt(3.0 / 4.0); //TODO: Reactivate once MacOS is upgraded to a more modern compiler. + // constexpr double sqrt_three_fourths = sqrt(3.0 / 4.0); //TODO: Reactivate once MacOS is upgraded to a more modern compiler. #define sqrt_three_fourths 0.86602540378443864676372317 const coord_t x = -0.5 * target.X - sqrt_three_fourths * target.Y; target.Y = -0.5 * target.Y + sqrt_three_fourths * target.X; @@ -289,4 +285,4 @@ void SubDivCube::addLineAndCombine(Polygons& group, Point from, Point to) group.addLine(from, to); } -}//namespace cura +} // namespace cura diff --git a/src/multiVolumes.cpp b/src/multiVolumes.cpp index 25a4147d65..8241d777e9 100644 --- a/src/multiVolumes.cpp +++ b/src/multiVolumes.cpp @@ -1,56 +1,53 @@ -//Copyright (c) 2021 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "multiVolumes.h" -#include - #include "Application.h" #include "Slice.h" +#include "settings/EnumSettings.h" +#include "settings/types/LayerIndex.h" #include "slicer.h" #include "utils/PolylineStitcher.h" -#include "settings/EnumSettings.h" -namespace cura +#include + +namespace cura { - -void carveMultipleVolumes(std::vector &volumes) + +void carveMultipleVolumes(std::vector& volumes) { - //Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas. + // Go trough all the volumes, and remove the previous volume outlines from our own outline, so we never have overlapped areas. const bool alternate_carve_order = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("alternate_carve_order"); std::vector ranked_volumes = volumes; - std::sort(ranked_volumes.begin(), ranked_volumes.end(), - [](Slicer* volume_1, Slicer* volume_2) - { - return volume_1->mesh->settings.get("infill_mesh_order") < volume_2->mesh->settings.get("infill_mesh_order"); - } ); + std::sort( + ranked_volumes.begin(), + ranked_volumes.end(), + [](Slicer* volume_1, Slicer* volume_2) + { + return volume_1->mesh->settings.get("infill_mesh_order") < volume_2->mesh->settings.get("infill_mesh_order"); + }); for (unsigned int volume_1_idx = 1; volume_1_idx < volumes.size(); volume_1_idx++) { Slicer& volume_1 = *ranked_volumes[volume_1_idx]; - if (volume_1.mesh->settings.get("infill_mesh") - || volume_1.mesh->settings.get("anti_overhang_mesh") - || volume_1.mesh->settings.get("support_mesh") - || volume_1.mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE - ) + if (volume_1.mesh->settings.get("infill_mesh") || volume_1.mesh->settings.get("anti_overhang_mesh") || volume_1.mesh->settings.get("support_mesh") + || volume_1.mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; } for (unsigned int volume_2_idx = 0; volume_2_idx < volume_1_idx; volume_2_idx++) { Slicer& volume_2 = *ranked_volumes[volume_2_idx]; - if (volume_2.mesh->settings.get("infill_mesh") - || volume_2.mesh->settings.get("anti_overhang_mesh") - || volume_2.mesh->settings.get("support_mesh") - || volume_2.mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE - ) + if (volume_2.mesh->settings.get("infill_mesh") || volume_2.mesh->settings.get("anti_overhang_mesh") || volume_2.mesh->settings.get("support_mesh") + || volume_2.mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::SURFACE) { continue; } - if (!volume_1.mesh->getAABB().hit(volume_2.mesh->getAABB())) + if (! volume_1.mesh->getAABB().hit(volume_2.mesh->getAABB())) { continue; } - for (unsigned int layerNr = 0; layerNr < volume_1.layers.size(); layerNr++) + for (LayerIndex layerNr = 0; layerNr < volume_1.layers.size(); layerNr++) { SlicerLayer& layer1 = volume_1.layers[layerNr]; SlicerLayer& layer2 = volume_2.layers[layerNr]; @@ -66,10 +63,10 @@ void carveMultipleVolumes(std::vector &volumes) } } } - -//Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes. -//This generates some overlap in dual extrusion, for better bonding in touching parts. -void generateMultipleVolumesOverlap(std::vector &volumes) + +// Expand each layer a bit and then keep the extra overlapping parts that overlap with other volumes. +// This generates some overlap in dual extrusion, for better bonding in touching parts. +void generateMultipleVolumesOverlap(std::vector& volumes) { if (volumes.size() < 2) { @@ -82,26 +79,20 @@ void generateMultipleVolumesOverlap(std::vector &volumes) ClipperLib::PolyFillType fill_type = volume->mesh->settings.get("meshfix_union_all") ? ClipperLib::pftNonZero : ClipperLib::pftEvenOdd; coord_t overlap = volume->mesh->settings.get("multiple_mesh_overlap"); - if (volume->mesh->settings.get("infill_mesh") - || volume->mesh->settings.get("anti_overhang_mesh") - || volume->mesh->settings.get("support_mesh") + if (volume->mesh->settings.get("infill_mesh") || volume->mesh->settings.get("anti_overhang_mesh") || volume->mesh->settings.get("support_mesh") || overlap == 0) { continue; } AABB3D aabb(volume->mesh->getAABB()); aabb.expandXY(overlap); // expand to account for the case where two models and their bounding boxes are adjacent along the X or Y-direction - for (unsigned int layer_nr = 0; layer_nr < volume->layers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < volume->layers.size(); layer_nr++) { Polygons all_other_volumes; for (Slicer* other_volume : volumes) { - if (other_volume->mesh->settings.get("infill_mesh") - || other_volume->mesh->settings.get("anti_overhang_mesh") - || other_volume->mesh->settings.get("support_mesh") - || !other_volume->mesh->getAABB().hit(aabb) - || other_volume == volume - ) + if (other_volume->mesh->settings.get("infill_mesh") || other_volume->mesh->settings.get("anti_overhang_mesh") + || other_volume->mesh->settings.get("support_mesh") || ! other_volume->mesh->getAABB().hit(aabb) || other_volume == volume) { continue; } @@ -120,12 +111,12 @@ void MultiVolumes::carveCuttingMeshes(std::vector& volumes, const std:: for (unsigned int carving_mesh_idx = 0; carving_mesh_idx < volumes.size(); carving_mesh_idx++) { const Mesh& cutting_mesh = meshes[carving_mesh_idx]; - if (!cutting_mesh.settings.get("cutting_mesh")) + if (! cutting_mesh.settings.get("cutting_mesh")) { continue; } Slicer& cutting_mesh_volume = *volumes[carving_mesh_idx]; - for (unsigned int layer_nr = 0; layer_nr < cutting_mesh_volume.layers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < cutting_mesh_volume.layers.size(); layer_nr++) { Polygons& cutting_mesh_polygons = cutting_mesh_volume.layers[layer_nr].polygons; Polygons& cutting_mesh_polylines = cutting_mesh_volume.layers[layer_nr].openPolylines; @@ -156,15 +147,14 @@ void MultiVolumes::carveCuttingMeshes(std::vector& volumes, const std:: cutting_mesh_area = &cutting_mesh_polygons; } } - + Polygons new_outlines; Polygons new_polylines; for (unsigned int carved_mesh_idx = 0; carved_mesh_idx < volumes.size(); carved_mesh_idx++) { const Mesh& carved_mesh = meshes[carved_mesh_idx]; - //Do not apply cutting_mesh for meshes which have settings (cutting_mesh, anti_overhang_mesh, support_mesh). - if (carved_mesh.settings.get("cutting_mesh") || carved_mesh.settings.get("anti_overhang_mesh") - || carved_mesh.settings.get("support_mesh")) + // Do not apply cutting_mesh for meshes which have settings (cutting_mesh, anti_overhang_mesh, support_mesh). + if (carved_mesh.settings.get("cutting_mesh") || carved_mesh.settings.get("anti_overhang_mesh") || carved_mesh.settings.get("support_mesh")) { continue; } @@ -191,4 +181,4 @@ void MultiVolumes::carveCuttingMeshes(std::vector& volumes, const std:: } -}//namespace cura +} // namespace cura diff --git a/src/pathPlanning/Comb.cpp b/src/pathPlanning/Comb.cpp index f9cd07ed08..eda5d5bc1d 100644 --- a/src/pathPlanning/Comb.cpp +++ b/src/pathPlanning/Comb.cpp @@ -1,30 +1,30 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "pathPlanning/Comb.h" -#include -#include // function -#include - -#include "pathPlanning/CombPaths.h" -#include "pathPlanning/LinePolygonsCrossings.h" #include "Application.h" #include "ExtruderTrain.h" #include "Slice.h" -#include "utils/linearAlg2D.h" -#include "utils/PolygonsPointIndex.h" +#include "pathPlanning/CombPaths.h" +#include "pathPlanning/LinePolygonsCrossings.h" #include "sliceDataStorage.h" +#include "utils/PolygonsPointIndex.h" #include "utils/SVG.h" +#include "utils/linearAlg2D.h" -namespace cura { +#include +#include // function +#include + +namespace cura +{ LocToLineGrid& Comb::getOutsideLocToLine(const ExtruderTrain& train) { if (outside_loc_to_line[train.extruder_nr] == nullptr) { - outside_loc_to_line[train.extruder_nr] = - PolygonUtils::createLocToLineGrid(getBoundaryOutside(train), offset_from_inside_to_outside * 3 / 2); + outside_loc_to_line[train.extruder_nr] = PolygonUtils::createLocToLineGrid(getBoundaryOutside(train), offset_from_inside_to_outside * 3 / 2); } return *outside_loc_to_line[train.extruder_nr]; } @@ -34,8 +34,7 @@ Polygons& Comb::getBoundaryOutside(const ExtruderTrain& train) if (boundary_outside[train.extruder_nr].empty()) { bool travel_avoid_supports = train.settings.get("travel_avoid_supports"); - boundary_outside[train.extruder_nr] = - storage.getLayerOutlines(layer_nr, travel_avoid_supports, travel_avoid_supports).offset(travel_avoid_distance); + boundary_outside[train.extruder_nr] = storage.getLayerOutlines(layer_nr, travel_avoid_supports, travel_avoid_supports).offset(travel_avoid_distance); } return boundary_outside[train.extruder_nr]; } @@ -45,8 +44,7 @@ Polygons& Comb::getModelBoundary(const ExtruderTrain& train) if (model_boundary[train.extruder_nr].empty()) { bool travel_avoid_supports = train.settings.get("travel_avoid_supports"); - model_boundary[train.extruder_nr] = - storage.getLayerOutlines(layer_nr, travel_avoid_supports, travel_avoid_supports); + model_boundary[train.extruder_nr] = storage.getLayerOutlines(layer_nr, travel_avoid_supports, travel_avoid_supports); } return boundary_outside[train.extruder_nr]; } @@ -55,32 +53,39 @@ LocToLineGrid& Comb::getModelBoundaryLocToLine(const ExtruderTrain& train) { if (model_boundary_loc_to_line[train.extruder_nr] == nullptr) { - model_boundary_loc_to_line[train.extruder_nr] = - PolygonUtils::createLocToLineGrid(getModelBoundary(train), offset_from_inside_to_outside * 3 / 2); + model_boundary_loc_to_line[train.extruder_nr] = PolygonUtils::createLocToLineGrid(getModelBoundary(train), offset_from_inside_to_outside * 3 / 2); } return *model_boundary_loc_to_line[train.extruder_nr]; } -Comb::Comb(const SliceDataStorage& storage, const LayerIndex layer_nr, const Polygons& comb_boundary_inside_minimum, const Polygons& comb_boundary_inside_optimal, coord_t comb_boundary_offset, coord_t travel_avoid_distance, coord_t move_inside_distance) -: storage(storage) -, layer_nr(layer_nr) -, offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls -, max_moveInside_distance2(offset_from_outlines * offset_from_outlines) -, offset_from_inside_to_outside(offset_from_outlines + travel_avoid_distance) -, max_crossing_dist2(offset_from_inside_to_outside * offset_from_inside_to_outside * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation -, boundary_inside_minimum( comb_boundary_inside_minimum ) // copy the boundary, because the partsView_inside will reorder the polygons -, boundary_inside_optimal( comb_boundary_inside_optimal ) // copy the boundary, because the partsView_inside will reorder the polygons -, partsView_inside_minimum( boundary_inside_minimum.splitIntoPartsView() ) // WARNING !! changes the order of boundary_inside !! -, partsView_inside_optimal( boundary_inside_optimal.splitIntoPartsView() ) // WARNING !! changes the order of boundary_inside !! -, inside_loc_to_line_minimum(PolygonUtils::createLocToLineGrid(boundary_inside_minimum, comb_boundary_offset)) -, inside_loc_to_line_optimal(PolygonUtils::createLocToLineGrid(boundary_inside_optimal, comb_boundary_offset)) -, move_inside_distance(move_inside_distance) -, travel_avoid_distance(travel_avoid_distance) +Comb::Comb( + const SliceDataStorage& storage, + const LayerIndex layer_nr, + const Polygons& comb_boundary_inside_minimum, + const Polygons& comb_boundary_inside_optimal, + coord_t comb_boundary_offset, + coord_t travel_avoid_distance, + coord_t move_inside_distance) + : storage(storage) + , layer_nr(layer_nr) + , offset_from_outlines(comb_boundary_offset) // between second wall and infill / other walls + , max_moveInside_distance2(offset_from_outlines * offset_from_outlines) + , offset_from_inside_to_outside(offset_from_outlines + travel_avoid_distance) + , max_crossing_dist2( + offset_from_inside_to_outside * offset_from_inside_to_outside + * 2) // so max_crossing_dist = offset_from_inside_to_outside * sqrt(2) =approx 1.5 to allow for slightly diagonal crossings and slightly inaccurate crossing computation + , boundary_inside_minimum(comb_boundary_inside_minimum) // copy the boundary, because the partsView_inside will reorder the polygons + , boundary_inside_optimal(comb_boundary_inside_optimal) // copy the boundary, because the partsView_inside will reorder the polygons + , partsView_inside_minimum(boundary_inside_minimum.splitIntoPartsView()) // WARNING !! changes the order of boundary_inside !! + , partsView_inside_optimal(boundary_inside_optimal.splitIntoPartsView()) // WARNING !! changes the order of boundary_inside !! + , inside_loc_to_line_minimum(PolygonUtils::createLocToLineGrid(boundary_inside_minimum, comb_boundary_offset)) + , inside_loc_to_line_optimal(PolygonUtils::createLocToLineGrid(boundary_inside_optimal, comb_boundary_offset)) + , move_inside_distance(move_inside_distance) + , travel_avoid_distance(travel_avoid_distance) { } -bool Comb::calc -( +bool Comb::calc( bool perform_z_hops, bool perform_z_hops_only_when_collides, const ExtruderTrain& train, // NOTE: USe for travel settings and 'extruder-nr' only, don't use for z-hop/retraction/wipe settings, as that should also be settable per mesh! @@ -90,15 +95,14 @@ bool Comb::calc bool _start_inside, bool _end_inside, coord_t max_comb_distance_ignored, - bool &unretract_before_last_travel_move -) + bool& unretract_before_last_travel_move) { - if(shorterThen(end_point - start_point, max_comb_distance_ignored)) + if (shorterThen(end_point - start_point, max_comb_distance_ignored)) { return true; } const Point travel_end_point_before_combing = end_point; - //Move start and end point inside the optimal comb boundary + // Move start and end point inside the optimal comb boundary unsigned int start_inside_poly = NO_INDEX; const bool start_inside = moveInside(boundary_inside_optimal, _start_inside, inside_loc_to_line_optimal.get(), start_point, start_inside_poly); @@ -107,8 +111,8 @@ bool Comb::calc unsigned int start_part_boundary_poly_idx = NO_INDEX; // Added initial value to stop MSVC throwing an exception in debug mode unsigned int end_part_boundary_poly_idx = NO_INDEX; - unsigned int start_part_idx = (start_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside_optimal.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx); - unsigned int end_part_idx = (end_inside_poly == NO_INDEX)? NO_INDEX : partsView_inside_optimal.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx); + unsigned int start_part_idx = (start_inside_poly == NO_INDEX) ? NO_INDEX : partsView_inside_optimal.getPartContaining(start_inside_poly, &start_part_boundary_poly_idx); + unsigned int end_part_idx = (end_inside_poly == NO_INDEX) ? NO_INDEX : partsView_inside_optimal.getPartContaining(end_inside_poly, &end_part_boundary_poly_idx); const bool fail_on_unavoidable_obstacles = perform_z_hops && perform_z_hops_only_when_collides; @@ -117,7 +121,15 @@ bool Comb::calc { PolygonsPart part = partsView_inside_optimal.assemblePart(start_part_idx); comb_paths.emplace_back(); - const bool combing_succeeded = LinePolygonsCrossings::comb(part, *inside_loc_to_line_optimal, start_point, end_point, comb_paths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles); + const bool combing_succeeded = LinePolygonsCrossings::comb( + part, + *inside_loc_to_line_optimal, + start_point, + end_point, + comb_paths.back(), + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_on_unavoidable_obstacles); // If the endpoint of the travel path changes with combing, then it means that we are moving to an outer wall // and we should unretract before the last travel move when travelling to that outer wall unretract_before_last_travel_move = combing_succeeded && end_point != travel_end_point_before_combing; @@ -131,10 +143,11 @@ bool Comb::calc unsigned int end_inside_poly_min = NO_INDEX; const bool end_inside_min = moveInside(boundary_inside_minimum, _end_inside, inside_loc_to_line_minimum.get(), end_point, end_inside_poly_min); - unsigned int start_part_boundary_poly_idx_min; - unsigned int end_part_boundary_poly_idx_min; - unsigned int start_part_idx_min = (start_inside_poly_min == NO_INDEX)? NO_INDEX : partsView_inside_minimum.getPartContaining(start_inside_poly_min, &start_part_boundary_poly_idx_min); - unsigned int end_part_idx_min = (end_inside_poly_min == NO_INDEX)? NO_INDEX : partsView_inside_minimum.getPartContaining(end_inside_poly_min, &end_part_boundary_poly_idx_min); + unsigned int start_part_boundary_poly_idx_min{}; + unsigned int end_part_boundary_poly_idx_min{}; + unsigned int start_part_idx_min + = (start_inside_poly_min == NO_INDEX) ? NO_INDEX : partsView_inside_minimum.getPartContaining(start_inside_poly_min, &start_part_boundary_poly_idx_min); + unsigned int end_part_idx_min = (end_inside_poly_min == NO_INDEX) ? NO_INDEX : partsView_inside_minimum.getPartContaining(end_inside_poly_min, &end_part_boundary_poly_idx_min); CombPath result_path; bool comb_result; @@ -145,8 +158,16 @@ bool Comb::calc PolygonsPart part = partsView_inside_minimum.assemblePart(start_part_idx_min); comb_paths.emplace_back(); - comb_result = LinePolygonsCrossings::comb(part, *inside_loc_to_line_minimum, start_point, end_point, result_path, -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles); - Comb::moveCombPathInside(boundary_inside_minimum, boundary_inside_optimal, result_path, comb_paths.back()); // add altered result_path to combPaths.back() + comb_result = LinePolygonsCrossings::comb( + part, + *inside_loc_to_line_minimum, + start_point, + end_point, + result_path, + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_on_unavoidable_obstacles); + Comb::moveCombPathInside(boundary_inside_minimum, boundary_inside_optimal, result_path, comb_paths.back()); // add altered result_path to combPaths.back() // If the endpoint of the travel path changes with combing, then it means that we are moving to an outer wall // and we should unretract before the last travel move when travelling to that outer wall unretract_before_last_travel_move = comb_result && end_point != travel_end_point_before_combing; @@ -160,13 +181,13 @@ bool Comb::calc // when start_point is inside crossing_1_in is of interest // when it is in between inside and outside it is equal to crossing_1_mid - if (perform_z_hops && !perform_z_hops_only_when_collides) //Combing via outside makes combing fail. + if (perform_z_hops && ! perform_z_hops_only_when_collides) // Combing via outside makes combing fail. { return false; } - //Find the crossings using the minimum comb boundary, since it's guaranteed to be as close as we can get to the destination. - //Getting as close as possible prevents exiting the polygon in the wrong direction (e.g. into a hole instead of to the outside). + // Find the crossings using the minimum comb boundary, since it's guaranteed to be as close as we can get to the destination. + // Getting as close as possible prevents exiting the polygon in the wrong direction (e.g. into a hole instead of to the outside). Crossing start_crossing(start_point, start_inside_min, start_part_idx_min, start_part_boundary_poly_idx_min, boundary_inside_minimum, *inside_loc_to_line_minimum); Crossing end_crossing(end_point, end_inside_min, end_part_idx_min, end_part_boundary_poly_idx_min, boundary_inside_minimum, *inside_loc_to_line_minimum); @@ -183,17 +204,17 @@ bool Comb::calc const bool travel_avoid_other_parts = train.settings.get("travel_avoid_other_parts"); - if (travel_avoid_other_parts && !skip_avoid_other_parts_path) + if (travel_avoid_other_parts && ! skip_avoid_other_parts_path) { // compute the crossing points when moving through air // comb through all air, since generally the outside consists of a single part bool success = start_crossing.findOutside(train, getBoundaryOutside(train), end_crossing.in_or_mid, fail_on_unavoidable_obstacles, *this); - if (!success) + if (! success) { return false; } success = end_crossing.findOutside(train, getBoundaryOutside(train), start_crossing.out, fail_on_unavoidable_obstacles, *this); - if (!success) + if (! success) { return false; } @@ -205,25 +226,43 @@ bool Comb::calc // start to boundary assert(start_crossing.dest_part.size() > 0 && "The part we start inside when combing should have been computed already!"); comb_paths.emplace_back(); - //If we're inside the optimal bound, first try the optimal combing path. If it fails, use the minimum path instead. + // If we're inside the optimal bound, first try the optimal combing path. If it fails, use the minimum path instead. constexpr bool fail_for_optimum_bound = true; - bool combing_succeeded = start_inside && LinePolygonsCrossings::comb(boundary_inside_optimal, *inside_loc_to_line_optimal, start_point, start_crossing.in_or_mid, comb_paths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_for_optimum_bound); - if(!combing_succeeded) + bool combing_succeeded = start_inside + && LinePolygonsCrossings::comb( + boundary_inside_optimal, + *inside_loc_to_line_optimal, + start_point, + start_crossing.in_or_mid, + comb_paths.back(), + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_for_optimum_bound); + if (! combing_succeeded) { - combing_succeeded = LinePolygonsCrossings::comb(start_crossing.dest_part, *inside_loc_to_line_minimum, start_point, start_crossing.in_or_mid, comb_paths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles); + combing_succeeded = LinePolygonsCrossings::comb( + start_crossing.dest_part, + *inside_loc_to_line_minimum, + start_point, + start_crossing.in_or_mid, + comb_paths.back(), + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_on_unavoidable_obstacles); } - if (!combing_succeeded) - { // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon + if (! combing_succeeded) + { // Couldn't comb between start point and computed crossing from the start part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside + // the polygon return false; } } // through air from boundary to boundary - if (travel_avoid_other_parts && !skip_avoid_other_parts_path) + if (travel_avoid_other_parts && ! skip_avoid_other_parts_path) { comb_paths.emplace_back(); comb_paths.throughAir = true; - if ( vSize(start_crossing.in_or_mid - end_crossing.in_or_mid) < vSize(start_crossing.in_or_mid - start_crossing.out) + vSize(end_crossing.in_or_mid - end_crossing.out) ) + if (vSize(start_crossing.in_or_mid - end_crossing.in_or_mid) < vSize(start_crossing.in_or_mid - start_crossing.out) + vSize(end_crossing.in_or_mid - end_crossing.out)) { // via outside is moving more over the in-between zone comb_paths.back().push_back(start_crossing.in_or_mid); comb_paths.back().push_back(end_crossing.in_or_mid); @@ -231,7 +270,15 @@ bool Comb::calc else { CombPath tmp_comb_path; - bool combing_succeeded = LinePolygonsCrossings::comb(getBoundaryOutside(train), getOutsideLocToLine(train), start_crossing.out, end_crossing.out, tmp_comb_path, offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, true); + bool combing_succeeded = LinePolygonsCrossings::comb( + getBoundaryOutside(train), + getOutsideLocToLine(train), + start_crossing.out, + end_crossing.out, + tmp_comb_path, + offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + true); if (combing_succeeded) { @@ -288,18 +335,36 @@ bool Comb::calc // boundary to end assert(end_crossing.dest_part.size() > 0 && "The part we end up inside when combing should have been computed already!"); comb_paths.emplace_back(); - //If we're inside the optimal bound, first try the optimal combing path. If it fails, use the minimum path instead. + // If we're inside the optimal bound, first try the optimal combing path. If it fails, use the minimum path instead. constexpr bool fail_for_optimum_bound = true; - bool combing_succeeded = end_inside && LinePolygonsCrossings::comb(boundary_inside_optimal, *inside_loc_to_line_optimal, end_crossing.in_or_mid, end_point, comb_paths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_for_optimum_bound); - if(!combing_succeeded) + bool combing_succeeded = end_inside + && LinePolygonsCrossings::comb( + boundary_inside_optimal, + *inside_loc_to_line_optimal, + end_crossing.in_or_mid, + end_point, + comb_paths.back(), + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_for_optimum_bound); + if (! combing_succeeded) { - combing_succeeded = LinePolygonsCrossings::comb(end_crossing.dest_part, *inside_loc_to_line_minimum, end_crossing.in_or_mid, end_point, comb_paths.back(), -offset_dist_to_get_from_on_the_polygon_to_outside, max_comb_distance_ignored, fail_on_unavoidable_obstacles); + combing_succeeded = LinePolygonsCrossings::comb( + end_crossing.dest_part, + *inside_loc_to_line_minimum, + end_crossing.in_or_mid, + end_point, + comb_paths.back(), + -offset_dist_to_get_from_on_the_polygon_to_outside, + max_comb_distance_ignored, + fail_on_unavoidable_obstacles); } // If the endpoint of the travel path changes with combing, then it means that we are moving to an outer wall // and we should unretract before the last travel move when traveling to that outer wall unretract_before_last_travel_move = combing_succeeded && end_point != travel_end_point_before_combing; - if (!combing_succeeded) - { // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the polygon + if (! combing_succeeded) + { // Couldn't comb between end point and computed crossing to the end part! Happens for very thin parts when the offset_to_get_off_boundary moves points to outside the + // polygon return false; } } @@ -318,7 +383,7 @@ void Comb::moveCombPathInside(Polygons& boundary_inside, Polygons& boundary_insi return; } comb_path_output.push_back(comb_path_input[0]); - for(unsigned int point_idx = 1; point_idx close_towards_start_penalty_function([_dest_point](Point candidate){ return vSize2((candidate - _dest_point) / 10); }); + std::function close_towards_start_penalty_function( + [_dest_point](Point candidate) + { + return vSize2((candidate - _dest_point) / 10); + }); dest_part = partsView_inside.assemblePart(dest_part_idx); ClosestPolygonPoint boundary_crossing_point; @@ -387,32 +463,39 @@ void Comb::Crossing::findCrossingInOrMid(const PartsView& partsView_inside, cons dest_part_poly_indices.emplace(poly_idx); } coord_t dist2_score = std::numeric_limits::max(); - std::function line_processor + std::function line_processor = [close_to, _dest_point, &boundary_crossing_point, &dist2_score, &dest_part_poly_indices](const PolygonsPointIndex& boundary_segment) + { + if (dest_part_poly_indices.find(boundary_segment.poly_idx) == dest_part_poly_indices.end()) + { // we're not looking at a polygon from the dest_part + return true; // a.k.a. continue; + } + Point closest_here = LinearAlg2D::getClosestOnLineSegment(close_to, boundary_segment.p(), boundary_segment.next().p()); + coord_t dist2_score_here = vSize2(close_to - closest_here) + vSize2(_dest_point - closest_here) / 10; + if (dist2_score_here < dist2_score) { - if (dest_part_poly_indices.find(boundary_segment.poly_idx) == dest_part_poly_indices.end()) - { // we're not looking at a polygon from the dest_part - return true; // a.k.a. continue; - } - Point closest_here = LinearAlg2D::getClosestOnLineSegment(close_to, boundary_segment.p(), boundary_segment.next().p()); - coord_t dist2_score_here = vSize2(close_to - closest_here) + vSize2(_dest_point - closest_here) / 10; - if (dist2_score_here < dist2_score) - { - dist2_score = dist2_score_here; - boundary_crossing_point = ClosestPolygonPoint(closest_here, boundary_segment.point_idx, boundary_segment.getPolygon(), boundary_segment.poly_idx); - } - return true; - }; + dist2_score = dist2_score_here; + boundary_crossing_point = ClosestPolygonPoint(closest_here, boundary_segment.point_idx, boundary_segment.getPolygon(), boundary_segment.poly_idx); + } + return true; + }; inside_loc_to_line.processLine(std::make_pair(dest_point, close_to), line_processor); } Point result(boundary_crossing_point.p()); // the inside point of the crossing - if (!boundary_crossing_point.isValid()) + if (! boundary_crossing_point.isValid()) { // no point has been found in the sparse grid result = dest_point; } - ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside(dest_part, result, boundary_crossing_point, offset_dist_to_get_from_on_the_polygon_to_outside, &boundary_inside, &inside_loc_to_line, close_towards_start_penalty_function); + ClosestPolygonPoint crossing_1_in_cp = PolygonUtils::ensureInsideOrOutside( + dest_part, + result, + boundary_crossing_point, + offset_dist_to_get_from_on_the_polygon_to_outside, + &boundary_inside, + &inside_loc_to_line, + close_towards_start_penalty_function); if (crossing_1_in_cp.isValid()) { dest_crossing_poly = crossing_1_in_cp.poly; @@ -435,7 +518,11 @@ bool Comb::Crossing::findOutside(const ExtruderTrain& train, const Polygons& out if (dest_is_inside || outside.inside(in_or_mid, true)) // start in_between { // move outside Point preferred_crossing_1_out = in_or_mid + normal(close_to - in_or_mid, comber.offset_from_inside_to_outside); - std::function close_to_penalty_function([preferred_crossing_1_out](Point candidate){ return vSize2((candidate - preferred_crossing_1_out) / 2); }); + std::function close_to_penalty_function( + [preferred_crossing_1_out](Point candidate) + { + return vSize2((candidate - preferred_crossing_1_out) / 2); + }); std::optional crossing_1_out_cpp = PolygonUtils::findClose(in_or_mid, outside, comber.getOutsideLocToLine(train), close_to_penalty_function); if (crossing_1_out_cpp) { @@ -466,7 +553,13 @@ bool Comb::Crossing::findOutside(const ExtruderTrain& train, const Polygons& out } -std::shared_ptr> Comb::Crossing::findBestCrossing(const ExtruderTrain& train, const Polygons& outside, ConstPolygonRef from, const Point estimated_start, const Point estimated_end, Comb& comber) +std::shared_ptr> Comb::Crossing::findBestCrossing( + const ExtruderTrain& train, + const Polygons& outside, + ConstPolygonRef from, + const Point estimated_start, + const Point estimated_end, + Comb& comber) { ClosestPolygonPoint* best_in = nullptr; ClosestPolygonPoint* best_out = nullptr; @@ -491,11 +584,13 @@ std::shared_ptr> Comb::Cross // the distance between an arbitrary point and the boundary may well be a couple of centimetres. // So the crossing_dist2 is about 1.000.000 while the detour_dist_2 is in the order of 400.000.000 // In the end we just want to choose between two points which have the _same_ crossing distance, modulo rounding error. - if ((!seen_close_enough_connection && detour_score < best_detour_score) // keep the best as long as we havent seen one close enough (so that we may walk along the polygon to find a closer connection from it in the code below) - || (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) // make the one which is close enough the best as soon as we see one close enough - || (seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2 && detour_score < best_detour_score)) // update to keep the best crossing which is close enough already + if ((! seen_close_enough_connection && detour_score < best_detour_score) // keep the best as long as we havent seen one close enough (so that we may walk along the polygon + // to find a closer connection from it in the code below) + || (! seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) // make the one which is close enough the best as soon as we see one close enough + || (seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2 + && detour_score < best_detour_score)) // update to keep the best crossing which is close enough already { - if (!seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) + if (! seen_close_enough_connection && crossing_dist2 <= comber.max_crossing_dist2) { seen_close_enough_connection = true; } @@ -521,4 +616,4 @@ std::shared_ptr> Comb::Cross return std::make_shared>(*best_in, *best_out); } -}//namespace cura +} // namespace cura diff --git a/src/pathPlanning/GCodePath.cpp b/src/pathPlanning/GCodePath.cpp index 3c4ed61633..5e0fa03be6 100644 --- a/src/pathPlanning/GCodePath.cpp +++ b/src/pathPlanning/GCodePath.cpp @@ -1,55 +1,34 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "pathPlanning/GCodePath.h" -#include "GCodePathConfig.h" namespace cura { -GCodePath::GCodePath(const GCodePathConfig& config, const SliceMeshStorage* mesh, const SpaceFillType space_fill_type, const Ratio flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor) : - config(&config), - mesh(mesh), - space_fill_type(space_fill_type), - flow(flow), - width_factor(width_factor), - speed_factor(speed_factor), - speed_back_pressure_factor(1.0), - retract(false), - unretract_before_last_travel_move(false), - perform_z_hop(false), - perform_prime(false), - skip_agressive_merge_hint(false), - points(std::vector()), - done(false), - spiralize(spiralize), - fan_speed(GCodePathConfig::FAN_SPEED_DEFAULT), - estimates(TimeMaterialEstimates()) -{ -} -bool GCodePath::isTravelPath() const +[[nodiscard]] bool GCodePath::isTravelPath() const noexcept { - return config->isTravelPath(); + return config.isTravelPath(); } -double GCodePath::getExtrusionMM3perMM() const +[[nodiscard]] double GCodePath::getExtrusionMM3perMM() const noexcept { - return flow * width_factor * config->getExtrusionMM3perMM(); + return flow * width_factor * config.getExtrusionMM3perMM(); } -coord_t GCodePath::getLineWidthForLayerView() const +[[nodiscard]] coord_t GCodePath::getLineWidthForLayerView() const noexcept { - return flow * width_factor * config->getLineWidth() * config->getFlowRatio(); + return static_cast(flow * width_factor * static_cast(config.getLineWidth()) * config.getFlowRatio()); } -void GCodePath::setFanSpeed(double fan_speed) +void GCodePath::setFanSpeed(const double fanspeed) noexcept { - this->fan_speed = fan_speed; + fan_speed = fanspeed; } -double GCodePath::getFanSpeed() const +[[nodiscard]] double GCodePath::getFanSpeed() const noexcept { - return (fan_speed >= 0 && fan_speed <= 100) ? fan_speed : config->getFanSpeed(); + return (fan_speed >= 0 && fan_speed <= 100) ? fan_speed : config.getFanSpeed(); } -} +} // namespace cura diff --git a/src/pathPlanning/SpeedDerivatives.cpp b/src/pathPlanning/SpeedDerivatives.cpp new file mode 100644 index 0000000000..c2ca78c8cc --- /dev/null +++ b/src/pathPlanning/SpeedDerivatives.cpp @@ -0,0 +1,21 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "pathPlanning/SpeedDerivatives.h" + +namespace cura +{ + +void SpeedDerivatives::smoothSpeed(const SpeedDerivatives& first_layer_config, const LayerIndex layer_nr, const LayerIndex max_speed_layer_nr) +{ + const auto max_speed_layer = static_cast(max_speed_layer_nr); + const auto first_layer_speed = std::min(speed, first_layer_config.speed); + const auto first_layer_acceleration = std::min(acceleration, first_layer_config.acceleration); + const auto first_layer_jerk = std::min(jerk, first_layer_config.jerk); + speed = (speed * static_cast(layer_nr)) / max_speed_layer + (first_layer_speed * (max_speed_layer - static_cast(layer_nr)) / max_speed_layer); + acceleration + = (acceleration * static_cast(layer_nr)) / max_speed_layer + (first_layer_acceleration * (max_speed_layer - static_cast(layer_nr)) / max_speed_layer); + jerk = (jerk * static_cast(layer_nr)) / max_speed_layer + (first_layer_jerk * (max_speed_layer - static_cast(layer_nr)) / max_speed_layer); +} + +} // namespace cura \ No newline at end of file diff --git a/src/pathPlanning/TimeMaterialEstimates.cpp b/src/pathPlanning/TimeMaterialEstimates.cpp deleted file mode 100644 index 1ecbd04d87..0000000000 --- a/src/pathPlanning/TimeMaterialEstimates.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#include "pathPlanning/TimeMaterialEstimates.h" - -namespace cura -{ - -TimeMaterialEstimates::TimeMaterialEstimates(double extrude_time, double unretracted_travel_time, double retracted_travel_time, double material) -: extrude_time(extrude_time) -, unretracted_travel_time(unretracted_travel_time) -, retracted_travel_time(retracted_travel_time) -, material(material) -{ -} - -TimeMaterialEstimates::TimeMaterialEstimates() -: extrude_time(0.0) -, unretracted_travel_time(0.0) -, retracted_travel_time(0.0) -, material(0.0) -{ -} - -TimeMaterialEstimates TimeMaterialEstimates::operator-(const TimeMaterialEstimates& other) -{ - return TimeMaterialEstimates(extrude_time - other.extrude_time,unretracted_travel_time - other.unretracted_travel_time,retracted_travel_time - other.retracted_travel_time,material - other.material); -} - -TimeMaterialEstimates& TimeMaterialEstimates::operator-=(const TimeMaterialEstimates& other) -{ - extrude_time -= other.extrude_time; - unretracted_travel_time -= other.unretracted_travel_time; - retracted_travel_time -= other.retracted_travel_time; - material -= other.material; - return *this; -} - -TimeMaterialEstimates TimeMaterialEstimates::operator+(const TimeMaterialEstimates& other) -{ - return TimeMaterialEstimates(extrude_time+other.extrude_time, unretracted_travel_time+other.unretracted_travel_time, retracted_travel_time+other.retracted_travel_time, material+other.material); -} - -TimeMaterialEstimates& TimeMaterialEstimates::operator+=(const TimeMaterialEstimates& other) -{ - extrude_time += other.extrude_time; - unretracted_travel_time += other.unretracted_travel_time; - retracted_travel_time += other.retracted_travel_time; - material += other.material; - return *this; -} - -double TimeMaterialEstimates::getExtrudeTime() const -{ - return extrude_time; -} - -double TimeMaterialEstimates::getMaterial() const -{ - return material; -} - -double TimeMaterialEstimates::getTotalTime() const -{ - return extrude_time + unretracted_travel_time + retracted_travel_time; -} - -double TimeMaterialEstimates::getTotalUnretractedTime() const -{ - return extrude_time + unretracted_travel_time; -} - -double TimeMaterialEstimates::getTravelTime() const -{ - return retracted_travel_time + unretracted_travel_time; -} - -void TimeMaterialEstimates::reset() -{ - extrude_time = 0.0; - unretracted_travel_time = 0.0; - retracted_travel_time = 0.0; - material = 0.0; -} - -}//namespace cura diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp new file mode 100644 index 0000000000..72a8166de7 --- /dev/null +++ b/src/plugins/converters.cpp @@ -0,0 +1,481 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + + +#include "plugins/converters.h" + +#include "GCodePathConfig.h" +#include "WallToolPaths.h" +#include "pathPlanning/GCodePath.h" +#include "pathPlanning/SpeedDerivatives.h" +#include "settings/Settings.h" +#include "settings/types/LayerIndex.h" +#include "utils/polygon.h" + +#include +#include +#include + +namespace cura::plugins +{ + +empty::value_type empty::operator()() const +{ + return {}; +} + +constexpr empty::native_value_type empty::operator()(const value_type&) const +{ + return nullptr; +} + +broadcast_settings_request::value_type broadcast_settings_request::operator()(const broadcast_settings_request::native_value_type& slice_message) const +{ + value_type message{}; + auto* global_settings = message.mutable_global_settings()->mutable_settings(); + for (const auto& setting : slice_message.global_settings().settings()) + { + global_settings->emplace(setting.name(), setting.value()); + } + + auto* extruders_settings = message.mutable_extruder_settings(); + for (const auto& extruder : slice_message.extruders()) + { + auto* settings = extruders_settings->Add()->mutable_settings(); + for (const auto& setting : extruder.settings().settings()) + { + settings->emplace(setting.name(), setting.value()); + } + } + + auto* object_settings = message.mutable_object_settings(); + for (const auto& object : slice_message.object_lists()) + { + auto* settings = object_settings->Add()->mutable_settings(); + for (const auto& setting : object.settings()) + { + settings->emplace(setting.name(), setting.value()); + } + } + + auto* limit_to_extruder = message.mutable_limit_to_extruder(); + for (const auto& setting_extruder : slice_message.limit_to_extruder()) + { + limit_to_extruder->emplace(setting_extruder.name(), setting_extruder.extruder()); + } + return message; +} + +handshake_request::value_type handshake_request::operator()(const std::string& name, const std::string& version, const handshake_request::native_value_type& slot_info) const +{ + value_type message{}; + message.set_slot_id(slot_info.slot_id); + message.set_version(slot_info.version.data()); + message.set_plugin_name(name); + message.set_plugin_version(version); + return message; +} + +handshake_response::native_value_type handshake_response::operator()(const handshake_response::value_type& message, std::string_view peer) const +{ + return { .slot_version_range = message.slot_version_range(), + .plugin_name = message.plugin_name(), + .plugin_version = message.plugin_version(), + .peer = std::string{ peer }, + .broadcast_subscriptions = std::set(message.broadcast_subscriptions().begin(), message.broadcast_subscriptions().end()) }; +} + +simplify_request::value_type + simplify_request::operator()(const simplify_request::native_value_type& polygons, const coord_t max_resolution, const coord_t max_deviation, const coord_t max_area_deviation) + const +{ + value_type message{}; + if (polygons.empty()) + { + return message; + } + + auto* msg_polygons = message.mutable_polygons(); + auto* msg_polygon = msg_polygons->add_polygons(); + auto* msg_outline = msg_polygon->mutable_outline(); + + for (const auto& point : ranges::front(polygons.paths)) + { + auto* msg_outline_path = msg_outline->add_path(); + msg_outline_path->set_x(point.X); + msg_outline_path->set_y(point.Y); + } + + auto* msg_holes = msg_polygon->mutable_holes(); + for (const auto& polygon : polygons.paths | ranges::views::drop(1)) + { + auto* msg_hole = msg_holes->Add(); + for (const auto& point : polygon) + { + auto* msg_path = msg_hole->add_path(); + msg_path->set_x(point.X); + msg_path->set_y(point.Y); + } + } + + message.set_max_resolution(max_resolution); + message.set_max_deviation(max_resolution); + message.set_max_area_deviation(max_resolution); + return message; +} + +simplify_response::native_value_type + simplify_response::operator()([[maybe_unused]] const simplify_response::native_value_type& original_value, const simplify_response::value_type& message) const +{ + native_value_type poly{}; + for (const auto& paths : message.polygons().polygons()) + { + Polygon o{}; + for (const auto& point : paths.outline().path()) + { + o.add(Point{ point.x(), point.y() }); + } + poly.add(o); + + for (const auto& hole : paths.holes()) + { + Polygon h{}; + for (const auto& point : hole.path()) + { + h.add(Point{ point.x(), point.y() }); + } + poly.add(h); + } + } + return poly; +} + +postprocess_request::value_type postprocess_request::operator()(const postprocess_request::native_value_type& gcode) const +{ + value_type message{}; + message.set_gcode_word(gcode); + return message; +} + +postprocess_response::native_value_type + postprocess_response::operator()([[maybe_unused]] const postprocess_response::native_value_type& original_value, const postprocess_response::value_type& message) const +{ + return message.gcode_word(); +} + +infill_generate_request::value_type + infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings) const +{ + value_type message{}; + message.set_pattern(pattern); + auto* msg_settings = message.mutable_settings()->mutable_settings(); + for (const auto& [key, value] : settings.getFlattendSettings()) + { + msg_settings->insert({ key, value }); + } + + if (inner_contour.empty()) + { + return message; + } + + auto* msg_polygons = message.mutable_infill_areas(); + auto* msg_polygon = msg_polygons->add_polygons(); + auto* msg_outline = msg_polygon->mutable_outline(); + + for (const auto& point : ranges::front(inner_contour.paths)) + { + auto* msg_outline_path = msg_outline->add_path(); + msg_outline_path->set_x(point.X); + msg_outline_path->set_y(point.Y); + } + + auto* msg_holes = msg_polygon->mutable_holes(); + for (const auto& polygon : inner_contour.paths | ranges::views::drop(1)) + { + auto* msg_hole = msg_holes->Add(); + for (const auto& point : polygon) + { + auto* msg_path = msg_hole->add_path(); + msg_path->set_x(point.X); + msg_path->set_y(point.Y); + } + } + + return message; +} + +infill_generate_response::native_value_type infill_generate_response::operator()(const infill_generate_response::value_type& message) const +{ + VariableWidthLines toolpaths; + Polygons result_polygons; + Polygons result_lines; + + for (auto& tool_path : message.tool_paths().tool_paths()) + { + ExtrusionLine lines; + for (auto& msg_junction : tool_path.junctions()) + { + auto& p = msg_junction.point(); + auto junction = ExtrusionJunction{ p.x(), p.y(), msg_junction.width() }; + lines.emplace_back(junction); + } + + toolpaths.push_back(lines); + } + + std::vector toolpaths_; + toolpaths_.push_back(toolpaths); + + for (auto& polygon_msg : message.polygons().polygons()) + { + Polygons polygon{}; + + Polygon outline{}; + for (auto& path_msg : polygon_msg.outline().path()) + { + outline.add(Point{ path_msg.x(), path_msg.y() }); + } + polygon.add(outline); + + + for (auto& hole_msg : polygon_msg.holes()) + { + Polygon hole{}; + for (auto& path_msg : hole_msg.path()) + { + hole.add(Point{ path_msg.x(), path_msg.y() }); + } + polygon.add(hole); + } + + result_polygons.add(polygon); + } + + for (auto& polygon : message.poly_lines().paths()) + { + Polygon poly_line; + for (auto& p : polygon.path()) + { + poly_line.emplace_back(Point{ p.x(), p.y() }); + } + result_lines.emplace_back(poly_line); + } + + return { toolpaths_, result_polygons, result_lines }; +} + +[[nodiscard]] constexpr v0::SpaceFillType gcode_paths_modify_request::getSpaceFillType(const cura::SpaceFillType space_fill_type) noexcept +{ + switch (space_fill_type) + { + case SpaceFillType::None: + return v0::SpaceFillType::NONE; + case SpaceFillType::Polygons: + return v0::SpaceFillType::POLYGONS; + case SpaceFillType::PolyLines: + return v0::SpaceFillType::POLY_LINES; + case SpaceFillType::Lines: + return v0::SpaceFillType::LINES; + default: + return v0::SpaceFillType::NONE; + } +} + +[[nodiscard]] constexpr v0::PrintFeature gcode_paths_modify_request::getPrintFeature(const cura::PrintFeatureType print_feature_type) noexcept +{ + switch (print_feature_type) + { + case PrintFeatureType::NoneType: + return v0::PrintFeature::NONETYPE; + case PrintFeatureType::OuterWall: + return v0::PrintFeature::OUTERWALL; + case PrintFeatureType::InnerWall: + return v0::PrintFeature::INNERWALL; + case PrintFeatureType::Skin: + return v0::PrintFeature::SKIN; + case PrintFeatureType::Support: + return v0::PrintFeature::SUPPORT; + case PrintFeatureType::SkirtBrim: + return v0::PrintFeature::SKIRTBRIM; + case PrintFeatureType::Infill: + return v0::PrintFeature::INFILL; + case PrintFeatureType::SupportInfill: + return v0::PrintFeature::SUPPORTINFILL; + case PrintFeatureType::MoveCombing: + return v0::PrintFeature::MOVECOMBING; + case PrintFeatureType::MoveRetraction: + return v0::PrintFeature::MOVERETRACTION; + case PrintFeatureType::SupportInterface: + return v0::PrintFeature::SUPPORTINTERFACE; + case PrintFeatureType::PrimeTower: + return v0::PrintFeature::PRIMETOWER; + case PrintFeatureType::NumPrintFeatureTypes: + return v0::PrintFeature::NUMPRINTFEATURETYPES; + default: + return v0::PrintFeature::NONETYPE; + } +} + +gcode_paths_modify_request::value_type + gcode_paths_modify_request::operator()(const gcode_paths_modify_request::native_value_type& paths, const size_t extruder_nr, const LayerIndex layer_nr) const +{ + value_type message{}; + message.set_extruder_nr(static_cast(extruder_nr)); + message.set_layer_nr(layer_nr); + + // Construct the repeated GCodepath message + auto* gcode_paths = message.mutable_gcode_paths(); + for (const auto& path : paths) + { + auto* gcode_path = gcode_paths->Add(); + // Construct the OpenPath from the points in a GCodePath + for (const auto& point : path.points) + { + auto* points = gcode_path->mutable_path()->add_path(); + points->set_x(point.X); + points->set_y(point.Y); + } + gcode_path->set_space_fill_type(getSpaceFillType(path.space_fill_type)); + gcode_path->set_flow(path.flow); + gcode_path->set_width_factor(path.width_factor); + gcode_path->set_spiralize(path.spiralize); + gcode_path->set_speed_factor(path.speed_factor); + gcode_path->set_speed_back_pressure_factor(path.speed_back_pressure_factor); + gcode_path->set_retract(path.retract); + gcode_path->set_unretract_before_last_travel_move(path.unretract_before_last_travel_move); + gcode_path->set_perform_z_hop(path.perform_z_hop); + gcode_path->set_perform_prime(path.perform_prime); + gcode_path->set_skip_agressive_merge_hint(path.skip_agressive_merge_hint); + gcode_path->set_done(path.done); + gcode_path->set_fan_speed(path.getFanSpeed()); + gcode_path->set_mesh_name(path.mesh ? path.mesh->mesh_name : ""); + gcode_path->set_feature(getPrintFeature(path.config.type)); + gcode_path->mutable_speed_derivatives()->set_velocity(path.config.getSpeed()); + gcode_path->mutable_speed_derivatives()->set_acceleration(path.config.getAcceleration()); + gcode_path->mutable_speed_derivatives()->set_jerk(path.config.getJerk()); + gcode_path->set_line_width(path.config.getLineWidth()); + gcode_path->set_layer_thickness(path.config.getLayerThickness()); + gcode_path->set_flow_ratio(path.config.getFlowRatio()); + gcode_path->set_is_bridge_path(path.config.isBridgePath()); + } + + return message; +} + +[[nodiscard]] constexpr PrintFeatureType gcode_paths_modify_response::getPrintFeatureType(const v0::PrintFeature feature) noexcept +{ + switch (feature) + { + case v0::PrintFeature::NONETYPE: + return PrintFeatureType::NoneType; + case v0::PrintFeature::OUTERWALL: + return PrintFeatureType::OuterWall; + case v0::PrintFeature::INNERWALL: + return PrintFeatureType::InnerWall; + case v0::PrintFeature::SKIN: + return PrintFeatureType::Skin; + case v0::PrintFeature::SUPPORT: + return PrintFeatureType::Support; + case v0::PrintFeature::SKIRTBRIM: + return PrintFeatureType::SkirtBrim; + case v0::PrintFeature::INFILL: + return PrintFeatureType::Infill; + case v0::PrintFeature::SUPPORTINFILL: + return PrintFeatureType::SupportInfill; + case v0::PrintFeature::MOVECOMBING: + return PrintFeatureType::MoveCombing; + case v0::PrintFeature::MOVERETRACTION: + return PrintFeatureType::MoveRetraction; + case v0::PrintFeature::SUPPORTINTERFACE: + return PrintFeatureType::SupportInterface; + case v0::PrintFeature::PRIMETOWER: + return PrintFeatureType::PrimeTower; + case v0::PrintFeature::NUMPRINTFEATURETYPES: + return PrintFeatureType::NumPrintFeatureTypes; + default: + return PrintFeatureType::NoneType; + } +} + +[[nodiscard]] constexpr SpaceFillType gcode_paths_modify_response::getSpaceFillType(const v0::SpaceFillType space_fill_type) noexcept +{ + switch (space_fill_type) + { + case v0::SpaceFillType::NONE: + return SpaceFillType::None; + case v0::SpaceFillType::POLYGONS: + return SpaceFillType::Polygons; + case v0::SpaceFillType::POLY_LINES: + return SpaceFillType::PolyLines; + case v0::SpaceFillType::LINES: + return SpaceFillType::Lines; + default: + return SpaceFillType::None; + } +} + +[[nodiscard]] GCodePathConfig gcode_paths_modify_response::buildConfig(const v0::GCodePath& path) +{ + return { .type = getPrintFeatureType(path.feature()), + .line_width = path.line_width(), + .layer_thickness = path.layer_thickness(), + .flow = path.flow_ratio(), + .speed_derivatives + = SpeedDerivatives{ .speed = path.speed_derivatives().velocity(), .acceleration = path.speed_derivatives().acceleration(), .jerk = path.speed_derivatives().jerk() }, + .is_bridge_path = path.is_bridge_path(), + .fan_speed = path.fan_speed() }; +} + +gcode_paths_modify_response::native_value_type + gcode_paths_modify_response::operator()(gcode_paths_modify_response::native_value_type& original_value, const gcode_paths_modify_response::value_type& message) const +{ + std::vector paths; + using map_t = std::unordered_map>; + auto meshes = original_value + | ranges::views::filter( + [](const auto& path) + { + return path.mesh != nullptr; + }) + | ranges::views::transform( + [](const auto& path) -> map_t::value_type + { + return { path.mesh->mesh_name, path.mesh }; + }) + | ranges::to; + + for (const auto& gcode_path_msg : message.gcode_paths()) + { + GCodePath path{ + .config = buildConfig(gcode_path_msg), + .mesh = gcode_path_msg.mesh_name().empty() ? nullptr : meshes.at(gcode_path_msg.mesh_name()), + .space_fill_type = getSpaceFillType(gcode_path_msg.space_fill_type()), + .flow = gcode_path_msg.flow(), + .width_factor = gcode_path_msg.width_factor(), + .spiralize = gcode_path_msg.spiralize(), + .speed_factor = gcode_path_msg.speed_factor(), + .speed_back_pressure_factor = gcode_path_msg.speed_back_pressure_factor(), + .retract = gcode_path_msg.retract(), + .unretract_before_last_travel_move = gcode_path_msg.unretract_before_last_travel_move(), + .perform_z_hop = gcode_path_msg.perform_z_hop(), + .perform_prime = gcode_path_msg.perform_prime(), + .skip_agressive_merge_hint = gcode_path_msg.skip_agressive_merge_hint(), + .done = gcode_path_msg.done(), + .fan_speed = gcode_path_msg.fan_speed(), + }; + + path.points = gcode_path_msg.path().path() + | ranges::views::transform( + [](const auto& point_msg) + { + return Point{ point_msg.x(), point_msg.y() }; + }) + | ranges::to_vector; + + paths.emplace_back(path); + } + + return paths; +} +} // namespace cura::plugins \ No newline at end of file diff --git a/src/progress/Progress.cpp b/src/progress/Progress.cpp index 62687044c6..be6a688813 100644 --- a/src/progress/Progress.cpp +++ b/src/progress/Progress.cpp @@ -1,86 +1,107 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "progress/Progress.h" + #include +#include +#include #include #include "Application.h" //To get the communication channel to send progress through. #include "communication/Communication.h" //To send progress through the communication channel. -#include "progress/Progress.h" #include "utils/gettime.h" namespace cura { - -double Progress::times[] = { - 0.0, // START = 0, - 5.269, // SLICING = 1, - 1.533, // PARTS = 2, - 71.811, // INSET_SKIN = 3 - 51.009, // SUPPORT = 4, - 154.62, // EXPORT = 5, - 0.1 // FINISH = 6 -}; -std::string Progress::names [] = -{ - "start", - "slice", - "layerparts", - "inset+skin", - "support", - "export", - "process" -}; - -double Progress::accumulated_times [N_PROGRESS_STAGES] = {-1}; +std::array Progress::accumulated_times = { -1 }; double Progress::total_timing = -1; +std::optional Progress::first_skipped_layer{}; -float Progress::calcOverallProgress(Stage stage, float stage_progress) +double Progress::calcOverallProgress(Stage stage, double stage_progress) { assert(stage_progress <= 1.0); assert(stage_progress >= 0.0); - return ( accumulated_times[(int)stage] + stage_progress * times[(int)stage] ) / total_timing; + return (accumulated_times.at(static_cast(stage)) + stage_progress * times.at(static_cast(stage))) / total_timing; } - void Progress::init() { double accumulated_time = 0; - for (int stage = 0; stage < N_PROGRESS_STAGES; stage++) + for (size_t stage = 0; stage < N_PROGRESS_STAGES; stage++) { - accumulated_times[(int)stage] = accumulated_time; - accumulated_time += times[(int)stage]; + accumulated_times.at(static_cast(stage)) = accumulated_time; + accumulated_time += times.at(static_cast(stage)); } total_timing = accumulated_time; } void Progress::messageProgress(Progress::Stage stage, int progress_in_stage, int progress_in_stage_max) { - float percentage = calcOverallProgress(stage, float(progress_in_stage) / float(progress_in_stage_max)); - Application::getInstance().communication->sendProgress(percentage); - - // logProgress(names[(int)stage].c_str(), progress_in_stage, progress_in_stage_max, percentage); FIXME: use different sink + double percentage = calcOverallProgress(stage, static_cast(progress_in_stage / static_cast(progress_in_stage_max))); + Application::getInstance().communication->sendProgress(static_cast(percentage)); } void Progress::messageProgressStage(Progress::Stage stage, TimeKeeper* time_keeper) { - if (time_keeper) + if (time_keeper != nullptr) { - if ((int)stage > 0) + if (static_cast(stage) > 0) { - spdlog::info("Progress: {} accomplished in {:3}s", names[(int)stage - 1], time_keeper->restart()); + spdlog::info("Progress: {} accomplished in {:03.3f}s", names.at(static_cast(stage) - 1), time_keeper->restart()); } else { time_keeper->restart(); } - - if ((int)stage < (int)Stage::FINISH) + + if (static_cast(stage) < static_cast(Stage::FINISH)) { - spdlog::info("Starting {}...", names[(int)stage]); + spdlog::info("Starting {}...", names.at(static_cast(stage))); + } + } +} + +void Progress::messageProgressLayer(LayerIndex layer_nr, size_t total_layers, double total_time, const TimeKeeper::RegisteredTimes& stages, double skip_threshold) +{ + if (total_time < skip_threshold) + { + if (! first_skipped_layer) + { + first_skipped_layer = layer_nr; + } + } + else + { + if (first_skipped_layer) + { + spdlog::info("Skipped time reporting for layers [{}...{}]", first_skipped_layer.value().value, layer_nr.value); + first_skipped_layer.reset(); + } + + messageProgress(Stage::EXPORT, std::max(layer_nr.value, LayerIndex::value_type(0)) + 1, total_layers); + + spdlog::info("┌ Layer export [{}] accomplished in {:03.3f}s", layer_nr.value, total_time); + + size_t padding = 0; + auto iterator_max_size = std::max_element( + stages.begin(), + stages.end(), + [](const TimeKeeper::RegisteredTime& time1, const TimeKeeper::RegisteredTime& time2) + { + return time1.stage.size() < time2.stage.size(); + }); + if (iterator_max_size != stages.end()) + { + padding = iterator_max_size->stage.size(); + + for (const auto& [index, time] : stages | ranges::views::enumerate) + { + spdlog::info("{}── {}:{} {:03.3f}s", index < stages.size() - 1 ? "├" : "└", time.stage, std::string(padding - time.stage.size(), ' '), time.duration); + } } } } -}// namespace cura \ No newline at end of file +} // namespace cura diff --git a/src/raft.cpp b/src/raft.cpp index 96c81be90b..e13836b43f 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -1,15 +1,15 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "raft.h" #include #include "Application.h" //To get settings. #include "ExtruderTrain.h" -#include "raft.h" #include "Slice.h" -#include "sliceDataStorage.h" -#include "support.h" #include "settings/EnumSettings.h" //For EPlatformAdhesion. +#include "sliceDataStorage.h" #include "utils/math.h" namespace cura @@ -21,24 +21,27 @@ void Raft::generate(SliceDataStorage& storage) const Settings& settings = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_base_extruder_nr").settings; const coord_t distance = settings.get("raft_margin"); constexpr bool include_support = true; - constexpr bool dont_include_prime_tower = false; // Prime tower raft will be handled separately in 'storage.primeRaftOutline'; see below. + constexpr bool dont_include_prime_tower = false; // Prime tower raft will be handled separately in 'storage.primeRaftOutline'; see below. storage.raftOutline = storage.getLayerOutlines(0, include_support, dont_include_prime_tower).offset(distance, ClipperLib::jtRound); const coord_t shield_line_width_layer0 = settings.get("skirt_brim_line_width"); if (storage.draft_protection_shield.size() > 0) { - Polygons draft_shield_raft = storage.draft_protection_shield.offset(shield_line_width_layer0) // start half a line width outside shield - .difference(storage.draft_protection_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield + Polygons draft_shield_raft + = storage.draft_protection_shield + .offset(shield_line_width_layer0) // start half a line width outside shield + .difference(storage.draft_protection_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield storage.raftOutline = storage.raftOutline.unionPolygons(draft_shield_raft); } if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0) { const Polygons& ooze_shield = storage.oozeShield[0]; - Polygons ooze_shield_raft = ooze_shield.offset(shield_line_width_layer0) // start half a line width outside shield + Polygons ooze_shield_raft = ooze_shield + .offset(shield_line_width_layer0) // start half a line width outside shield .difference(ooze_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield storage.raftOutline = storage.raftOutline.unionPolygons(ooze_shield_raft); } - if(settings.get("raft_remove_inside_corners")) + if (settings.get("raft_remove_inside_corners")) { storage.raftOutline.makeConvex(); } @@ -61,26 +64,17 @@ void Raft::generate(SliceDataStorage& storage) return; } } - - storage.primeRaftOutline = storage.primeTower.outer_poly.offset(distance, ClipperLib::jtRound); - // NOTE: the raft doesn't take the prime tower brim into account, because it's (currently) not being printed when printing a raft - if (settings.get("raft_remove_inside_corners")) - { - storage.primeRaftOutline = storage.primeRaftOutline.unionPolygons(storage.raftOutline); - storage.primeRaftOutline.makeConvex(); - } - storage.primeRaftOutline = storage.primeRaftOutline.difference(storage.raftOutline); // In case of overlaps. } coord_t Raft::getTotalThickness() { - const Settings& mesh_group_settings =Application::getInstance().current_slice->scene.current_mesh_group->settings; + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const ExtruderTrain& base_train = mesh_group_settings.get("raft_base_extruder_nr"); const ExtruderTrain& interface_train = mesh_group_settings.get("raft_interface_extruder_nr"); const ExtruderTrain& surface_train = mesh_group_settings.get("raft_surface_extruder_nr"); return base_train.settings.get("raft_base_thickness") - + interface_train.settings.get("raft_interface_layers") * interface_train.settings.get("raft_interface_thickness") - + surface_train.settings.get("raft_surface_layers") * surface_train.settings.get("raft_surface_thickness"); + + interface_train.settings.get("raft_interface_layers") * interface_train.settings.get("raft_interface_thickness") + + surface_train.settings.get("raft_surface_layers") * surface_train.settings.get("raft_surface_thickness"); } coord_t Raft::getZdiffBetweenRaftAndLayer0() @@ -109,13 +103,14 @@ coord_t Raft::getFillerLayerHeight() const coord_t normal_layer_height = mesh_group_settings.get("layer_height"); return normal_layer_height; } + return round_divide(getZdiffBetweenRaftAndLayer0(), getFillerLayerCount()); } size_t Raft::getTotalExtraLayers() { - const Settings& mesh_group_settings =Application::getInstance().current_slice->scene.current_mesh_group->settings; + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const ExtruderTrain& base_train = mesh_group_settings.get("raft_base_extruder_nr"); const ExtruderTrain& interface_train = mesh_group_settings.get("raft_interface_extruder_nr"); const ExtruderTrain& surface_train = mesh_group_settings.get("raft_surface_extruder_nr"); @@ -127,4 +122,4 @@ size_t Raft::getTotalExtraLayers() } -}//namespace cura +} // namespace cura diff --git a/src/settings/MeshPathConfigs.cpp b/src/settings/MeshPathConfigs.cpp new file mode 100644 index 0000000000..d91595ce37 --- /dev/null +++ b/src/settings/MeshPathConfigs.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "settings/MeshPathConfigs.h" + +#include "ExtruderTrain.h" +#include "PrintFeature.h" + +#include + +namespace cura +{ + +MeshPathConfigs::MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t layer_thickness, const LayerIndex layer_nr, const std::vector& line_width_factor_per_extruder) + : inset0_config{ .type = PrintFeatureType::OuterWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_0") * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("wall_0_material_flow") * (layer_nr == 0 ? mesh.settings.get("wall_0_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_0"), + .acceleration = mesh.settings.get("acceleration_wall_0"), + .jerk = mesh.settings.get("jerk_wall_0") } } + , insetX_config{ .type = PrintFeatureType::InnerWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_x") * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("wall_x_material_flow") * (layer_nr == 0 ? mesh.settings.get("wall_x_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x"), + .acceleration = mesh.settings.get("acceleration_wall_x"), + .jerk = mesh.settings.get("jerk_wall_x") } } + , inset0_roofing_config{ .type = PrintFeatureType::OuterWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_0") + * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow + = mesh.settings.get("wall_0_material_flow_roofing") * (layer_nr == 0 ? mesh.settings.get("wall_0_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_0_roofing"), + .acceleration = mesh.settings.get("acceleration_wall_0_roofing"), + .jerk = mesh.settings.get("jerk_wall_0_roofing") } } + , insetX_roofing_config{ .type = PrintFeatureType::InnerWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_x") + * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow + = mesh.settings.get("wall_x_material_flow_roofing") * (layer_nr == 0 ? mesh.settings.get("wall_x_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x_roofing"), + .acceleration = mesh.settings.get("acceleration_wall_x_roofing"), + .jerk = mesh.settings.get("jerk_wall_x_roofing") } } + , bridge_inset0_config{ .type = PrintFeatureType::OuterWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_0") + * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("bridge_wall_material_flow"), + .speed_derivatives = { .speed = mesh.settings.get("bridge_wall_speed"), + .acceleration = mesh.settings.get("acceleration_wall_0"), + .jerk = mesh.settings.get("jerk_wall_0") }, + .is_bridge_path = true, + .fan_speed = mesh.settings.get("bridge_fan_speed") * 100.0 } + , bridge_insetX_config{ .type = PrintFeatureType::InnerWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_x") + * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("bridge_wall_material_flow"), + .speed_derivatives = { .speed = mesh.settings.get("bridge_wall_speed"), + .acceleration = mesh.settings.get("acceleration_wall_x"), + .jerk = mesh.settings.get("jerk_wall_x") }, + .is_bridge_path = true, + .fan_speed = mesh.settings.get("bridge_fan_speed") * 100.0 } + , skin_config{ .type = PrintFeatureType::Skin, + .line_width = static_cast( + mesh.settings.get("skin_line_width") * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("skin_material_flow") * (layer_nr == 0 ? mesh.settings.get("skin_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_topbottom"), + .acceleration = mesh.settings.get("acceleration_topbottom"), + .jerk = mesh.settings.get("jerk_topbottom") } } + , bridge_skin_config{ .type = PrintFeatureType::Skin, + .line_width = static_cast( + mesh.settings.get("skin_line_width") + * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("bridge_skin_material_flow"), + .speed_derivatives = { .speed = mesh.settings.get("bridge_skin_speed"), + .acceleration = mesh.settings.get("acceleration_topbottom"), + .jerk = mesh.settings.get("jerk_topbottom") }, + .is_bridge_path = true, + .fan_speed = mesh.settings.get("bridge_fan_speed") * 100.0 } + , bridge_skin_config2{ .type = PrintFeatureType::Skin, + .line_width = static_cast( + mesh.settings.get("skin_line_width") + * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("bridge_skin_material_flow_2"), + .speed_derivatives = { .speed = mesh.settings.get("bridge_skin_speed_2"), + .acceleration = mesh.settings.get("acceleration_topbottom"), + .jerk = mesh.settings.get("jerk_topbottom") }, + .is_bridge_path = true, + .fan_speed = mesh.settings.get("bridge_fan_speed_2") * 100.0 } + , bridge_skin_config3{ .type = PrintFeatureType::Skin, + .line_width = static_cast( + mesh.settings.get("skin_line_width") + * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("bridge_skin_material_flow_3"), + .speed_derivatives = { .speed = mesh.settings.get("bridge_skin_speed_3"), + .acceleration = mesh.settings.get("acceleration_topbottom"), + .jerk = mesh.settings.get("jerk_topbottom") }, + .is_bridge_path = true, + .fan_speed = mesh.settings.get("bridge_fan_speed_3") * 100.0 } + , roofing_config{ .type = PrintFeatureType::Skin, + .line_width = mesh.settings.get("roofing_line_width"), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("roofing_material_flow") * (layer_nr == 0 ? mesh.settings.get("material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_roofing"), + .acceleration = mesh.settings.get("acceleration_roofing"), + .jerk = mesh.settings.get("jerk_roofing") } } + , ironing_config{ .type = PrintFeatureType::Skin, + .line_width = mesh.settings.get("ironing_line_spacing"), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("ironing_flow"), + .speed_derivatives = { .speed = mesh.settings.get("speed_ironing"), + .acceleration = mesh.settings.get("acceleration_ironing"), + .jerk = mesh.settings.get("jerk_ironing") } } + +{ + infill_config.reserve(MAX_INFILL_COMBINE); + + for (const auto combine_idx : ranges::views::iota(1, MAX_INFILL_COMBINE + 1)) + { + infill_config.emplace_back(GCodePathConfig{ + .type = PrintFeatureType::Infill, + .line_width = static_cast( + mesh.settings.get("infill_line_width") * line_width_factor_per_extruder[mesh.settings.get("infill_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow = mesh.settings.get("infill_material_flow") * (layer_nr == 0 ? mesh.settings.get("material_flow_layer_0") : Ratio{ 1.0 }) * combine_idx, + .speed_derivatives = { .speed = mesh.settings.get("speed_infill"), + .acceleration = mesh.settings.get("acceleration_infill"), + .jerk = mesh.settings.get("jerk_infill") } }); + } +} + +} // namespace cura \ No newline at end of file diff --git a/src/settings/PathConfigStorage.cpp b/src/settings/PathConfigStorage.cpp index 9ab4b7704c..2ecf94eb70 100644 --- a/src/settings/PathConfigStorage.cpp +++ b/src/settings/PathConfigStorage.cpp @@ -1,14 +1,15 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "settings/PathConfigStorage.h" -#include "settings/Settings.h" // MAX_INFILL_COMBINE + #include "Application.h" #include "ExtruderTrain.h" -#include "raft.h" #include "Slice.h" -#include "sliceDataStorage.h" // SliceDataStorage +#include "raft.h" #include "settings/EnumSettings.h" //For EPlatformAdhesion. +#include "settings/Settings.h" // MAX_INFILL_COMBINE +#include "sliceDataStorage.h" // SliceDataStorage namespace cura { @@ -31,149 +32,56 @@ std::vector PathConfigStorage::getLineWidthFactorPerExtruder(const LayerI return ret; } -PathConfigStorage::MeshPathConfigs::MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t layer_thickness, const LayerIndex& layer_nr, const std::vector& line_width_factor_per_extruder) -: inset0_config( - PrintFeatureType::OuterWall - , mesh.settings.get("wall_line_width_0") * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("wall_0_material_flow") * ((layer_nr == 0) ? mesh.settings.get("wall_0_material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_wall_0"), mesh.settings.get("acceleration_wall_0"), mesh.settings.get("jerk_wall_0")} -) -, insetX_config( - PrintFeatureType::InnerWall - , mesh.settings.get("wall_line_width_x") * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("wall_x_material_flow") * ((layer_nr == 0) ? mesh.settings.get("wall_x_material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_wall_x"), mesh.settings.get("acceleration_wall_x"), mesh.settings.get("jerk_wall_x")} -) -, bridge_inset0_config( - PrintFeatureType::OuterWall - , mesh.settings.get("wall_line_width_0") * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("bridge_wall_material_flow") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("bridge_wall_speed"), mesh.settings.get("acceleration_wall_0"), mesh.settings.get("jerk_wall_0")} - , true // is_bridge_path - , mesh.settings.get("bridge_fan_speed") * 100.0 -) -, bridge_insetX_config( - PrintFeatureType::InnerWall - , mesh.settings.get("wall_line_width_x") * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("bridge_wall_material_flow") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("bridge_wall_speed"), mesh.settings.get("acceleration_wall_x"), mesh.settings.get("jerk_wall_x")} - , true // is_bridge_path - , mesh.settings.get("bridge_fan_speed") * 100.0 -) -, skin_config( - PrintFeatureType::Skin - , mesh.settings.get("skin_line_width") * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("skin_material_flow") * ((layer_nr == 0) ? mesh.settings.get("skin_material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_topbottom"), mesh.settings.get("acceleration_topbottom"), mesh.settings.get("jerk_topbottom")} -) -, bridge_skin_config( // use bridge skin flow, speed and fan - PrintFeatureType::Skin - , mesh.settings.get("skin_line_width") * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("bridge_skin_material_flow") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("bridge_skin_speed"), mesh.settings.get("acceleration_topbottom"), mesh.settings.get("jerk_topbottom")} - , true // is_bridge_path - , mesh.settings.get("bridge_fan_speed") * 100.0 -) -, bridge_skin_config2( // use bridge skin 2 flow, speed and fan - PrintFeatureType::Skin - , mesh.settings.get("skin_line_width") * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("bridge_skin_material_flow_2") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("bridge_skin_speed_2"), mesh.settings.get("acceleration_topbottom"), mesh.settings.get("jerk_topbottom")} - , true // is_bridge_path - , mesh.settings.get("bridge_fan_speed_2") * 100.0 -) -, bridge_skin_config3( // use bridge skin 3 flow, speed and fan - PrintFeatureType::Skin - , mesh.settings.get("skin_line_width") * line_width_factor_per_extruder[mesh.settings.get("top_bottom_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("bridge_skin_material_flow_3") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("bridge_skin_speed_3"), mesh.settings.get("acceleration_topbottom"), mesh.settings.get("jerk_topbottom")} - , true // is_bridge_path - , mesh.settings.get("bridge_fan_speed_3") * 100.0 -) -, roofing_config( - PrintFeatureType::Skin - , mesh.settings.get("roofing_line_width") - , layer_thickness - , mesh.settings.get("roofing_material_flow") * ((layer_nr == 0) ? mesh.settings.get("material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_roofing"), mesh.settings.get("acceleration_roofing"), mesh.settings.get("jerk_roofing")} -) -, ironing_config( - PrintFeatureType::Skin - , mesh.settings.get("ironing_line_spacing") - , layer_thickness - , mesh.settings.get("ironing_flow") - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_ironing"), mesh.settings.get("acceleration_ironing"), mesh.settings.get("jerk_ironing")} -) - -{ - infill_config.reserve(MAX_INFILL_COMBINE); - - for (int combine_idx = 0; combine_idx < MAX_INFILL_COMBINE; combine_idx++) - { - infill_config.emplace_back( - PrintFeatureType::Infill - , mesh.settings.get("infill_line_width") * line_width_factor_per_extruder[mesh.settings.get("infill_extruder_nr").extruder_nr] - , layer_thickness - , mesh.settings.get("infill_material_flow") * ((layer_nr == 0) ? mesh.settings.get("material_flow_layer_0") : Ratio(1.0)) * (combine_idx + 1) - , GCodePathConfig::SpeedDerivatives{mesh.settings.get("speed_infill"), mesh.settings.get("acceleration_infill"), mesh.settings.get("jerk_infill")} - ); - } -} - PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const LayerIndex& layer_nr, const coord_t layer_thickness) -: support_infill_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_infill_extruder_nr").extruder_nr) -, support_roof_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_roof_extruder_nr").extruder_nr) -, support_bottom_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_bottom_extruder_nr").extruder_nr) -, raft_base_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_base_extruder_nr")) -, raft_interface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_interface_extruder_nr")) -, raft_surface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_surface_extruder_nr")) -, support_infill_train(Application::getInstance().current_slice->scene.extruders[support_infill_extruder_nr]) -, support_roof_train(Application::getInstance().current_slice->scene.extruders[support_roof_extruder_nr]) -, support_bottom_train(Application::getInstance().current_slice->scene.extruders[support_bottom_extruder_nr]) -, line_width_factor_per_extruder(PathConfigStorage::getLineWidthFactorPerExtruder(layer_nr)) -, raft_base_config( - PrintFeatureType::SupportInterface - , raft_base_train.settings.get("raft_base_line_width") - , raft_base_train.settings.get("raft_base_thickness") - , Ratio(1.0) - , GCodePathConfig::SpeedDerivatives{raft_base_train.settings.get("raft_base_speed"), raft_base_train.settings.get("raft_base_acceleration"), raft_base_train.settings.get("raft_base_jerk")} - ) -, raft_interface_config( - PrintFeatureType::Support - , raft_interface_train.settings.get("raft_interface_line_width") - , raft_interface_train.settings.get("raft_interface_thickness") - , Ratio(1.0) - , GCodePathConfig::SpeedDerivatives{raft_interface_train.settings.get("raft_interface_speed"), raft_interface_train.settings.get("raft_interface_acceleration"), raft_interface_train.settings.get("raft_interface_jerk")} - ) -, raft_surface_config( - PrintFeatureType::SupportInterface - , raft_surface_train.settings.get("raft_surface_line_width") - , raft_surface_train.settings.get("raft_surface_thickness") - , Ratio(1.0) - , GCodePathConfig::SpeedDerivatives{raft_surface_train.settings.get("raft_surface_speed"), raft_surface_train.settings.get("raft_surface_acceleration"), raft_surface_train.settings.get("raft_surface_jerk")} - ) -, support_roof_config( - PrintFeatureType::SupportInterface - , support_roof_train.settings.get("support_roof_line_width") * line_width_factor_per_extruder[support_roof_extruder_nr] - , layer_thickness - , support_roof_train.settings.get("support_roof_material_flow") * ((layer_nr == 0) ? support_roof_train.settings.get("material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{support_roof_train.settings.get("speed_support_roof"), support_roof_train.settings.get("acceleration_support_roof"), support_roof_train.settings.get("jerk_support_roof")} - ) -, support_bottom_config( - PrintFeatureType::SupportInterface - , support_bottom_train.settings.get("support_bottom_line_width") * line_width_factor_per_extruder[support_bottom_extruder_nr] - , layer_thickness - , support_roof_train.settings.get("support_bottom_material_flow") * ((layer_nr == 0) ? support_roof_train.settings.get("material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{support_bottom_train.settings.get("speed_support_bottom"), support_bottom_train.settings.get("acceleration_support_bottom"), support_bottom_train.settings.get("jerk_support_bottom")} - ) + : support_infill_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_infill_extruder_nr").extruder_nr) + , support_roof_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_roof_extruder_nr").extruder_nr) + , support_bottom_extruder_nr(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_bottom_extruder_nr").extruder_nr) + , raft_base_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_base_extruder_nr")) + , raft_interface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_interface_extruder_nr")) + , raft_surface_train(Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_surface_extruder_nr")) + , support_infill_train(Application::getInstance().current_slice->scene.extruders[support_infill_extruder_nr]) + , support_roof_train(Application::getInstance().current_slice->scene.extruders[support_roof_extruder_nr]) + , support_bottom_train(Application::getInstance().current_slice->scene.extruders[support_bottom_extruder_nr]) + , line_width_factor_per_extruder(PathConfigStorage::getLineWidthFactorPerExtruder(layer_nr)) + , raft_base_config(GCodePathConfig{ .type = PrintFeatureType::SupportInterface, + .line_width = raft_base_train.settings.get("raft_base_line_width"), + .layer_thickness = raft_base_train.settings.get("raft_base_thickness"), + .flow = Ratio(1.0), + .speed_derivatives = SpeedDerivatives{ .speed = raft_base_train.settings.get("raft_base_speed"), + .acceleration = raft_base_train.settings.get("raft_base_acceleration"), + .jerk = raft_base_train.settings.get("raft_base_jerk") } }) + , raft_interface_config(GCodePathConfig{ .type = PrintFeatureType::Support, + .line_width = raft_interface_train.settings.get("raft_interface_line_width"), + .layer_thickness = raft_interface_train.settings.get("raft_interface_thickness"), + .flow = Ratio(1.0), + .speed_derivatives = SpeedDerivatives{ .speed = raft_interface_train.settings.get("raft_interface_speed"), + .acceleration = raft_interface_train.settings.get("raft_interface_acceleration"), + .jerk = raft_interface_train.settings.get("raft_interface_jerk") } }) + , raft_surface_config(GCodePathConfig{ .type = PrintFeatureType::SupportInterface, + .line_width = raft_surface_train.settings.get("raft_surface_line_width"), + .layer_thickness = raft_surface_train.settings.get("raft_surface_thickness"), + .flow = Ratio(1.0), + .speed_derivatives = SpeedDerivatives{ .speed = raft_surface_train.settings.get("raft_surface_speed"), + .acceleration = raft_surface_train.settings.get("raft_surface_acceleration"), + .jerk = raft_surface_train.settings.get("raft_surface_jerk") } }) + , support_roof_config(GCodePathConfig{ + .type = PrintFeatureType::SupportInterface, + .line_width = static_cast(support_roof_train.settings.get("support_roof_line_width") * line_width_factor_per_extruder[support_roof_extruder_nr]), + .layer_thickness = layer_thickness, + .flow + = support_roof_train.settings.get("support_roof_material_flow") * ((layer_nr == 0) ? support_roof_train.settings.get("material_flow_layer_0") : Ratio(1.0)), + .speed_derivatives = { .speed = support_roof_train.settings.get("speed_support_roof"), + .acceleration = support_roof_train.settings.get("acceleration_support_roof"), + .jerk = support_roof_train.settings.get("jerk_support_roof") } }) + , support_bottom_config(GCodePathConfig{ + .type = PrintFeatureType::SupportInterface, + .line_width = static_cast(support_bottom_train.settings.get("support_bottom_line_width") * line_width_factor_per_extruder[support_bottom_extruder_nr]), + .layer_thickness = layer_thickness, + .flow = support_roof_train.settings.get("support_bottom_material_flow") + * ((layer_nr == 0) ? support_roof_train.settings.get("material_flow_layer_0") : Ratio(1.0)), + .speed_derivatives = SpeedDerivatives{ .speed = support_bottom_train.settings.get("speed_support_bottom"), + .acceleration = support_bottom_train.settings.get("acceleration_support_bottom"), + .jerk = support_bottom_train.settings.get("jerk_support_bottom") } }) { const size_t extruder_count = Application::getInstance().current_slice->scene.extruders.size(); travel_config_per_extruder.reserve(extruder_count); @@ -183,48 +91,58 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) { const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; - travel_config_per_extruder.emplace_back( - PrintFeatureType::MoveCombing - , 0 - , 0 - , 0.0 - , GCodePathConfig::SpeedDerivatives{train.settings.get("speed_travel"), train.settings.get("acceleration_travel"), train.settings.get("jerk_travel")} - ); + travel_config_per_extruder.emplace_back(GCodePathConfig{ .type = PrintFeatureType::MoveCombing, + .line_width = 0, + .layer_thickness = 0, + .flow = 0.0, + .speed_derivatives = SpeedDerivatives{ .speed = train.settings.get("speed_travel"), + .acceleration = train.settings.get("acceleration_travel"), + .jerk = train.settings.get("jerk_travel") } }); skirt_brim_config_per_extruder.emplace_back( - PrintFeatureType::SkirtBrim - , train.settings.get("skirt_brim_line_width") - * ((mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) ? 1.0_r : line_width_factor_per_extruder[extruder_nr]) // cause it's also used for the draft/ooze shield - , layer_thickness - , train.settings.get("skirt_brim_material_flow") * ((layer_nr == 0) ? train.settings.get("material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{train.settings.get("skirt_brim_speed"), train.settings.get("acceleration_skirt_brim"), train.settings.get("jerk_skirt_brim")} - ); - prime_tower_config_per_extruder.emplace_back( - PrintFeatureType::PrimeTower - , train.settings.get("prime_tower_line_width") - * ((mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) ? 1.0_r : line_width_factor_per_extruder[extruder_nr]) - , layer_thickness - , train.settings.get("prime_tower_flow") * ((layer_nr == 0) ? train.settings.get("material_flow_layer_0") : Ratio(1.0)) - , GCodePathConfig::SpeedDerivatives{train.settings.get("speed_prime_tower"), train.settings.get("acceleration_prime_tower"), train.settings.get("jerk_prime_tower")} - ); + GCodePathConfig{ .type = PrintFeatureType::SkirtBrim, + .line_width = static_cast( + train.settings.get("skirt_brim_line_width") + * ((mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) + ? 1.0_r + : line_width_factor_per_extruder[extruder_nr])) // cause it's also used for the draft/ooze shield + , + .layer_thickness = layer_thickness, + .flow = train.settings.get("skirt_brim_material_flow") * ((layer_nr == 0) ? train.settings.get("material_flow_layer_0") : Ratio(1.0)), + .speed_derivatives = SpeedDerivatives{ .speed = train.settings.get("skirt_brim_speed"), + .acceleration = train.settings.get("acceleration_skirt_brim"), + .jerk = train.settings.get("jerk_skirt_brim") } }); + prime_tower_config_per_extruder.emplace_back(GCodePathConfig{ + .type = PrintFeatureType::PrimeTower, + .line_width = static_cast( + train.settings.get("prime_tower_line_width") + * ((mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) ? 1.0_r : line_width_factor_per_extruder[extruder_nr])), + .layer_thickness = layer_thickness, + .flow = train.settings.get("prime_tower_flow") * ((layer_nr == 0) ? train.settings.get("material_flow_layer_0") : Ratio(1.0)), + .speed_derivatives = SpeedDerivatives{ .speed = train.settings.get("speed_prime_tower"), + .acceleration = train.settings.get("acceleration_prime_tower"), + .jerk = train.settings.get("jerk_prime_tower") } }); } mesh_configs.reserve(storage.meshes.size()); - for (const SliceMeshStorage& mesh_storage : storage.meshes) + for (const std::shared_ptr& mesh_storage : storage.meshes) { - mesh_configs.emplace_back(mesh_storage, layer_thickness, layer_nr, line_width_factor_per_extruder); + mesh_configs.emplace_back(*mesh_storage, layer_thickness, layer_nr, line_width_factor_per_extruder); } support_infill_config.reserve(MAX_INFILL_COMBINE); - const float support_infill_line_width_factor = (mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) ? 1.0_r : line_width_factor_per_extruder[support_infill_extruder_nr]; + const float support_infill_line_width_factor + = (mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT) ? 1.0_r : line_width_factor_per_extruder[support_infill_extruder_nr]; for (int combine_idx = 0; combine_idx < MAX_INFILL_COMBINE; combine_idx++) { support_infill_config.emplace_back( - PrintFeatureType::Support - , support_infill_train.settings.get("support_line_width") * support_infill_line_width_factor - , layer_thickness - , support_infill_train.settings.get("support_material_flow") * ((layer_nr == 0) ? support_infill_train.settings.get("material_flow_layer_0") : Ratio(1.0)) * (combine_idx + 1) - , GCodePathConfig::SpeedDerivatives{support_infill_train.settings.get("speed_support_infill"), support_infill_train.settings.get("acceleration_support_infill"), support_infill_train.settings.get("jerk_support_infill")} - ); + GCodePathConfig{ .type = PrintFeatureType::Support, + .line_width = static_cast(support_infill_train.settings.get("support_line_width") * support_infill_line_width_factor), + .layer_thickness = layer_thickness, + .flow = support_infill_train.settings.get("support_material_flow") + * ((layer_nr == 0) ? support_infill_train.settings.get("material_flow_layer_0") : Ratio(1.0)) * (combine_idx + 1), + .speed_derivatives = SpeedDerivatives{ .speed = support_infill_train.settings.get("speed_support_infill"), + .acceleration = support_infill_train.settings.get("acceleration_support_infill"), + .jerk = support_infill_train.settings.get("jerk_support_infill") } }); } const size_t initial_speedup_layer_count = mesh_group_settings.get("speed_slowdown_layers"); @@ -232,52 +150,70 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye { handleInitialLayerSpeedup(storage, layer_nr, initial_speedup_layer_count); } + + const auto layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height"); + const auto support_top_distance = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_top_distance"); + const coord_t leftover_support_distance = support_top_distance % layer_height; + + support_fractional_infill_config = support_infill_config; // copy + for (auto& config : support_fractional_infill_config) + { + config.z_offset = -leftover_support_distance; + config.flow *= Ratio(layer_height - leftover_support_distance, layer_height); + } + + support_fractional_roof_config = support_roof_config; // copy + support_fractional_roof_config.z_offset = -leftover_support_distance; + support_fractional_roof_config.flow *= Ratio(layer_height - leftover_support_distance, layer_height); } -void PathConfigStorage::MeshPathConfigs::smoothAllSpeeds(GCodePathConfig::SpeedDerivatives first_layer_config, const LayerIndex& layer_nr, const LayerIndex& max_speed_layer) +void MeshPathConfigs::smoothAllSpeeds(const SpeedDerivatives& first_layer_config, const LayerIndex layer_nr, const LayerIndex max_speed_layer) { - inset0_config.smoothSpeed( first_layer_config, layer_nr, max_speed_layer); - insetX_config.smoothSpeed( first_layer_config, layer_nr, max_speed_layer); - skin_config.smoothSpeed( first_layer_config, layer_nr, max_speed_layer); - ironing_config.smoothSpeed( first_layer_config, layer_nr, max_speed_layer); + inset0_config.speed_derivatives.smoothSpeed(first_layer_config, layer_nr, max_speed_layer); + insetX_config.speed_derivatives.smoothSpeed(first_layer_config, layer_nr, max_speed_layer); + skin_config.speed_derivatives.smoothSpeed(first_layer_config, layer_nr, max_speed_layer); + ironing_config.speed_derivatives.smoothSpeed(first_layer_config, layer_nr, max_speed_layer); for (size_t idx = 0; idx < MAX_INFILL_COMBINE; idx++) { - //Infill speed (per combine part per mesh). - infill_config[idx].smoothSpeed(first_layer_config, layer_nr, max_speed_layer); + // Infill speed (per combine part per mesh). + infill_config[idx].speed_derivatives.smoothSpeed(first_layer_config, layer_nr, max_speed_layer); } } -void cura::PathConfigStorage::handleInitialLayerSpeedup(const SliceDataStorage& storage, const LayerIndex& layer_nr, const size_t initial_speedup_layer_count) +void PathConfigStorage::handleInitialLayerSpeedup(const SliceDataStorage& storage, const LayerIndex& layer_nr, const size_t initial_speedup_layer_count) { - std::vector global_first_layer_config_per_extruder; + std::vector global_first_layer_config_per_extruder; global_first_layer_config_per_extruder.reserve(Application::getInstance().current_slice->scene.extruders.size()); for (const ExtruderTrain& extruder : Application::getInstance().current_slice->scene.extruders) { - global_first_layer_config_per_extruder.emplace_back( - GCodePathConfig::SpeedDerivatives{ - extruder.settings.get("speed_print_layer_0") - , extruder.settings.get("acceleration_print_layer_0") - , extruder.settings.get("jerk_print_layer_0") - }); + global_first_layer_config_per_extruder.emplace_back(SpeedDerivatives{ .speed = extruder.settings.get("speed_print_layer_0"), + .acceleration = extruder.settings.get("acceleration_print_layer_0"), + .jerk = extruder.settings.get("jerk_print_layer_0") }); } { // support if (layer_nr < static_cast(initial_speedup_layer_count)) { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const size_t extruder_nr_support_infill = mesh_group_settings.get((layer_nr <= 0) ? "support_extruder_nr_layer_0" : "support_infill_extruder_nr").extruder_nr; - GCodePathConfig::SpeedDerivatives& first_layer_config_infill = global_first_layer_config_per_extruder[extruder_nr_support_infill]; + const size_t extruder_nr_support_infill + = mesh_group_settings.get((layer_nr <= 0) ? "support_extruder_nr_layer_0" : "support_infill_extruder_nr").extruder_nr; for (unsigned int idx = 0; idx < MAX_INFILL_COMBINE; idx++) { - support_infill_config[idx].smoothSpeed(first_layer_config_infill, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); + support_infill_config[idx].speed_derivatives.smoothSpeed( + global_first_layer_config_per_extruder[extruder_nr_support_infill], + std::max(LayerIndex(0), layer_nr), + initial_speedup_layer_count); } - const size_t extruder_nr_support_roof = mesh_group_settings.get("support_roof_extruder_nr").extruder_nr; - GCodePathConfig::SpeedDerivatives& first_layer_config_roof = global_first_layer_config_per_extruder[extruder_nr_support_roof]; - support_roof_config.smoothSpeed(first_layer_config_roof, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); + support_roof_config.speed_derivatives.smoothSpeed( + global_first_layer_config_per_extruder[extruder_nr_support_roof], + std::max(LayerIndex(0), layer_nr), + initial_speedup_layer_count); const size_t extruder_nr_support_bottom = mesh_group_settings.get("support_bottom_extruder_nr").extruder_nr; - GCodePathConfig::SpeedDerivatives& first_layer_config_bottom = global_first_layer_config_per_extruder[extruder_nr_support_bottom]; - support_bottom_config.smoothSpeed(first_layer_config_bottom, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); + support_bottom_config.speed_derivatives.smoothSpeed( + global_first_layer_config_per_extruder[extruder_nr_support_bottom], + std::max(LayerIndex(0), layer_nr), + initial_speedup_layer_count); } } @@ -285,41 +221,34 @@ void cura::PathConfigStorage::handleInitialLayerSpeedup(const SliceDataStorage& for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) { const ExtruderTrain& train = Application::getInstance().current_slice->scene.extruders[extruder_nr]; - GCodePathConfig::SpeedDerivatives initial_layer_travel_speed_config{ - train.settings.get("speed_travel_layer_0") - , train.settings.get("acceleration_travel_layer_0") - , train.settings.get("jerk_travel_layer_0") - }; + const SpeedDerivatives initial_layer_travel_speed_config{ .speed = train.settings.get("speed_travel_layer_0"), + .acceleration = train.settings.get("acceleration_travel_layer_0"), + .jerk = train.settings.get("jerk_travel_layer_0") }; GCodePathConfig& travel = travel_config_per_extruder[extruder_nr]; - travel.smoothSpeed(initial_layer_travel_speed_config, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); + travel.speed_derivatives.smoothSpeed(initial_layer_travel_speed_config, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); // don't smooth speed for the skirt/brim! // NOTE: not smoothing skirt/brim means the speeds are also not smoothed for the draft/ooze shield - const GCodePathConfig::SpeedDerivatives& initial_layer_print_speed_config = global_first_layer_config_per_extruder[extruder_nr]; - GCodePathConfig& prime_tower = prime_tower_config_per_extruder[extruder_nr]; - prime_tower.smoothSpeed(initial_layer_print_speed_config, std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); + prime_tower.speed_derivatives.smoothSpeed(global_first_layer_config_per_extruder[extruder_nr], std::max(LayerIndex(0), layer_nr), initial_speedup_layer_count); } - } { // meshes for (size_t mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; - GCodePathConfig::SpeedDerivatives initial_layer_speed_config{ - mesh.settings.get("speed_print_layer_0") - , mesh.settings.get("acceleration_print_layer_0") - , mesh.settings.get("jerk_print_layer_0") - }; + const SpeedDerivatives initial_layer_speed_config{ .speed = mesh.settings.get("speed_print_layer_0"), + .acceleration = mesh.settings.get("acceleration_print_layer_0"), + .jerk = mesh.settings.get("jerk_print_layer_0") }; mesh_configs[mesh_idx].smoothAllSpeeds(initial_layer_speed_config, layer_nr, initial_speedup_layer_count); - mesh_configs[mesh_idx].roofing_config.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layer_count); + mesh_configs[mesh_idx].roofing_config.speed_derivatives.smoothSpeed(initial_layer_speed_config, layer_nr, initial_speedup_layer_count); } } } -}//namespace cura +} // namespace cura diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index 9ae6a22ad2..aa38f7c838 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -1,14 +1,7 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include -#include -#include // regex parsing for temp flow graph -#include // ostringstream -#include -#include //Parsing strings (stod, stoul). - -#include +#include "settings/Settings.h" #include "Application.h" //To get the extruders. #include "BeadingStrategy/BeadingStrategyFactory.h" @@ -16,7 +9,6 @@ #include "Slice.h" #include "settings/EnumSettings.h" #include "settings/FlowTempGraph.h" -#include "settings/Settings.h" #include "settings/types/Angle.h" #include "settings/types/Duration.h" //For duration and time settings. #include "settings/types/LayerIndex.h" //For layer index settings. @@ -24,8 +16,20 @@ #include "settings/types/Temperature.h" //For temperature settings. #include "settings/types/Velocity.h" //For velocity settings. #include "utils/FMatrix4x3.h" -#include "utils/string.h" //For Escaped. #include "utils/polygon.h" +#include "utils/string.h" //For Escaped. +#include "utils/types/string_switch.h" //For string switch. + +#include +#include +#include + +#include +#include +#include // regex parsing for temp flow graph +#include // ostringstream +#include +#include //Parsing strings (stod, stoul). namespace cura { @@ -112,7 +116,8 @@ ExtruderTrain& Settings::get(const std::string& key) const return Application::getInstance().current_slice->scene.extruders[extruder_nr]; } -template<> std::vector Settings::get>(const std::string& key) const +template<> +std::vector Settings::get>(const std::string& key) const { int extruder_nr = std::atoi(get(key).c_str()); std::vector ret; @@ -133,7 +138,8 @@ template<> std::vector Settings::get template<> LayerIndex Settings::get(const std::string& key) const { - return std::atoi(get(key).c_str()) - 1; // For the user we display layer numbers starting from 1, but we start counting from 0. Still it may be negative for Raft layers. + // For the user we display layer numbers starting from 1, but we start counting from 0. Still it may be negative for Raft layers. + return std::atoi(get(key).c_str()) - 1; } template<> @@ -166,6 +172,12 @@ Velocity Settings::get(const std::string& key) const return get(key); } +template<> +Acceleration Settings::get(const std::string& key) const +{ + return get(key); +} + template<> Ratio Settings::get(const std::string& key) const { @@ -182,12 +194,16 @@ template<> DraftShieldHeightLimitation Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "limited") + using namespace cura::utils; + switch (hash_enum(value)) { + case "full"_sw: + return DraftShieldHeightLimitation::FULL; + case "limited"_sw: return DraftShieldHeightLimitation::LIMITED; - } - else // if (value == "full") or default. - { + case "plugin"_sw: + return DraftShieldHeightLimitation::PLUGIN; + default: return DraftShieldHeightLimitation::FULL; } } @@ -240,14 +256,15 @@ FlowTempGraph Settings::get(const std::string& key) const return result; } -template<> Polygons Settings::get(const std::string& key) const +template<> +Polygons Settings::get(const std::string& key) const { std::string value_string = get(key); Polygons result; if (value_string.empty()) { - return result; //Empty at this point. + return result; // Empty at this point. } /* We're looking to match one or more floating point values separated by * commas and surrounded by square brackets. Note that because the QML @@ -260,22 +277,22 @@ template<> Polygons Settings::get(const std::string& key) const if (std::regex_search(value_string, polygons_match, polygons_regex) && polygons_match.size() > 1) { std::string polygons_string = polygons_match.str(1); - + std::regex polygon_regex(R"(\[((\[[^\[\]]*\]\s*,?\s*)*)\]\s*,?)"); // matches with a list of lists (a list of 2D vertices) std::smatch polygon_match; - - std::regex_token_iterator rend; //Default constructor gets the end-of-sequence iterator. + + std::regex_token_iterator rend; // Default constructor gets the end-of-sequence iterator. std::regex_token_iterator polygon_match_iter(polygons_string.begin(), polygons_string.end(), polygon_regex, 0); while (polygon_match_iter != rend) { std::string polygon_str = *polygon_match_iter++; - + result.emplace_back(); PolygonRef poly = result.back(); std::regex point2D_regex(R"(\[([^,\[]*),([^,\]]*)\])"); // matches to a list of exactly two things - const int submatches[] = {1, 2}; // Match first number and second number of a pair. + const int submatches[] = { 1, 2 }; // Match first number and second number of a pair. std::regex_token_iterator match_iter(polygon_str.begin(), polygon_str.end(), point2D_regex, submatches); while (match_iter != rend) { @@ -346,105 +363,74 @@ template<> EGCodeFlavor Settings::get(const std::string& key) const { const std::string& value = get(key); - // I wish that switch statements worked for std::string... - if (value == "Griffin") + using namespace cura::utils; + switch (hash_enum(value)) { + case "Marlin"_sw: + return EGCodeFlavor::MARLIN; + case "Griffin"_sw: return EGCodeFlavor::GRIFFIN; - } - else if (value == "UltiGCode") - { + case "UltiGCode"_sw: return EGCodeFlavor::ULTIGCODE; - } - else if (value == "Makerbot") - { + case "Makerbot"_sw: return EGCodeFlavor::MAKERBOT; - } - else if (value == "BFB") - { + case "BFB"_sw: return EGCodeFlavor::BFB; - } - else if (value == "MACH3") - { + case "MACH3"_sw: return EGCodeFlavor::MACH3; - } - else if (value == "RepRap (Volumetric)") - { + case "RepRap (Volumetric)"_sw: return EGCodeFlavor::MARLIN_VOLUMATRIC; - } - else if (value == "Repetier") - { + case "Repetier"_sw: return EGCodeFlavor::REPETIER; - } - else if (value == "RepRap (RepRap)") - { + case "RepRap (RepRap)"_sw: return EGCodeFlavor::REPRAP; + case "plugin"_sw: + return EGCodeFlavor::PLUGIN; + default: + return EGCodeFlavor::MARLIN; } - // Default: - return EGCodeFlavor::MARLIN; } template<> EFillMethod Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "lines") + using namespace cura::utils; + switch (hash_enum(value)) { + case "none"_sw: + return EFillMethod::NONE; + case "lines"_sw: return EFillMethod::LINES; - } - else if (value == "grid") - { + case "grid"_sw: return EFillMethod::GRID; - } - else if (value == "cubic") - { + case "cubic"_sw: return EFillMethod::CUBIC; - } - else if (value == "cubicsubdiv") - { + case "cubicsubdiv"_sw: return EFillMethod::CUBICSUBDIV; - } - else if (value == "tetrahedral") - { + case "tetrahedral"_sw: return EFillMethod::TETRAHEDRAL; - } - else if (value == "quarter_cubic") - { + case "quarter_cubic"_sw: return EFillMethod::QUARTER_CUBIC; - } - else if (value == "triangles") - { + case "triangles"_sw: return EFillMethod::TRIANGLES; - } - else if (value == "trihexagon") - { + case "trihexagon"_sw: return EFillMethod::TRIHEXAGON; - } - else if (value == "concentric") - { + case "concentric"_sw: return EFillMethod::CONCENTRIC; - } - else if (value == "zigzag") - { + case "zigzag"_sw: return EFillMethod::ZIG_ZAG; - } - else if (value == "cross") - { + case "cross"_sw: return EFillMethod::CROSS; - } - else if (value == "cross_3d") - { + case "cross_3d"_sw: return EFillMethod::CROSS_3D; - } - else if (value == "gyroid") - { + case "gyroid"_sw: return EFillMethod::GYROID; - } - else if (value == "lightning") - { + case "lightning"_sw: return EFillMethod::LIGHTNING; - } - else // Default. - { + case "plugin"_sw: + return EFillMethod::PLUGIN; + default: return EFillMethod::NONE; } } @@ -453,20 +439,20 @@ template<> EPlatformAdhesion Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "brim") + using namespace cura::utils; + switch (hash_enum(value)) { + case "skirt"_sw: + return EPlatformAdhesion::SKIRT; + case "brim"_sw: return EPlatformAdhesion::BRIM; - } - else if (value == "raft") - { + case "raft"_sw: return EPlatformAdhesion::RAFT; - } - else if (value == "none") - { + case "none"_sw: return EPlatformAdhesion::NONE; - } - else // Default. - { + case "plugin"_sw: + return EPlatformAdhesion::PLUGIN; + default: return EPlatformAdhesion::SKIRT; } } @@ -475,16 +461,18 @@ template<> ESupportType Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "everywhere") + using namespace cura::utils; + switch (hash_enum(value)) { + case "none"_sw: + return ESupportType::NONE; + case "everywhere"_sw: return ESupportType::EVERYWHERE; - } - else if (value == "buildplate") - { + case "buildplate"_sw: return ESupportType::PLATFORM_ONLY; - } - else // Default. - { + case "plugin"_sw: + return ESupportType::PLUGIN; + default: return ESupportType::NONE; } } @@ -493,16 +481,16 @@ template<> ESupportStructure Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "normal") + using namespace cura::utils; + switch (hash_enum(value)) { + case "normal"_sw: return ESupportStructure::NORMAL; - } - else if (value == "tree") - { + case "tree"_sw: return ESupportStructure::TREE; - } - else // Default. - { + case "plugin"_sw: + return ESupportStructure::PLUGIN; + default: return ESupportStructure::NORMAL; } } @@ -512,20 +500,20 @@ template<> EZSeamType Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "random") + using namespace cura::utils; + switch (hash_enum(value)) { + case "shortest"_sw: + return EZSeamType::SHORTEST; + case "random"_sw: return EZSeamType::RANDOM; - } - else if (value == "back") // It's called 'back' internally because originally this was intended to allow the user to put the seam in the back of the object where it's less visible. - { + case "back"_sw: return EZSeamType::USER_SPECIFIED; - } - else if (value == "sharpest_corner") - { + case "sharpest_corner"_sw: return EZSeamType::SHARPEST_CORNER; - } - else // Default. - { + case "plugin"_sw: + return EZSeamType::PLUGIN; + default: return EZSeamType::SHORTEST; } } @@ -534,24 +522,22 @@ template<> EZSeamCornerPrefType Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "z_seam_corner_inner") + using namespace cura::utils; + switch (hash_enum(value)) { + case "z_seam_corner_none"_sw: + return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; + case "z_seam_corner_inner"_sw: return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER; - } - else if (value == "z_seam_corner_outer") - { + case "z_seam_corner_outer"_sw: return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER; - } - else if (value == "z_seam_corner_any") - { + case "z_seam_corner_any"_sw: return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY; - } - else if (value == "z_seam_corner_weighted") - { + case "z_seam_corner_weighted"_sw: return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED; - } - else // Default. - { + case "plugin"_sw: + return EZSeamCornerPrefType::PLUGIN; + default: return EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; } } @@ -560,16 +546,18 @@ template<> ESurfaceMode Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "surface") + using namespace cura::utils; + switch (hash_enum(value)) { + case "normal"_sw: + return ESurfaceMode::NORMAL; + case "surface"_sw: return ESurfaceMode::SURFACE; - } - else if (value == "both") - { + case "both"_sw: return ESurfaceMode::BOTH; - } - else // Default. - { + case "plugin"_sw: + return ESurfaceMode::PLUGIN; + default: return ESurfaceMode::NORMAL; } } @@ -577,12 +565,17 @@ ESurfaceMode Settings::get(const std::string& key) const template<> FillPerimeterGapMode Settings::get(const std::string& key) const { - if (get(key) == "everywhere") + const std::string& value = get(key); + using namespace cura::utils; + switch (hash_enum(value)) { + case "nowhere"_sw: + return FillPerimeterGapMode::NOWHERE; + case "everywhere"_sw: return FillPerimeterGapMode::EVERYWHERE; - } - else // Default. - { + case "plugin"_sw: + return FillPerimeterGapMode::PLUGIN; + default: return FillPerimeterGapMode::NOWHERE; } } @@ -590,12 +583,17 @@ FillPerimeterGapMode Settings::get(const std::string& key) template<> BuildPlateShape Settings::get(const std::string& key) const { - if (get(key) == "elliptic") + const std::string& value = get(key); + using namespace cura::utils; + switch (hash_enum(value)) { + case "rectangular"_sw: + return BuildPlateShape::RECTANGULAR; + case "elliptic"_sw: return BuildPlateShape::ELLIPTIC; - } - else // Default. - { + case "plugin"_sw: + return BuildPlateShape::PLUGIN; + default: return BuildPlateShape::RECTANGULAR; } } @@ -604,24 +602,22 @@ template<> CombingMode Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "off") + using namespace cura::utils; + switch (hash_enum(value)) { + case "all"_sw: + return CombingMode::ALL; + case "off"_sw: return CombingMode::OFF; - } - else if (value == "noskin") - { + case "noskin"_sw: return CombingMode::NO_SKIN; - } - else if (value == "no_outer_surfaces") - { + case "no_outer_surfaces"_sw: return CombingMode::NO_OUTER_SURFACES; - } - else if (value == "infill") - { + case "infill"_sw: return CombingMode::INFILL; - } - else // Default. - { + case "plugin"_sw: + return CombingMode::PLUGIN; + default: return CombingMode::ALL; } } @@ -629,12 +625,17 @@ CombingMode Settings::get(const std::string& key) const template<> SupportDistPriority Settings::get(const std::string& key) const { - if (get(key) == "z_overrides_xy") + const std::string& value = get(key); + using namespace cura::utils; + switch (hash_enum(value)) { + case "xy_overrides_z"_sw: + return SupportDistPriority::XY_OVERRIDES_Z; + case "z_overrides_xy"_sw: return SupportDistPriority::Z_OVERRIDES_XY; - } - else // Default. - { + case "plugin"_sw: + return SupportDistPriority::PLUGIN; + default: return SupportDistPriority::XY_OVERRIDES_Z; } } @@ -643,16 +644,18 @@ template<> SlicingTolerance Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "inclusive") + using namespace cura::utils; + switch (hash_enum(value)) { + case "middle"_sw: + return SlicingTolerance::MIDDLE; + case "inclusive"_sw: return SlicingTolerance::INCLUSIVE; - } - else if (value == "exclusive") - { + case "exclusive"_sw: return SlicingTolerance::EXCLUSIVE; - } - else // Default. - { + case "plugin"_sw: + return SlicingTolerance::PLUGIN; + default: return SlicingTolerance::MIDDLE; } } @@ -661,12 +664,16 @@ template<> InsetDirection Settings::get(const std::string& key) const { const std::string& value = get(key); - if (value == "outside_in") + using namespace cura::utils; + switch (hash_enum(value)) { + case "inside_out"_sw: + return InsetDirection::INSIDE_OUT; + case "outside_in"_sw: return InsetDirection::OUTSIDE_IN; - } - else // Default. - { + case "plugin"_sw: + return InsetDirection::PLUGIN; + default: return InsetDirection::INSIDE_OUT; } } @@ -798,4 +805,25 @@ std::string Settings::getWithoutLimiting(const std::string& key) const } } +std::unordered_map Settings::getFlattendSettings() const +{ + auto keys = getKeys(); + return keys + | ranges::views::transform( + [&](const auto& key) + { + return std::pair(key, get(key)); + }) + | ranges::to>(); +} + +std::vector Settings::getKeys() const +{ + if (parent) + { + return parent->getKeys(); + } + return ranges::views::keys(settings) | ranges::to_vector; +} + } // namespace cura diff --git a/src/skin.cpp b/src/skin.cpp index c7bb0c736f..28fc60c238 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -1,20 +1,21 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include // std::ceil +#include "skin.h" #include "Application.h" //To get settings. -#include "Slice.h" #include "ExtruderTrain.h" -#include "skin.h" +#include "Slice.h" +#include "WallToolPaths.h" #include "infill.h" -#include "sliceDataStorage.h" #include "settings/EnumSettings.h" //For EFillMethod. #include "settings/types/Angle.h" //For the infill support angle. #include "settings/types/Ratio.h" +#include "sliceDataStorage.h" #include "utils/math.h" #include "utils/polygonUtils.h" -#include "WallToolPaths.h" + +#include // std::ceil #define MIN_AREA_SIZE (0.4 * 0.4) @@ -33,18 +34,18 @@ coord_t SkinInfillAreaComputation::getSkinLineWidth(const SliceMeshStorage& mesh } SkinInfillAreaComputation::SkinInfillAreaComputation(const LayerIndex& layer_nr, SliceMeshStorage& mesh, bool process_infill) -: layer_nr(layer_nr) -, mesh(mesh) -, bottom_layer_count(mesh.settings.get("bottom_layers")) -, initial_bottom_layer_count(mesh.settings.get("initial_bottom_layers")) -, top_layer_count(mesh.settings.get("top_layers")) -, skin_line_width(getSkinLineWidth(mesh, layer_nr)) -, no_small_gaps_heuristic(mesh.settings.get("skin_no_small_gaps_heuristic")) -, process_infill(process_infill) -, top_skin_preshrink(mesh.settings.get("top_skin_preshrink")) -, bottom_skin_preshrink(mesh.settings.get("bottom_skin_preshrink")) -, top_skin_expand_distance(mesh.settings.get("top_skin_expand_distance")) -, bottom_skin_expand_distance(mesh.settings.get("bottom_skin_expand_distance")) + : layer_nr(layer_nr) + , mesh(mesh) + , bottom_layer_count(mesh.settings.get("bottom_layers")) + , initial_bottom_layer_count(mesh.settings.get("initial_bottom_layers")) + , top_layer_count(mesh.settings.get("top_layers")) + , skin_line_width(getSkinLineWidth(mesh, layer_nr)) + , no_small_gaps_heuristic(mesh.settings.get("skin_no_small_gaps_heuristic")) + , process_infill(process_infill) + , top_skin_preshrink(mesh.settings.get("top_skin_preshrink")) + , bottom_skin_preshrink(mesh.settings.get("bottom_skin_preshrink")) + , top_skin_expand_distance(mesh.settings.get("top_skin_expand_distance")) + , bottom_skin_expand_distance(mesh.settings.get("bottom_skin_expand_distance")) { } @@ -105,12 +106,12 @@ void SkinInfillAreaComputation::generateSkinAndInfillAreas() { SliceLayer& layer = mesh.layers[layer_nr]; - if (!process_infill && bottom_layer_count == 0 && top_layer_count == 0) + if (! process_infill && bottom_layer_count == 0 && top_layer_count == 0) { return; } - for(SliceLayerPart& part : layer.parts) + for (SliceLayerPart& part : layer.parts) { generateSkinAndInfillAreas(part); } @@ -124,7 +125,7 @@ void SkinInfillAreaComputation::generateSkinAndInfillAreas() */ void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part) { - //Make a copy of the outline which we later intersect and union with the resized skins to ensure the resized skin isn't too large or removed completely. + // Make a copy of the outline which we later intersect and union with the resized skins to ensure the resized skin isn't too large or removed completely. Polygons top_skin; if (top_layer_count > 0) { @@ -141,15 +142,16 @@ void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part) applySkinExpansion(part.inner_area, top_skin, bottom_skin); - //Now combine the resized top skin and bottom skin. + // Now combine the resized top skin and bottom skin. Polygons skin = top_skin.unionPolygons(bottom_skin); skin.removeSmallAreas(MIN_AREA_SIZE); - + // Create infill area irrespective if the infill is to be generated or not(would be used for bridging). + part.infill_area = part.inner_area.difference(skin); if (process_infill) { // process infill when infill density > 0 // or when other infill meshes want to modify this infill - generateInfill(part, skin); + generateInfill(part); } for (PolygonsPart& skin_area_part : skin.splitIntoParts()) @@ -175,9 +177,9 @@ void SkinInfillAreaComputation::calculateBottomSkin(const SliceLayerPart& part, { return; // don't subtract anything form the downskin } - LayerIndex bottom_check_start_layer_idx = std::max(LayerIndex(0), layer_nr - bottom_layer_count); + LayerIndex bottom_check_start_layer_idx{ std::max(LayerIndex{ 0 }, LayerIndex{ layer_nr - bottom_layer_count }) }; Polygons not_air = getOutlineOnLayer(part, bottom_check_start_layer_idx); - if (!no_small_gaps_heuristic) + if (! no_small_gaps_heuristic) { for (int downskin_layer_nr = bottom_check_start_layer_idx + 1; downskin_layer_nr < layer_nr; downskin_layer_nr++) { @@ -194,15 +196,15 @@ void SkinInfillAreaComputation::calculateBottomSkin(const SliceLayerPart& part, void SkinInfillAreaComputation::calculateTopSkin(const SliceLayerPart& part, Polygons& upskin) { - if(layer_nr > LayerIndex(mesh.layers.size()) - top_layer_count || top_layer_count <= 0) + if (layer_nr > LayerIndex(mesh.layers.size()) - top_layer_count || top_layer_count <= 0) { - //If we're in the very top layers (less than top_layer_count from the top of the mesh) everything will be top skin anyway, so no need to generate infill. Just take the original inner contour. - //If top_layer_count is 0, no need to calculate anything either. + // If we're in the very top layers (less than top_layer_count from the top of the mesh) everything will be top skin anyway, so no need to generate infill. Just take the + // original inner contour. If top_layer_count is 0, no need to calculate anything either. return; } Polygons not_air = getOutlineOnLayer(part, layer_nr + top_layer_count); - if (!no_small_gaps_heuristic) + if (! no_small_gaps_heuristic) { for (int upskin_layer_nr = layer_nr + 1; upskin_layer_nr < layer_nr + top_layer_count; upskin_layer_nr++) { @@ -230,17 +232,17 @@ void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outl const coord_t min_width = mesh.settings.get("min_skin_width_for_expansion") / 2; // Expand some areas of the skin for Skin Expand Distance. - if(min_width > 0) + if (min_width > 0) { // This performs an opening operation by first insetting by the minimum width, then offsetting with the same width. // The expansion is only applied to that opened shape. - if(bottom_skin_expand_distance != 0) + if (bottom_skin_expand_distance != 0) { const Polygons expanded = downskin.offset(-min_width).offset(min_width + bottom_skin_expand_distance); // And then re-joined with the original part that was not offset, to retain parts smaller than min_width. downskin = downskin.unionPolygons(expanded); } - if(top_skin_expand_distance != 0) + if (top_skin_expand_distance != 0) { const Polygons expanded = upskin.offset(-min_width).offset(min_width + top_skin_expand_distance); upskin = upskin.unionPolygons(expanded); @@ -248,11 +250,11 @@ void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outl } else // No need to pay attention to minimum width. Just expand. { - if(bottom_skin_expand_distance != 0) + if (bottom_skin_expand_distance != 0) { downskin = downskin.offset(bottom_skin_expand_distance); } - if(top_skin_expand_distance != 0) + if (top_skin_expand_distance != 0) { upskin = upskin.offset(top_skin_expand_distance); } @@ -276,22 +278,24 @@ void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outl // as the final polygon is limited (intersected) with the original polygon. constexpr double MITER_LIMIT = 10000000.0; // Remove thin pieces of support for Skin Removal Width. - if(bottom_skin_preshrink > 0 || (min_width == 0 && bottom_skin_expand_distance != 0)) + if (bottom_skin_preshrink > 0 || (min_width == 0 && bottom_skin_expand_distance != 0)) { - downskin = downskin.offset(-bottom_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT).offset(bottom_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT).intersection(downskin); - should_bottom_be_clipped = true; // Rounding errors can lead to propagation of errors. This could mean that skin goes beyond the original outline + downskin = downskin.offset(-bottom_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT) + .offset(bottom_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT) + .intersection(downskin); + should_bottom_be_clipped = true; // Rounding errors can lead to propagation of errors. This could mean that skin goes beyond the original outline } - if(top_skin_preshrink > 0 || (min_width == 0 && top_skin_expand_distance != 0)) + if (top_skin_preshrink > 0 || (min_width == 0 && top_skin_expand_distance != 0)) { upskin = upskin.offset(-top_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT).offset(top_skin_preshrink / 2, ClipperLib::jtMiter, MITER_LIMIT); - should_top_be_clipped = true; // Rounding errors can lead to propagation of errors. This could mean that skin goes beyond the original outline + should_top_be_clipped = true; // Rounding errors can lead to propagation of errors. This could mean that skin goes beyond the original outline } - if(should_bottom_be_clipped) + if (should_bottom_be_clipped) { downskin = downskin.intersection(original_outline); } - if(should_top_be_clipped) + if (should_top_be_clipped) { upskin = upskin.intersection(original_outline); } @@ -303,9 +307,9 @@ void SkinInfillAreaComputation::applySkinExpansion(const Polygons& original_outl * * generateInfill read mesh.layers[n].parts[*].{insets,skin_parts,boundingBox} and write mesh.layers[n].parts[*].infill_area */ -void SkinInfillAreaComputation::generateInfill(SliceLayerPart& part, const Polygons& skin) +void SkinInfillAreaComputation::generateInfill(SliceLayerPart& part) { - part.infill_area = part.inner_area.difference(skin); //Generate infill everywhere where there wasn't any skin. + // Generate infill everywhere where there wasn't any skin. part.infill_area.removeSmallAreas(MIN_AREA_SIZE); } @@ -317,7 +321,7 @@ void SkinInfillAreaComputation::generateInfill(SliceLayerPart& part, const Polyg */ void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& part) { - for(SkinPart& skin_part : part.skin_parts) + for (SkinPart& skin_part : part.skin_parts) { const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); const coord_t skin_overlap = mesh.settings.get("skin_overlap_mm"); @@ -342,10 +346,8 @@ void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& p */ Polygons SkinInfillAreaComputation::generateFilledAreaAbove(SliceLayerPart& part, size_t roofing_layer_count) { - const size_t wall_idx = std::min(size_t(2), mesh.settings.get("wall_line_count")); - Polygons filled_area_above = getOutlineOnLayer(part, layer_nr + roofing_layer_count); - if (!no_small_gaps_heuristic) + if (! no_small_gaps_heuristic) { for (int layer_nr_above = layer_nr + 1; layer_nr_above < layer_nr + roofing_layer_count; layer_nr_above++) { @@ -363,7 +365,7 @@ Polygons SkinInfillAreaComputation::generateFilledAreaAbove(SliceLayerPart& part // set air_below to the skin area for the current layer that has air below it Polygons air_below = getOutlineOnLayer(part, layer_nr).difference(getOutlineOnLayer(part, layer_nr - 1)); - if (!air_below.empty()) + if (! air_below.empty()) { // add the polygons that have air below to the no air above polygons filled_area_above = filled_area_above.unionPolygons(air_below); @@ -378,34 +380,34 @@ Polygons SkinInfillAreaComputation::generateFilledAreaAbove(SliceLayerPart& part * * this function may only read the skin and infill from the *current* layer. */ - Polygons SkinInfillAreaComputation::generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count) +Polygons SkinInfillAreaComputation::generateFilledAreaBelow(SliceLayerPart& part, size_t flooring_layer_count) +{ + if (layer_nr < flooring_layer_count) { - if (layer_nr < flooring_layer_count) - { - return {}; - } - constexpr size_t min_wall_line_count = 2; - const int lowest_flooring_layer = layer_nr - flooring_layer_count; - Polygons filled_area_below = getOutlineOnLayer(part, lowest_flooring_layer); + return {}; + } + constexpr size_t min_wall_line_count = 2; + const int lowest_flooring_layer = layer_nr - flooring_layer_count; + Polygons filled_area_below = getOutlineOnLayer(part, lowest_flooring_layer); - if (!no_small_gaps_heuristic) + if (! no_small_gaps_heuristic) + { + const int next_lowest_flooring_layer = lowest_flooring_layer + 1; + for (int layer_nr_below = next_lowest_flooring_layer; layer_nr_below < layer_nr; layer_nr_below++) { - const int next_lowest_flooring_layer = lowest_flooring_layer + 1; - for (int layer_nr_below = next_lowest_flooring_layer; layer_nr_below < layer_nr; layer_nr_below++) - { - Polygons outlines_below = getOutlineOnLayer(part, layer_nr_below); - filled_area_below = filled_area_below.intersection(outlines_below); - } + Polygons outlines_below = getOutlineOnLayer(part, layer_nr_below); + filled_area_below = filled_area_below.intersection(outlines_below); } - return filled_area_below; } + return filled_area_below; +} void SkinInfillAreaComputation::generateInfillSupport(SliceMeshStorage& mesh) { const coord_t layer_height = mesh.settings.get("layer_height"); const AngleRadians support_angle = mesh.settings.get("infill_support_angle"); - const double tan_angle = tan(support_angle) - 0.01; //The X/Y component of the support angle. 0.01 to make 90 degrees work too. - const coord_t max_dist_from_lower_layer = tan_angle * layer_height; //Maximum horizontal distance that can be bridged. + const double tan_angle = tan(support_angle) - 0.01; // The X/Y component of the support angle. 0.01 to make 90 degrees work too. + const coord_t max_dist_from_lower_layer = tan_angle * layer_height; // Maximum horizontal distance that can be bridged. for (int layer_idx = mesh.layers.size() - 2; layer_idx >= 0; layer_idx--) { @@ -443,15 +445,17 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) { // no early-out for this function; it needs to initialize the [infill_area_per_combine_per_density] float layer_skip_count = 8; // skip every so many layers as to ignore small gaps in the model making computation more easy - if (!mesh.settings.get("skin_no_small_gaps_heuristic")) + if (! mesh.settings.get("skin_no_small_gaps_heuristic")) { layer_skip_count = 1; } const coord_t gradual_infill_step_height = mesh.settings.get("gradual_infill_step_height"); - const size_t gradual_infill_step_layer_count = round_divide(gradual_infill_step_height, mesh.settings.get("layer_height")); // The difference in layer count between consecutive density infill areas + const size_t gradual_infill_step_layer_count + = round_divide(gradual_infill_step_height, mesh.settings.get("layer_height")); // The difference in layer count between consecutive density infill areas // make gradual_infill_step_height divisible by layer_skip_count - float n_skip_steps_per_gradual_step = std::max(1.0f, std::ceil(gradual_infill_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_infill_step_layer_count + float n_skip_steps_per_gradual_step + = std::max(1.0f, std::ceil(gradual_infill_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_infill_step_layer_count layer_skip_count = gradual_infill_step_layer_count / n_skip_steps_per_gradual_step; const size_t max_infill_steps = mesh.settings.get("gradual_infill_steps"); @@ -469,7 +473,15 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) { assert((part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is supposed to be uninitialized")); - const Polygons& infill_area = Infill::generateWallToolPaths(part.infill_wall_toolpaths, part.getOwnInfillArea(), infill_wall_count, infill_wall_width, infill_overlap, mesh.settings, layer_idx, SectionType::SKIN); + const Polygons& infill_area = Infill::generateWallToolPaths( + part.infill_wall_toolpaths, + part.getOwnInfillArea(), + infill_wall_count, + infill_wall_width, + infill_overlap, + mesh.settings, + layer_idx, + SectionType::SKIN); if (infill_area.empty() || layer_idx < min_layer || layer_idx > max_layer) { // initialize infill_area_per_combine_per_density empty @@ -495,7 +507,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) Polygons relevent_upper_polygons; for (const SliceLayerPart& upper_layer_part : upper_layer.parts) { - if (!upper_layer_part.boundaryBox.hit(part.boundaryBox)) + if (! upper_layer_part.boundaryBox.hit(part.boundaryBox)) { continue; } @@ -517,21 +529,26 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); infill_area_per_combine_current_density.push_back(infill_area); part.infill_area_own = std::nullopt; // clear infill_area_own, it's not needed any more. - assert(!part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is now initialized"); + assert(! part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is now initialized"); } } } void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) { - if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast(mesh.settings.get("top_layers")) || mesh.settings.get("infill_line_distance") == 0) //No infill is even generated. + if (mesh.layers.empty() || mesh.layers.size() - 1 < static_cast(mesh.settings.get("top_layers")) + || mesh.settings.get("infill_line_distance") == 0) // No infill is even generated. { return; } const coord_t layer_height = mesh.settings.get("layer_height"); - const size_t amount = std::max(uint64_t(1), round_divide(mesh.settings.get("infill_sparse_thickness"), std::max(layer_height, coord_t(1)))); //How many infill layers to combine to obtain the requested sparse thickness. - if(amount <= 1) //If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid. + const size_t amount = std::max( + uint64_t(1), + round_divide( + mesh.settings.get("infill_sparse_thickness"), + std::max(layer_height, coord_t(1)))); // How many infill layers to combine to obtain the requested sparse thickness. + if (amount <= 1) // If we must combine 1 layer, nothing needs to be combined. Combining 0 layers is invalid. { return; } @@ -542,15 +559,15 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) other. */ size_t bottom_most_layers = mesh.settings.get("initial_bottom_layers"); LayerIndex min_layer = static_cast(bottom_most_layers + amount) - 1; - min_layer -= min_layer % amount; //Round upwards to the nearest layer divisible by infill_sparse_combine. + min_layer -= min_layer % amount; // Round upwards to the nearest layer divisible by infill_sparse_combine. LayerIndex max_layer = static_cast(mesh.layers.size()) - 1 - mesh.settings.get("top_layers"); - max_layer -= max_layer % amount; //Round downwards to the nearest layer divisible by infill_sparse_combine. - for(LayerIndex layer_idx = min_layer; layer_idx <= max_layer; layer_idx += amount) //Skip every few layers, but extrude more. + max_layer -= max_layer % amount; // Round downwards to the nearest layer divisible by infill_sparse_combine. + for (LayerIndex layer_idx = min_layer; layer_idx <= max_layer; layer_idx += amount) // Skip every few layers, but extrude more. { SliceLayer* layer = &mesh.layers[layer_idx]; - for(size_t combine_count_here = 1; combine_count_here < amount; combine_count_here++) + for (size_t combine_count_here = 1; combine_count_here < amount; combine_count_here++) { - if(layer_idx < static_cast(combine_count_here)) + if (layer_idx < static_cast(combine_count_here)) { break; } @@ -571,10 +588,10 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) { if (part.boundaryBox.hit(lower_layer_part.boundaryBox)) { - Polygons intersection = infill_area_per_combine[combine_count_here - 1].intersection(lower_layer_part.infill_area).offset(-200).offset(200); result.add(intersection); // add area to be thickened - infill_area_per_combine[combine_count_here - 1] = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here + infill_area_per_combine[combine_count_here - 1] + = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here unsigned int max_lower_density_idx = density_idx; // Generally: remove only from *same density* areas on layer below // If there are no same density areas, then it's ok to print them anyway @@ -588,10 +605,13 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) // of the lower layer with the same or higher density index max_lower_density_idx = lower_layer_part.infill_area_per_combine_per_density.size() - 1; } - for (size_t lower_density_idx = density_idx; lower_density_idx <= max_lower_density_idx && lower_density_idx < lower_layer_part.infill_area_per_combine_per_density.size(); lower_density_idx++) + for (size_t lower_density_idx = density_idx; + lower_density_idx <= max_lower_density_idx && lower_density_idx < lower_layer_part.infill_area_per_combine_per_density.size(); + lower_density_idx++) { std::vector& lower_infill_area_per_combine = lower_layer_part.infill_area_per_combine_per_density[lower_density_idx]; - lower_infill_area_per_combine[0] = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (single thickness) layer + lower_infill_area_per_combine[0] + = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (single thickness) layer } } } @@ -610,9 +630,10 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) * this function may only read/write the skin and infill from the *current* layer. */ -void SkinInfillAreaComputation::generateTopAndBottomMostSkinFill(SliceLayerPart &part) { - - for (SkinPart& skin_part : part.skin_parts) { +void SkinInfillAreaComputation::generateTopAndBottomMostSkinFill(SliceLayerPart& part) +{ + for (SkinPart& skin_part : part.skin_parts) + { Polygons filled_area_above = generateFilledAreaAbove(part, 1); skin_part.top_most_surface_fill = skin_part.outline.difference(filled_area_above); @@ -622,4 +643,4 @@ void SkinInfillAreaComputation::generateTopAndBottomMostSkinFill(SliceLayerPart } -}//namespace cura +} // namespace cura diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 6dfe67a5e2..fbd4a5d1a9 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -1,6 +1,8 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "sliceDataStorage.h" + #include #include "Application.h" //To get settings. @@ -12,24 +14,22 @@ #include "infill/SierpinskiFillProvider.h" #include "infill/SubDivCube.h" // For the destructor #include "raft.h" -#include "sliceDataStorage.h" #include "utils/math.h" //For PI. namespace cura { -SupportStorage::SupportStorage() : generated(false), layer_nr_max_filled_layer(-1), cross_fill_provider(nullptr) +SupportStorage::SupportStorage() + : generated(false) + , layer_nr_max_filled_layer(-1) + , cross_fill_provider(nullptr) { } SupportStorage::~SupportStorage() { supportLayers.clear(); - if (cross_fill_provider) - { - delete cross_fill_provider; - } } Polygons& SliceLayerPart::getOwnInfillArea() @@ -102,21 +102,6 @@ SliceMeshStorage::SliceMeshStorage(Mesh* mesh, const size_t slice_layer_count) layers.resize(slice_layer_count); } -SliceMeshStorage::~SliceMeshStorage() -{ - if (base_subdiv_cube) - { - delete base_subdiv_cube; - } - if (cross_fill_provider) - { - delete cross_fill_provider; - } - if (lightning_generator) - { - delete lightning_generator; - } -} bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const { @@ -143,7 +128,8 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr) const { return true; } - if ((settings.get("wall_line_count") > 1 || settings.get("alternate_extra_perimeter")) && settings.get("wall_x_extruder_nr").extruder_nr == extruder_nr) + if ((settings.get("wall_line_count") > 1 || settings.get("alternate_extra_perimeter")) + && settings.get("wall_x_extruder_nr").extruder_nr == extruder_nr) { return true; } @@ -174,7 +160,8 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr, const LayerIn return false; } const SliceLayer& layer = layers[layer_nr]; - if (settings.get("wall_0_extruder_nr").extruder_nr == extruder_nr && (settings.get("wall_line_count") > 0 || settings.get("skin_outline_count") > 0)) + if (settings.get("wall_0_extruder_nr").extruder_nr == extruder_nr + && (settings.get("wall_line_count") > 0 || settings.get("skin_outline_count") > 0)) { for (const SliceLayerPart& part : layer.parts) { @@ -184,11 +171,13 @@ bool SliceMeshStorage::getExtruderIsUsed(const size_t extruder_nr, const LayerIn } } } - if (settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && settings.get("wall_0_extruder_nr").extruder_nr == extruder_nr && layer.openPolyLines.size() > 0) + if (settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL && settings.get("wall_0_extruder_nr").extruder_nr == extruder_nr + && layer.openPolyLines.size() > 0) { return true; } - if ((settings.get("wall_line_count") > 1 || settings.get("alternate_extra_perimeter")) && settings.get("wall_x_extruder_nr").extruder_nr == extruder_nr) + if ((settings.get("wall_line_count") > 1 || settings.get("alternate_extra_perimeter")) + && settings.get("wall_x_extruder_nr").extruder_nr == extruder_nr) { for (const SliceLayerPart& part : layer.parts) { @@ -260,10 +249,10 @@ std::vector SliceDataStorage::initializeRetractionAndWi return ret; } -SliceDataStorage::SliceDataStorage() : - print_layer_count(0), - retraction_wipe_config_per_extruder(initializeRetractionAndWipeConfigs()), - max_print_height_second_to_last_extruder(-1) +SliceDataStorage::SliceDataStorage() + : print_layer_count(0) + , retraction_wipe_config_per_extruder(initializeRetractionAndWipeConfigs()) + , max_print_height_second_to_last_extruder(-1) { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; Point3 machine_max(mesh_group_settings.get("machine_width"), mesh_group_settings.get("machine_depth"), mesh_group_settings.get("machine_height")); @@ -277,7 +266,9 @@ SliceDataStorage::SliceDataStorage() : machine_size.include(machine_max); } -Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only, const int extruder_nr) const +Polygons + SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const bool include_support, const bool include_prime_tower, const bool external_polys_only, const int extruder_nr) + const { const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; if (layer_nr < 0 && layer_nr < -static_cast(Raft::getFillerLayerCount())) @@ -309,16 +300,16 @@ Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const boo Polygons total; if (layer_nr >= 0) { - for (const SliceMeshStorage& mesh : meshes) + for (const std::shared_ptr& mesh : meshes) { - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh") - || (extruder_nr != -1 && extruder_nr != int(mesh.settings.get("wall_0_extruder_nr").extruder_nr))) + if (mesh->settings.get("infill_mesh") || mesh->settings.get("anti_overhang_mesh") + || (extruder_nr != -1 && extruder_nr != int(mesh->settings.get("wall_0_extruder_nr").extruder_nr))) { continue; } - const SliceLayer& layer = mesh.layers[layer_nr]; + const SliceLayer& layer = mesh->layers[layer_nr]; layer.getOutlines(total, external_polys_only); - if (mesh.settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) + if (mesh->settings.get("magic_mesh_surface_mode") != ESurfaceMode::NORMAL) { total = total.unionPolygons(layer.openPolyLines.offsetPolyLine(MM2INT(0.1))); } @@ -342,7 +333,7 @@ Polygons SliceDataStorage::getLayerOutlines(const LayerIndex layer_nr, const boo { if (primeTower.enabled) { - total.add(primeTower.outer_poly); + total.add(primeTower.getOuterPoly(layer_nr)); } } return total; @@ -390,9 +381,9 @@ std::vector SliceDataStorage::getExtrudersUsed() const // support // support is presupposed to be present... - for (const SliceMeshStorage& mesh : meshes) + for (const std::shared_ptr& mesh : meshes) { - if (mesh.settings.get("support_enable") || mesh.settings.get("support_mesh")) + if (mesh->settings.get("support_enable") || mesh->settings.get("support_mesh")) { ret[mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr] = true; ret[mesh_group_settings.get("support_infill_extruder_nr").extruder_nr] = true; @@ -408,11 +399,11 @@ std::vector SliceDataStorage::getExtrudersUsed() const } // all meshes are presupposed to actually have content - for (const SliceMeshStorage& mesh : meshes) + for (const std::shared_ptr& mesh : meshes) { for (unsigned int extruder_nr = 0; extruder_nr < ret.size(); extruder_nr++) { - ret[extruder_nr] = ret[extruder_nr] || mesh.getExtruderIsUsed(extruder_nr); + ret[extruder_nr] = ret[extruder_nr] || mesh->getExtruderIsUsed(extruder_nr); } } return ret; @@ -488,7 +479,8 @@ std::vector SliceDataStorage::getExtrudersUsed(const LayerIndex layer_nr) // support if (layer_nr < int(support.supportLayers.size())) { - const SupportLayer& support_layer = support.supportLayers[std::max(LayerIndex(0), layer_nr)]; // Below layer 0, it's the same as layer 0 (even though it's not stored here). + const SupportLayer& support_layer + = support.supportLayers[std::max(LayerIndex(0), layer_nr)]; // Below layer 0, it's the same as layer 0 (even though it's not stored here). if (layer_nr == 0) { if (! support_layer.support_infill_parts.empty()) @@ -516,11 +508,11 @@ std::vector SliceDataStorage::getExtrudersUsed(const LayerIndex layer_nr) if (include_models) { - for (const SliceMeshStorage& mesh : meshes) + for (const std::shared_ptr& mesh : meshes) { for (unsigned int extruder_nr = 0; extruder_nr < ret.size(); extruder_nr++) { - ret[extruder_nr] = ret[extruder_nr] || mesh.getExtruderIsUsed(extruder_nr, layer_nr); + ret[extruder_nr] = ret[extruder_nr] || mesh->getExtruderIsUsed(extruder_nr, layer_nr); } } } @@ -571,7 +563,7 @@ Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const for (PolygonRef poly : disallowed_areas) for (Point& p : poly) p = Point(machine_size.max.x / 2 + p.X, machine_size.max.y / 2 - p.Y); // apparently the frontend stores the disallowed areas in a different coordinate system - + std::vector extruder_is_used = getExtrudersUsed(); constexpr coord_t prime_clearance = MM2INT(6.5); @@ -582,7 +574,7 @@ Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const continue; } Settings& extruder_settings = Application::getInstance().current_slice->scene.extruders[extruder_nr].settings; - if (!(extruder_settings.get("prime_blob_enable") && mesh_group_settings.get("extruder_prime_pos_abs"))) + if (! (extruder_settings.get("prime_blob_enable") && mesh_group_settings.get("extruder_prime_pos_abs"))) { continue; } @@ -602,7 +594,7 @@ Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const bool first = true; for (size_t extruder_nr = 0; extruder_nr < extruder_is_used.size(); extruder_nr++) { - if ((checking_extruder_nr != -1 && int(extruder_nr) != checking_extruder_nr) || !extruder_is_used[extruder_nr]) + if ((checking_extruder_nr != -1 && int(extruder_nr) != checking_extruder_nr) || ! extruder_is_used[extruder_nr]) { continue; } @@ -621,7 +613,7 @@ Polygons SliceDataStorage::getMachineBorder(int checking_extruder_nr) const } } disallowed_all_extruders.processEvenOdd(ClipperLib::pftNonZero); // prevent overlapping disallowed areas from XORing - + Polygons border_all_extruders = border; // each extruders border areas must be limited to the global border, which is the union of all extruders borders if (mesh_group_settings.has("nozzle_offsetting_for_disallowed_areas") && mesh_group_settings.get("nozzle_offsetting_for_disallowed_areas")) { @@ -713,4 +705,27 @@ void SupportLayer::excludeAreasFromSupportInfillAreas(const Polygons& exclude_po } } +void SupportLayer::fillInfillParts( + const LayerIndex layer_nr, + const std::vector& support_fill_per_layer, + const coord_t support_line_width, + const coord_t wall_line_count, + const coord_t grow_layer_above /*has default 0*/, + const bool unionAll /*has default false*/) +{ + const Polygons& support_this_layer = support_fill_per_layer[layer_nr]; + const Polygons& support_layer_above + = (layer_nr + 1) >= support_fill_per_layer.size() || layer_nr <= 0 ? Polygons() : support_fill_per_layer[layer_nr + 1].offset(grow_layer_above); + const auto all_support_areas_in_layer = { support_this_layer.difference(support_layer_above), support_this_layer.intersection(support_layer_above) }; + bool use_fractional_config = true; + for (auto& support_areas : all_support_areas_in_layer) + { + for (const PolygonsPart& island_outline : support_areas.splitIntoParts(unionAll)) + { + support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count); + } + use_fractional_config = false; + } +} + } // namespace cura diff --git a/src/slicer.cpp b/src/slicer.cpp index 889429ceb0..2758d133f0 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -1,6 +1,8 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "slicer.h" + #include // remove_if #include #include @@ -10,10 +12,10 @@ #include "Application.h" #include "Slice.h" +#include "plugins/slots.h" #include "settings/AdaptiveLayerHeights.h" #include "settings/EnumSettings.h" #include "settings/types/LayerIndex.h" -#include "slicer.h" #include "utils/Simplify.h" #include "utils/SparsePointGridInclusive.h" #include "utils/ThreadPool.h" @@ -407,7 +409,8 @@ void SlicerLayer::joinPolylines(PolygonRef& polyline_0, PolygonRef& polyline_1, polyline_1.clear(); } -SlicerLayer::TerminusTrackingMap::TerminusTrackingMap(Terminus::Index end_idx) : m_terminus_old_to_cur_map(end_idx) +SlicerLayer::TerminusTrackingMap::TerminusTrackingMap(Terminus::Index end_idx) + : m_terminus_old_to_cur_map(end_idx) { // Initialize map to everything points to itself since nothing has moved yet. for (size_t idx = 0U; idx != end_idx; ++idx) @@ -417,7 +420,12 @@ SlicerLayer::TerminusTrackingMap::TerminusTrackingMap(Terminus::Index end_idx) : m_terminus_cur_to_old_map = m_terminus_old_to_cur_map; } -void SlicerLayer::TerminusTrackingMap::updateMap(size_t num_terms, const Terminus* cur_terms, const Terminus* next_terms, size_t num_removed_terms, const Terminus* removed_cur_terms) +void SlicerLayer::TerminusTrackingMap::updateMap( + size_t num_terms, + const Terminus* cur_terms, + const Terminus* next_terms, + size_t num_removed_terms, + const Terminus* removed_cur_terms) { // save old locations std::vector old_terms(num_terms); @@ -736,10 +744,12 @@ void SlicerLayer::makePolygons(const Mesh* mesh) connectOpenPolylines(open_polylines); - // TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in the middle between the two open polygons. + // TODO: (?) for mesh surface mode: connect open polygons. Maybe the above algorithm can create two open polygons which are actually connected when the starting segment is in + // the middle between the two open polygons. if (mesh->settings.get("magic_mesh_surface_mode") == ESurfaceMode::NORMAL) - { // don't stitch when using (any) mesh surface mode, i.e. also don't stitch when using mixed mesh surface and closed polygons, because then polylines which are supposed to be open will be closed + { // don't stitch when using (any) mesh surface mode, i.e. also don't stitch when using mixed mesh surface and closed polygons, because then polylines which are supposed to be + // open will be closed stitch(open_polylines); } @@ -767,22 +777,39 @@ void SlicerLayer::makePolygons(const Mesh* mesh) // Remove all the tiny polygons, or polygons that are not closed. As they do not contribute to the actual print. const coord_t snap_distance = std::max(mesh->settings.get("minimum_polygon_circumference"), static_cast(1)); - auto it = std::remove_if(polygons.begin(), polygons.end(), [snap_distance](PolygonRef poly) { return poly.shorterThan(snap_distance); }); + auto it = std::remove_if( + polygons.begin(), + polygons.end(), + [snap_distance](PolygonRef poly) + { + return poly.shorterThan(snap_distance); + }); polygons.erase(it, polygons.end()); // Finally optimize all the polygons. Every point removed saves time in the long run. - polygons = Simplify(mesh->settings).polygon(polygons); - + // polygons = Simplify(mesh->settings).polygon(polygons); + polygons = slots::instance().modify( + polygons, + mesh->settings.get("meshfix_maximum_resolution"), + mesh->settings.get("meshfix_maximum_deviation"), + static_cast(mesh->settings.get("meshfix_maximum_extrusion_area_deviation"))); polygons.removeDegenerateVerts(); // remove verts connected to overlapping line segments // Clean up polylines for Surface Mode printing - it = std::remove_if(openPolylines.begin(), openPolylines.end(), [snap_distance](PolygonRef poly) { return poly.shorterThan(snap_distance); }); + it = std::remove_if( + openPolylines.begin(), + openPolylines.end(), + [snap_distance](PolygonRef poly) + { + return poly.shorterThan(snap_distance); + }); openPolylines.erase(it, openPolylines.end()); openPolylines.removeDegenerateVertsPolyline(); } -Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_count, bool use_variable_layer_heights, std::vector* adaptive_layers) : mesh(i_mesh) +Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_count, bool use_variable_layer_heights, std::vector* adaptive_layers) + : mesh(i_mesh) { const SlicingTolerance slicing_tolerance = mesh->settings.get("slicing_tolerance"); const coord_t initial_layer_thickness = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height_0"); @@ -792,152 +819,167 @@ Slicer::Slicer(Mesh* i_mesh, const coord_t thickness, const size_t slice_layer_c TimeKeeper slice_timer; layers = buildLayersWithHeight(slice_layer_count, slicing_tolerance, initial_layer_thickness, thickness, use_variable_layer_heights, adaptive_layers); - scripta::setAll(layers); + scripta::setAll( + layers, + static_cast(mesh->settings.get("adhesion_type")), + mesh->settings.get("raft_surface_layers"), + mesh->settings.get("raft_surface_thickness"), + mesh->settings.get("raft_interface_layers"), + mesh->settings.get("raft_interface_thickness"), + mesh->settings.get("raft_base_thickness"), + mesh->settings.get("raft_airgap"), + mesh->settings.get("layer_0_z_overlap")); std::vector> zbbox = buildZHeightsForFaces(*mesh); buildSegments(*mesh, zbbox, slicing_tolerance, layers); - spdlog::info("Slice of mesh took {:3} seconds", slice_timer.restart()); + spdlog::info("Slice of mesh took {:03.3f} seconds", slice_timer.restart()); makePolygons(*i_mesh, slicing_tolerance, layers); scripta::log("sliced_polygons", layers, SectionType::NA); - spdlog::info("Make polygons took {:3} seconds", slice_timer.restart()); + spdlog::info("Make polygons took {:03.3f} seconds", slice_timer.restart()); } void Slicer::buildSegments(const Mesh& mesh, const std::vector>& zbbox, const SlicingTolerance& slicing_tolerance, std::vector& layers) { - cura::parallel_for(layers, - [&](auto layer_it) - { - SlicerLayer& layer = *layer_it; - const int32_t& z = layer.z; - layer.segments.reserve(100); - - // loop over all mesh faces - for (unsigned int mesh_idx = 0; mesh_idx < mesh.faces.size(); mesh_idx++) - { - if ((z < zbbox[mesh_idx].first) || (z > zbbox[mesh_idx].second)) - { - continue; - } - - // get all vertices per face - const MeshFace& face = mesh.faces[mesh_idx]; - const MeshVertex& v0 = mesh.vertices[face.vertex_index[0]]; - const MeshVertex& v1 = mesh.vertices[face.vertex_index[1]]; - const MeshVertex& v2 = mesh.vertices[face.vertex_index[2]]; - - // get all vertices represented as 3D point - Point3 p0 = v0.p; - Point3 p1 = v1.p; - Point3 p2 = v2.p; - - // Compensate for points exactly on the slice-boundary, except for 'inclusive', which already handles this correctly. - if (slicing_tolerance != SlicingTolerance::INCLUSIVE) - { - p0.z += static_cast(p0.z == z) * -static_cast(p0.z < 1); - p1.z += static_cast(p1.z == z) * -static_cast(p1.z < 1); - p2.z += static_cast(p2.z == z) * -static_cast(p2.z < 1); - } - - SlicerSegment s; - s.endVertex = nullptr; - int end_edge_idx = -1; - - /* - Now see if the triangle intersects the layer, and if so, where. - - Edge cases are important here: - - If all three vertices of the triangle are exactly on the layer, - don't count the triangle at all, because if the model is - watertight, there will be adjacent triangles on all 3 sides that - are not flat on the layer. - - If two of the vertices are exactly on the layer, only count the - triangle if the last vertex is going up. We can't count both - upwards and downwards triangles here, because if the model is - manifold there will always be an adjacent triangle that is going - the other way and you'd get double edges. You would also get one - layer too many if the total model height is an exact multiple of - the layer thickness. Between going up and going down, we need to - choose the triangles going up, because otherwise the first layer - of where the model starts will be empty and the model will float - in mid-air. We'd much rather let the last layer be empty in that - case. - - If only one of the vertices is exactly on the layer, the - intersection between the triangle and the plane would be a point. - We can't print points and with a manifold model there would be - line segments adjacent to the point on both sides anyway, so we - need to discard this 0-length line segment then. - - Vertices in ccw order if look from outside. - */ - - if (p0.z < z && p1.z > z && p2.z > z) // 1_______2 - { // \ / - s = project2D(p0, p2, p1, z); //------------- z - end_edge_idx = 0; // \ / - } // 0 - - else if (p0.z > z && p1.z <= z && p2.z <= z) // 0 - { // / \ . - s = project2D(p0, p1, p2, z); //------------- z - end_edge_idx = 2; // / \ . - if (p2.z == z) // 1_______2 - { - s.endVertex = &v2; - } - } - - else if (p1.z < z && p0.z > z && p2.z > z) // 0_______2 - { // \ / - s = project2D(p1, p0, p2, z); //------------- z - end_edge_idx = 1; // \ / - } // 1 - - else if (p1.z > z && p0.z <= z && p2.z <= z) // 1 - { // / \ . - s = project2D(p1, p2, p0, z); //------------- z - end_edge_idx = 0; // / \ . - if (p0.z == z) // 0_______2 - { - s.endVertex = &v0; - } - } - - else if (p2.z < z && p1.z > z && p0.z > z) // 0_______1 - { // \ / - s = project2D(p2, p1, p0, z); //------------- z - end_edge_idx = 2; // \ / - } // 2 - - else if (p2.z > z && p1.z <= z && p0.z <= z) // 2 - { // / \ . - s = project2D(p2, p0, p1, z); //------------- z - end_edge_idx = 1; // / \ . - if (p1.z == z) // 0_______1 - { - s.endVertex = &v1; - } - } - else - { - // Not all cases create a segment, because a point of a face could create just a dot, and two touching faces - // on the slice would create two segments - continue; - } - - // store the segments per layer - layer.face_idx_to_segment_idx.insert(std::make_pair(mesh_idx, layer.segments.size())); - s.faceIndex = mesh_idx; - s.endOtherFaceIdx = face.connected_face_index[end_edge_idx]; - s.addedToPolygon = false; - layer.segments.push_back(s); - } - }); + cura::parallel_for( + layers, + [&](auto layer_it) + { + SlicerLayer& layer = *layer_it; + const int32_t& z = layer.z; + layer.segments.reserve(100); + + // loop over all mesh faces + for (unsigned int mesh_idx = 0; mesh_idx < mesh.faces.size(); mesh_idx++) + { + if ((z < zbbox[mesh_idx].first) || (z > zbbox[mesh_idx].second)) + { + continue; + } + + // get all vertices per face + const MeshFace& face = mesh.faces[mesh_idx]; + const MeshVertex& v0 = mesh.vertices[face.vertex_index[0]]; + const MeshVertex& v1 = mesh.vertices[face.vertex_index[1]]; + const MeshVertex& v2 = mesh.vertices[face.vertex_index[2]]; + + // get all vertices represented as 3D point + Point3 p0 = v0.p; + Point3 p1 = v1.p; + Point3 p2 = v2.p; + + // Compensate for points exactly on the slice-boundary, except for 'inclusive', which already handles this correctly. + if (slicing_tolerance != SlicingTolerance::INCLUSIVE) + { + p0.z += static_cast(p0.z == z) * -static_cast(p0.z < 1); + p1.z += static_cast(p1.z == z) * -static_cast(p1.z < 1); + p2.z += static_cast(p2.z == z) * -static_cast(p2.z < 1); + } + + SlicerSegment s; + s.endVertex = nullptr; + int end_edge_idx = -1; + + /* + Now see if the triangle intersects the layer, and if so, where. + + Edge cases are important here: + - If all three vertices of the triangle are exactly on the layer, + don't count the triangle at all, because if the model is + watertight, there will be adjacent triangles on all 3 sides that + are not flat on the layer. + - If two of the vertices are exactly on the layer, only count the + triangle if the last vertex is going up. We can't count both + upwards and downwards triangles here, because if the model is + manifold there will always be an adjacent triangle that is going + the other way and you'd get double edges. You would also get one + layer too many if the total model height is an exact multiple of + the layer thickness. Between going up and going down, we need to + choose the triangles going up, because otherwise the first layer + of where the model starts will be empty and the model will float + in mid-air. We'd much rather let the last layer be empty in that + case. + - If only one of the vertices is exactly on the layer, the + intersection between the triangle and the plane would be a point. + We can't print points and with a manifold model there would be + line segments adjacent to the point on both sides anyway, so we + need to discard this 0-length line segment then. + - Vertices in ccw order if look from outside. + */ + + if (p0.z < z && p1.z > z && p2.z > z) // 1_______2 + { // \ / + s = project2D(p0, p2, p1, z); //------------- z + end_edge_idx = 0; // \ / + } // 0 + + else if (p0.z > z && p1.z <= z && p2.z <= z) // 0 + { // / \ . + s = project2D(p0, p1, p2, z); //------------- z + end_edge_idx = 2; // / \ . + if (p2.z == z) // 1_______2 + { + s.endVertex = &v2; + } + } + + else if (p1.z < z && p0.z > z && p2.z > z) // 0_______2 + { // \ / + s = project2D(p1, p0, p2, z); //------------- z + end_edge_idx = 1; // \ / + } // 1 + + else if (p1.z > z && p0.z <= z && p2.z <= z) // 1 + { // / \ . + s = project2D(p1, p2, p0, z); //------------- z + end_edge_idx = 0; // / \ . + if (p0.z == z) // 0_______2 + { + s.endVertex = &v0; + } + } + + else if (p2.z < z && p1.z > z && p0.z > z) // 0_______1 + { // \ / + s = project2D(p2, p1, p0, z); //------------- z + end_edge_idx = 2; // \ / + } // 2 + + else if (p2.z > z && p1.z <= z && p0.z <= z) // 2 + { // / \ . + s = project2D(p2, p0, p1, z); //------------- z + end_edge_idx = 1; // / \ . + if (p1.z == z) // 0_______1 + { + s.endVertex = &v1; + } + } + else + { + // Not all cases create a segment, because a point of a face could create just a dot, and two touching faces + // on the slice would create two segments + continue; + } + + // store the segments per layer + layer.face_idx_to_segment_idx.insert(std::make_pair(mesh_idx, layer.segments.size())); + s.faceIndex = mesh_idx; + s.endOtherFaceIdx = face.connected_face_index[end_edge_idx]; + s.addedToPolygon = false; + layer.segments.push_back(s); + } + }); } -std::vector - Slicer::buildLayersWithHeight(size_t slice_layer_count, SlicingTolerance slicing_tolerance, coord_t initial_layer_thickness, coord_t thickness, bool use_variable_layer_heights, const std::vector* adaptive_layers) +std::vector Slicer::buildLayersWithHeight( + size_t slice_layer_count, + SlicingTolerance slicing_tolerance, + coord_t initial_layer_thickness, + coord_t thickness, + bool use_variable_layer_heights, + const std::vector* adaptive_layers) { std::vector layers_res; @@ -957,7 +999,7 @@ std::vector } // define all layer z positions (depending on slicing mode, see above) - for (unsigned int layer_nr = 1; layer_nr < slice_layer_count; layer_nr++) + for (LayerIndex layer_nr = 1; layer_nr < slice_layer_count; layer_nr++) { if (use_variable_layer_heights) { @@ -974,18 +1016,23 @@ std::vector void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::vector& layers) { - cura::parallel_for(layers, [&mesh](auto layer_it) { layer_it->makePolygons(&mesh); }); + cura::parallel_for( + layers, + [&mesh](auto layer_it) + { + layer_it->makePolygons(&mesh); + }); switch (slicing_tolerance) { case SlicingTolerance::INCLUSIVE: - for (unsigned int layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) { layers[layer_nr].polygons = layers[layer_nr].polygons.unionPolygons(layers[layer_nr + 1].polygons); } break; case SlicingTolerance::EXCLUSIVE: - for (unsigned int layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr + 1 < layers.size(); layer_nr++) { layers[layer_nr].polygons = layers[layer_nr].polygons.intersection(layers[layer_nr + 1].polygons); } @@ -998,8 +1045,8 @@ void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::v } size_t layer_apply_initial_xy_offset = 0; - if (layers.size() > 0 && layers[0].polygons.size() == 0 && ! mesh.settings.get("support_mesh") && ! mesh.settings.get("anti_overhang_mesh") && ! mesh.settings.get("cutting_mesh") - && ! mesh.settings.get("infill_mesh")) + if (layers.size() > 0 && layers[0].polygons.size() == 0 && ! mesh.settings.get("support_mesh") && ! mesh.settings.get("anti_overhang_mesh") + && ! mesh.settings.get("cutting_mesh") && ! mesh.settings.get("infill_mesh")) { layer_apply_initial_xy_offset = 1; } @@ -1012,8 +1059,7 @@ void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::v const auto max_hole_area = std::numbers::pi / 4 * static_cast(hole_offset_max_diameter * hole_offset_max_diameter); - cura::parallel_for - ( + cura::parallel_for( 0, layers.size(), [&layers, layer_apply_initial_xy_offset, xy_offset, xy_offset_0, xy_offset_hole, hole_offset_max_diameter, max_hole_area](size_t layer_nr) @@ -1062,8 +1108,7 @@ void Slicer::makePolygons(Mesh& mesh, SlicingTolerance slicing_tolerance, std::v layers[layer_nr].polygons.add(outline.difference(holes.unionPolygons())); } } - } - ); + }); mesh.expandXY(xy_offset); } diff --git a/src/support.cpp b/src/support.cpp index 2fc782c93c..6bae947fa2 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -1,6 +1,8 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include "support.h" + #include // sqrt, round #include #include // ifstream.good() @@ -31,7 +33,6 @@ #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "slicer.h" -#include "support.h" #include "utils/Simplify.h" #include "utils/ThreadPool.h" #include "utils/VoronoiUtils.h" @@ -53,8 +54,9 @@ bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Set SUPPORT_DROP_DOWN, SUPPORT_VANILLA }; - ModifierType modifier_type = (mesh_settings.get("anti_overhang_mesh")) ? ANTI_OVERHANG : ((mesh_settings.get("support_mesh_drop_down")) ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA); - for (unsigned int layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++) + ModifierType modifier_type + = (mesh_settings.get("anti_overhang_mesh")) ? ANTI_OVERHANG : ((mesh_settings.get("support_mesh_drop_down")) ? SUPPORT_DROP_DOWN : SUPPORT_VANILLA); + for (LayerIndex layer_nr = 0; layer_nr < slicer->layers.size(); layer_nr++) { SupportLayer& support_layer = storage.support.supportLayers[layer_nr]; const SlicerLayer& slicer_layer = slicer->layers[layer_nr]; @@ -75,7 +77,10 @@ bool AreaSupport::handleSupportModifierMesh(SliceDataStorage& storage, const Set } -void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage& storage, const std::vector& global_support_areas_per_layer, unsigned int total_layer_count) +void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts( + SliceDataStorage& storage, + const std::vector& global_support_areas_per_layer, + unsigned int total_layer_count) { if (total_layer_count == 0) { @@ -94,14 +99,13 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage const size_t wall_line_count = infill_extruder.settings.get("support_wall_count"); // Generate separate support islands - for (unsigned int layer_nr = 0; layer_nr < total_layer_count - 1; ++layer_nr) + for (LayerIndex layer_nr = 0; layer_nr < total_layer_count - 1; ++layer_nr) { unsigned int wall_line_count_this_layer = wall_line_count; if (layer_nr == 0 && (support_pattern == EFillMethod::LINES || support_pattern == EFillMethod::ZIG_ZAG)) { // The first layer will be printed with a grid pattern wall_line_count_this_layer++; } - assert(storage.support.supportLayers[layer_nr].support_infill_parts.empty() && "support infill part list is supposed to be uninitialized"); const Polygons& global_support_areas = global_support_areas_per_layer[layer_nr]; if (global_support_areas.size() == 0 || layer_nr < min_layer || layer_nr > max_layer) @@ -111,20 +115,14 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts(SliceDataStorage continue; } - std::vector support_islands = global_support_areas.splitIntoParts(); - for (const PolygonsPart& island_outline : support_islands) + coord_t support_line_width_here = support_line_width; + if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) { - coord_t support_line_width_here = support_line_width; - if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) - { - support_line_width_here *= infill_extruder.settings.get("initial_layer_line_width_factor"); - } - // We don't generate insets and infill area for the parts yet because later the skirt/brim and prime - // tower will remove themselves from the support, so the outlines of the parts can be changed. - SupportInfillPart support_infill_part(island_outline, support_line_width_here, wall_line_count_this_layer); - - storage.support.supportLayers[layer_nr].support_infill_parts.push_back(support_infill_part); + support_line_width_here *= infill_extruder.settings.get("initial_layer_line_width_factor"); } + // We don't generate insets and infill area for the parts yet because later the skirt/brim and prime + // tower will remove themselves from the support, so the outlines of the parts can be changed. + storage.support.supportLayers[layer_nr].fillInfillParts(layer_nr, global_support_areas_per_layer, support_line_width_here, wall_line_count_this_layer); } } @@ -192,18 +190,20 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage) const coord_t wall_width = infill_extruder.settings.get("support_line_width"); // no early-out for this function; it needs to initialize the [infill_area_per_combine_per_density] - float layer_skip_count = 8; // skip every so many layers as to ignore small gaps in the model making computation more easy - size_t gradual_support_step_layer_count = round_divide(gradual_support_step_height, mesh_group_settings.get("layer_height")); // The difference in layer count between consecutive density infill areas. + double layer_skip_count{ 8.0 }; // skip every so many layers as to ignore small gaps in the model making computation more easy + size_t gradual_support_step_layer_count + = round_divide(gradual_support_step_height, mesh_group_settings.get("layer_height")); // The difference in layer count between consecutive density infill areas. // make gradual_support_step_height divisable by layer_skip_count - const float n_skip_steps_per_gradual_step = std::max(1.0f, std::ceil(gradual_support_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_support_step_layer_count + const auto n_skip_steps_per_gradual_step + = std::max(1.0, std::ceil(gradual_support_step_layer_count / layer_skip_count)); // only decrease layer_skip_count to make it a divisor of gradual_support_step_layer_count layer_skip_count = gradual_support_step_layer_count / n_skip_steps_per_gradual_step; LayerIndex min_layer = 0; LayerIndex max_layer = total_layer_count - 1; // compute different density areas for each support island - for (LayerIndex layer_nr = 0; layer_nr < static_cast(total_layer_count) - 1; layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < total_layer_count - 1; layer_nr++) { if (layer_nr < min_layer || layer_nr > max_layer) { @@ -222,15 +222,23 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage) continue; } // NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method. - const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, support_infill_part.inset_count_to_generate, wall_width, 0, infill_extruder.settings, layer_nr, SectionType::SUPPORT); + const Polygons infill_area = Infill::generateWallToolPaths( + support_infill_part.wall_toolpaths, + original_area, + support_infill_part.inset_count_to_generate, + wall_width, + 0, + infill_extruder.settings, + layer_nr, + SectionType::SUPPORT); const AABB& this_part_boundary_box = support_infill_part.outline_boundary_box; // calculate density areas for this island Polygons less_dense_support = infill_area; // one step less dense with each density_step for (unsigned int density_step = 0; density_step < max_density_steps; ++density_step) { - LayerIndex min_layer = layer_nr + density_step * gradual_support_step_layer_count + LayerIndex(layer_skip_count); - LayerIndex max_layer = layer_nr + (density_step + 1) * gradual_support_step_layer_count; + LayerIndex min_layer{ layer_nr + density_step * gradual_support_step_layer_count + static_cast(layer_skip_count) }; + LayerIndex max_layer{ layer_nr + (density_step + 1) * gradual_support_step_layer_count }; for (float upper_layer_idx = min_layer; upper_layer_idx <= max_layer; upper_layer_idx += layer_skip_count) { @@ -311,7 +319,8 @@ void AreaSupport::combineSupportInfillLayers(SliceDataStorage& storage) const coord_t layer_height = mesh_group_settings.get("layer_height"); // How many support infill layers to combine to obtain the requested sparse thickness. const ExtruderTrain& infill_extruder = mesh_group_settings.get("support_infill_extruder_nr"); - const size_t combine_layers_amount = std::max(uint64_t(1), round_divide(infill_extruder.settings.get("support_infill_sparse_thickness"), std::max(layer_height, coord_t(1)))); + const size_t combine_layers_amount + = std::max(uint64_t(1), round_divide(infill_extruder.settings.get("support_infill_sparse_thickness"), std::max(layer_height, coord_t(1)))); if (combine_layers_amount <= 1) { return; @@ -373,7 +382,8 @@ void AreaSupport::combineSupportInfillLayers(SliceDataStorage& storage) } result.add(intersection); // add area to be thickened - infill_area_per_combine[combine_count_here - 1] = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here + infill_area_per_combine[combine_count_here - 1] + = infill_area_per_combine[combine_count_here - 1].difference(intersection); // remove thickened area from less thick layer here unsigned int max_lower_density_idx = density_idx; // Generally: remove only from *same density* areas on layer below @@ -388,10 +398,13 @@ void AreaSupport::combineSupportInfillLayers(SliceDataStorage& storage) // of the lower layer with the same or higher density index max_lower_density_idx = lower_layer_part.infill_area_per_combine_per_density.size() - 1; } - for (unsigned int lower_density_idx = density_idx; lower_density_idx <= max_lower_density_idx && lower_density_idx < lower_layer_part.infill_area_per_combine_per_density.size(); lower_density_idx++) + for (unsigned int lower_density_idx = density_idx; + lower_density_idx <= max_lower_density_idx && lower_density_idx < lower_layer_part.infill_area_per_combine_per_density.size(); + lower_density_idx++) { std::vector& lower_infill_area_per_combine = lower_layer_part.infill_area_per_combine_per_density[lower_density_idx]; - lower_infill_area_per_combine[0] = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (single thickness) layer + lower_infill_area_per_combine[0] + = lower_infill_area_per_combine[0].difference(intersection); // remove thickened area from lower (single thickness) layer } } @@ -405,7 +418,7 @@ void AreaSupport::combineSupportInfillLayers(SliceDataStorage& storage) void AreaSupport::cleanup(SliceDataStorage& storage) { const coord_t support_line_width = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_line_width"); - for (unsigned int layer_nr = 0; layer_nr < storage.support.supportLayers.size(); layer_nr++) + for (LayerIndex layer_nr = 0; layer_nr < storage.support.supportLayers.size(); layer_nr++) { SupportLayer& layer = storage.support.supportLayers[layer_nr]; for (unsigned int part_idx = 0; part_idx < layer.support_infill_parts.size(); part_idx++) @@ -497,7 +510,7 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp const std::vector is_extruder_used = storage.getExtrudersUsed(); for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice->scene.extruders.size(); extruder_nr++) { - if (! is_extruder_used[extruder_nr]) //Unused extruders and the primary adhesion extruder don't generate an extra skirt line. + if (! is_extruder_used[extruder_nr]) // Unused extruders and the primary adhesion extruder don't generate an extra skirt line. { continue; } @@ -517,13 +530,15 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp for (ExtruderTrain* skirt_brim_extruder_p : skirt_brim_extruders) { ExtruderTrain& skirt_brim_extruder = *skirt_brim_extruder_p; - adhesion_size = std::max(adhesion_size, coord_t( - skirt_brim_extruder.settings.get(adhesion_width_str) - + skirt_brim_extruder.settings.get("skirt_brim_line_width") - * (skirt_brim_extruder.settings.get(adhesion_line_count_str) - 1) // - 1 because the line is also included in extra_skirt_line_width - * skirt_brim_extruder.settings.get("initial_layer_line_width_factor") - + extra_skirt_line_width)); - } + adhesion_size = std::max( + adhesion_size, + coord_t( + skirt_brim_extruder.settings.get(adhesion_width_str) + + skirt_brim_extruder.settings.get("skirt_brim_line_width") + * (skirt_brim_extruder.settings.get(adhesion_line_count_str) - 1) // - 1 because the line is also included in extra_skirt_line_width + * skirt_brim_extruder.settings.get("initial_layer_line_width_factor") + + extra_skirt_line_width)); + } break; case EPlatformAdhesion::RAFT: { @@ -563,11 +578,10 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp const coord_t min_even_wall_line_width = infill_settings.get("min_even_wall_line_width"); auto half_min_feature_width = min_even_wall_line_width + 10; - joined = joined - .offset(-half_min_feature_width) - .offset(join_distance + half_min_feature_width, ClipperLib::jtRound) - .offset(-join_distance, ClipperLib::jtRound) - .unionPolygons(joined); + joined = joined.offset(-half_min_feature_width) + .offset(join_distance + half_min_feature_width, ClipperLib::jtRound) + .offset(-join_distance, ClipperLib::jtRound) + .unionPolygons(joined); } const Simplify simplify(infill_settings); @@ -578,8 +592,9 @@ Polygons AreaSupport::join(const SliceDataStorage& storage, const Polygons& supp void AreaSupport::generateOverhangAreas(SliceDataStorage& storage) { - for (SliceMeshStorage& mesh : storage.meshes) + for (std::shared_ptr& mesh_ptr : storage.meshes) { + auto& mesh = *mesh_ptr; if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) { continue; @@ -624,17 +639,18 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) { continue; } - Settings* infill_settings = &storage.meshes[mesh_idx].settings; - Settings* roof_settings = &storage.meshes[mesh_idx].settings; - Settings* bottom_settings = &storage.meshes[mesh_idx].settings; + Settings* infill_settings = &storage.meshes[mesh_idx]->settings; + Settings* roof_settings = &storage.meshes[mesh_idx]->settings; + Settings* bottom_settings = &storage.meshes[mesh_idx]->settings; if (mesh.settings.get("support_mesh")) { - if ((mesh.settings.get("support_mesh_drop_down") && support_meshes_drop_down_handled) || (! mesh.settings.get("support_mesh_drop_down") && support_meshes_handled)) + if ((mesh.settings.get("support_mesh_drop_down") && support_meshes_drop_down_handled) + || (! mesh.settings.get("support_mesh_drop_down") && support_meshes_handled)) { // handle all support_mesh and support_mesh_drop_down areas only once continue; } @@ -663,28 +679,26 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) } } - for (unsigned int layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++) + for (Polygons& support_areas : global_support_areas_per_layer) { - Polygons& support_areas = global_support_areas_per_layer[layer_idx]; support_areas = support_areas.unionPolygons(); } // handle support interface - for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) + for (auto& mesh : storage.meshes) { - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (mesh->settings.get("infill_mesh") || mesh->settings.get("anti_overhang_mesh")) { continue; } - if (mesh.settings.get("support_roof_enable")) + if (mesh->settings.get("support_roof_enable")) { - generateSupportRoof(storage, mesh, global_support_areas_per_layer); + generateSupportRoof(storage, *mesh, global_support_areas_per_layer); } - if (mesh.settings.get("support_bottom_enable")) + if (mesh->settings.get("support_bottom_enable")) { - generateSupportBottom(storage, mesh, global_support_areas_per_layer); + generateSupportBottom(storage, *mesh, global_support_areas_per_layer); } } @@ -703,12 +717,12 @@ void AreaSupport::precomputeCrossInfillTree(SliceDataStorage& storage) AABB3D aabb; for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) { - const SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + const SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) { continue; } - Settings& infill_settings = storage.meshes[mesh_idx].settings; + Settings& infill_settings = storage.meshes[mesh_idx]->settings; if (mesh.settings.get("support_mesh")) { // use extruder train settings rather than the per-object settings of the first support mesh encountered. @@ -727,8 +741,11 @@ void AreaSupport::precomputeCrossInfillTree(SliceDataStorage& storage) std::ifstream cross_fs(cross_subdisivion_spec_image_file.c_str()); if (cross_subdisivion_spec_image_file != "" && cross_fs.good()) { - storage.support.cross_fill_provider = - new SierpinskiFillProvider(aabb, infill_extruder.settings.get("support_line_distance"), infill_extruder.settings.get("support_line_width"), cross_subdisivion_spec_image_file); + storage.support.cross_fill_provider = std::make_shared( + aabb, + infill_extruder.settings.get("support_line_distance"), + infill_extruder.settings.get("support_line_width"), + cross_subdisivion_spec_image_file); } else { @@ -736,7 +753,10 @@ void AreaSupport::precomputeCrossInfillTree(SliceDataStorage& storage) { spdlog::error("Cannot find density image: {}.", cross_subdisivion_spec_image_file); } - storage.support.cross_fill_provider = new SierpinskiFillProvider(aabb, infill_extruder.settings.get("support_line_distance"), infill_extruder.settings.get("support_line_width")); + storage.support.cross_fill_provider = std::make_shared( + aabb, + infill_extruder.settings.get("support_line_distance"), + infill_extruder.settings.get("support_line_width")); } } } @@ -765,7 +785,7 @@ void AreaSupport::generateOverhangAreasForMesh(SliceDataStorage& storage, SliceM // Don't generate overhang areas if the Z distance is higher than the objects we're generating support for. const coord_t layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height"); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; // Support must always be 1 layer below overhang. + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; if (z_distance_top_layers + 1 > storage.print_layer_count) { return; @@ -780,22 +800,23 @@ void AreaSupport::generateOverhangAreasForMesh(SliceDataStorage& storage, SliceM } // Generate the actual areas and store them in the mesh. - cura::parallel_for(1, - storage.print_layer_count, - [&](const size_t layer_idx) - { - std::pair basic_and_full_overhang = computeBasicAndFullOverhang(storage, mesh, layer_idx); - mesh.overhang_areas[layer_idx] = basic_and_full_overhang.first; // Store the results. - mesh.full_overhang_areas[layer_idx] = basic_and_full_overhang.second; - scripta::log("support_basic_overhang_area", basic_and_full_overhang.first, SectionType::SUPPORT, layer_idx); - scripta::log("support_full_overhang_area", basic_and_full_overhang.second, SectionType::SUPPORT, layer_idx); - }); + cura::parallel_for( + 1, + storage.print_layer_count, + [&](const size_t layer_idx) + { + std::pair basic_and_full_overhang = computeBasicAndFullOverhang(storage, mesh, layer_idx); + mesh.overhang_areas[layer_idx] = basic_and_full_overhang.first; // Store the results. + mesh.full_overhang_areas[layer_idx] = basic_and_full_overhang.second; + scripta::log("support_basic_overhang_area", basic_and_full_overhang.first, SectionType::SUPPORT, layer_idx); + scripta::log("support_full_overhang_area", basic_and_full_overhang.second, SectionType::SUPPORT, layer_idx); + }); } Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& storage, const Settings& infill_settings, const LayerIndex layer_idx) { const auto& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const Simplify simplify { mesh_group_settings }; + const Simplify simplify{ mesh_group_settings }; const auto layer_thickness = mesh_group_settings.get("layer_height"); const auto support_distance_top = static_cast(mesh_group_settings.get("support_top_distance")); const auto support_distance_bot = static_cast(mesh_group_settings.get("support_bottom_distance")); @@ -807,9 +828,7 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st constexpr coord_t close_dist = snap_radius + 5; // needs to be larger than the snap radius! constexpr coord_t search_radius = 0; - auto layer_current = simplify.polygon(storage.layers[layer_idx].getOutlines() - .offset(-close_dist) - .offset(close_dist)); + auto layer_current = simplify.polygon(storage.layers[layer_idx].getOutlines().offset(-close_dist).offset(close_dist)); // sparse grid for storing the offset distances at each point. For each point there can be multiple offset // values as multiple may be calculated when multiple layers are used for z-smoothing of the offsets. @@ -818,7 +837,7 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st // equal to commutative_offset / n. using point_pair_t = std::pair; using grid_t = SparsePointGridInclusive; - grid_t offset_dist_at_point { snap_radius }; + grid_t offset_dist_at_point{ snap_radius }; // Collection of the various areas we used to calculate the areas for. This is a combination // - the support distance (this is the support top distance for overhang areas, and support @@ -829,46 +848,26 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st // here either the slope or overhang area is stored std::vector> z_distances_layer_deltas; - constexpr LayerIndex layer_index_offset { 1 }; + constexpr LayerIndex layer_index_offset{ 1 }; - const LayerIndex layer_idx_below { std::max(layer_idx - layer_index_offset, LayerIndex { 0 }) }; + const LayerIndex layer_idx_below{ std::max(LayerIndex{ layer_idx - layer_index_offset }, LayerIndex{ 0 }) }; if (layer_idx_below != layer_idx) { - auto layer_below = simplify.polygon(storage.layers[layer_idx_below].getOutlines() - .offset(-close_dist) - .offset(close_dist)); - - z_distances_layer_deltas.emplace_back( - support_distance_top, - static_cast(layer_index_offset * layer_thickness), - layer_current.difference(layer_below) - ); - - z_distances_layer_deltas.emplace_back( - support_distance_bot, - static_cast(layer_index_offset * layer_thickness), - layer_below.difference(layer_current) - ); + auto layer_below = simplify.polygon(storage.layers[layer_idx_below].getOutlines().offset(-close_dist).offset(close_dist)); + + z_distances_layer_deltas.emplace_back(support_distance_top, static_cast(layer_index_offset * layer_thickness), layer_current.difference(layer_below)); + + z_distances_layer_deltas.emplace_back(support_distance_bot, static_cast(layer_index_offset * layer_thickness), layer_below.difference(layer_current)); } - const LayerIndex layer_idx_above { std::min(layer_idx + layer_index_offset, LayerIndex(static_cast(storage.layers.size()) - 1)) }; + const LayerIndex layer_idx_above{ std::min(LayerIndex{ layer_idx + layer_index_offset }, LayerIndex{ storage.layers.size() - 1 }) }; if (layer_idx_above != layer_idx) { - auto layer_above = simplify.polygon(storage.layers[layer_idx_below].getOutlines() - .offset(-close_dist) - .offset(close_dist)); - - z_distances_layer_deltas.emplace_back( - support_distance_bot, - static_cast(layer_index_offset * layer_thickness), - layer_current.difference(layer_above) - ); - - z_distances_layer_deltas.emplace_back( - support_distance_top, - static_cast(layer_index_offset * layer_thickness), - layer_above.difference(layer_current) - ); + auto layer_above = simplify.polygon(storage.layers[layer_idx_below].getOutlines().offset(-close_dist).offset(close_dist)); + + z_distances_layer_deltas.emplace_back(support_distance_bot, static_cast(layer_index_offset * layer_thickness), layer_current.difference(layer_above)); + + z_distances_layer_deltas.emplace_back(support_distance_top, static_cast(layer_index_offset * layer_thickness), layer_above.difference(layer_current)); } for (auto& [support_distance, delta_z, layer_delta_] : z_distances_layer_deltas) @@ -885,14 +884,14 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st } // grid for storing the "slope" (wall overhang area at that specific point in the polygon) - grid_t slope_at_point { snap_radius }; + grid_t slope_at_point{ snap_radius }; // construct a voronoi diagram. The slope is calculated based // on the edge length from the boundary to the center edge(s) std::vector segments; - for (auto [poly_idx, poly]: layer_delta | ranges::views::enumerate) + for (auto [poly_idx, poly] : layer_delta | ranges::views::enumerate) { - for (auto [point_idx, _p]: poly | ranges::views::enumerate) + for (auto [point_idx, _p] : poly | ranges::views::enumerate) { segments.emplace_back(&layer_delta, poly_idx, point_idx); } @@ -901,7 +900,7 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st boost::polygon::voronoi_diagram vonoroi_diagram; boost::polygon::construct_voronoi(segments.begin(), segments.end(), &vonoroi_diagram); - for (const auto& edge: vonoroi_diagram.edges()) + for (const auto& edge : vonoroi_diagram.edges()) { if (edge.is_infinite()) { @@ -931,8 +930,8 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st auto slope = dist_to_boundary / delta_z; auto nearby_vals = slope_at_point.getNearbyVals(p0, search_radius); - auto n = ranges::accumulate(nearby_vals | views::get( &point_pair_t::first ), 0); - auto cumulative_slope = ranges::accumulate(nearby_vals | views::get( &point_pair_t::second ), 0.); + auto n = ranges::accumulate(nearby_vals | views::get(&point_pair_t::first), 0); + auto cumulative_slope = ranges::accumulate(nearby_vals | views::get(&point_pair_t::second), 0.); n += 1; cumulative_slope += slope; @@ -941,13 +940,13 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st slope_at_point.insert(p0, { n, cumulative_slope }); } - for (const auto& poly: layer_current) + for (const auto& poly : layer_current) { - for (const auto& point: poly) + for (const auto& point : poly) { auto nearby_vals = slope_at_point.getNearbyVals(point, search_radius); - auto n = ranges::accumulate(nearby_vals | views::get( &point_pair_t::first ), 0); - auto cumulative_slope = ranges::accumulate(nearby_vals | views::get( &point_pair_t::second ), 0.); + auto n = ranges::accumulate(nearby_vals | views::get(&point_pair_t::first), 0); + auto cumulative_slope = ranges::accumulate(nearby_vals | views::get(&point_pair_t::second), 0.); if (n != 0) { @@ -960,26 +959,26 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st auto nearby_vals_offset_dist = offset_dist_at_point.getNearbyVals(point, search_radius); // update and insert cumulative varying xy distance in one go - offset_dist_at_point.insert(point, { - ranges::accumulate(nearby_vals_offset_dist | views::get( &point_pair_t::first ), 0) + 1, - ranges::accumulate(nearby_vals_offset_dist | views::get( &point_pair_t::second ), 0.) + xy_distance_varying - }); + offset_dist_at_point.insert( + point, + { ranges::accumulate(nearby_vals_offset_dist | views::get(&point_pair_t::first), 0) + 1, + ranges::accumulate(nearby_vals_offset_dist | views::get(&point_pair_t::second), 0.) + xy_distance_varying }); } } } } std::vector varying_offsets; - for (const auto& poly: layer_current) + for (const auto& poly : layer_current) { for (const auto& point : poly) { auto nearby_vals = offset_dist_at_point.getNearbyVals(point, search_radius); - auto n = ranges::accumulate(nearby_vals | views::get( &point_pair_t::first ), 0); - auto cumulative_offset_dist = ranges::accumulate(nearby_vals | views::get( &point_pair_t::second ), 0.); + auto n = ranges::accumulate(nearby_vals | views::get(&point_pair_t::first), 0); + auto cumulative_offset_dist = ranges::accumulate(nearby_vals | views::get(&point_pair_t::second), 0.); - double offset_dist {}; + double offset_dist{}; if (n == 0) { // if there are no offset dists generated for a vertex $p$ this must mean that vertex $p$ was not @@ -1005,7 +1004,8 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st // close operation to smooth the x/y disallowed area boundary. With varying xy distances we see some jumps in the boundary. // As the x/y disallowed areas "cut in" to support the xy-disallowed area may propagate through the support area. If the // x/y disallowed area is not smoothed boost has trouble generating a voronoi diagram. - .offset(smooth_dist).offset(-smooth_dist); + .offset(smooth_dist) + .offset(-smooth_dist); scripta::log("support_varying_xy_disallowed_areas", varying_xy_disallowed_areas, SectionType::SUPPORT, layer_idx); return varying_xy_disallowed_areas; } @@ -1016,23 +1016,26 @@ Polygons AreaSupport::generateVaryingXYDisallowedArea(const SliceMeshStorage& st * - find overhang by looking at the difference between two consecutive layers * - join with support areas from layer above * - subtract current layer - * - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more stability) + * - use the result for the next lower support layer (without doing XY-distance and Z bottom distance, so that a single support beam may move around the model a bit => more + * stability) * - perform inset using X/Y-distance and bottom Z distance * * for support buildplate only: purge all support not connected to build plate */ -void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, - const Settings& infill_settings, - const Settings& roof_settings, - const Settings& bottom_settings, - const size_t mesh_idx, - const size_t layer_count, - std::vector& support_areas) +void AreaSupport::generateSupportAreasForMesh( + SliceDataStorage& storage, + const Settings& infill_settings, + const Settings& roof_settings, + const Settings& bottom_settings, + const size_t mesh_idx, + const size_t layer_count, + std::vector& support_areas) { - SliceMeshStorage& mesh = storage.meshes[mesh_idx]; + SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; const ESupportStructure support_structure = mesh.settings.get("support_structure"); - const bool is_support_mesh_place_holder = mesh.settings.get("support_mesh"); // whether this mesh has empty SliceMeshStorage and this function is now called to only generate support for all support meshes + const bool is_support_mesh_place_holder + = mesh.settings.get("support_mesh"); // whether this mesh has empty SliceMeshStorage and this function is now called to only generate support for all support meshes if ((! mesh.settings.get("support_enable") || support_structure != ESupportStructure::NORMAL) && ! is_support_mesh_place_holder) { return; @@ -1047,7 +1050,7 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, // early out const coord_t layer_thickness = mesh_group_settings.get("layer_height"); const coord_t z_distance_top = ((mesh.settings.get("support_roof_enable")) ? roof_settings : infill_settings).get("support_top_distance"); - const size_t layer_z_distance_top = round_up_divide(z_distance_top, layer_thickness) + 1; // support must always be 1 layer below overhang + const size_t layer_z_distance_top = (z_distance_top / layer_thickness) + 1; if (layer_z_distance_top + 1 > layer_count) { return; @@ -1062,7 +1065,8 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, // simplified processing for bottom layer - just ensure support clears part by XY distance const coord_t xy_distance = infill_settings.get("support_xy_distance"); const coord_t xy_distance_overhang = infill_settings.get("support_xy_distance_overhang"); - const bool use_xy_distance_overhang = infill_settings.get("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs + const bool use_xy_distance_overhang + = infill_settings.get("support_xy_overrides_z") == SupportDistPriority::Z_OVERRIDES_XY; // whether to use a different xy distance at overhangs const AngleRadians angle = ((mesh.settings.get("support_roof_enable")) ? roof_settings : infill_settings).get("support_angle"); const double tan_angle = tan(angle) - 0.01; // the XY-component of the supportAngle constexpr bool no_support = false; @@ -1077,48 +1081,53 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, // The maximum width of an odd wall = 2 * minimum even wall width. auto half_min_feature_width = min_even_wall_line_width + 10; - cura::parallel_for(1, - layer_count, - [&](const size_t layer_idx) - { - const Polygons outlines = storage.getLayerOutlines(layer_idx, no_support, no_prime_tower); - - // Build sloped areas. We need this for the stair-stepping later on. - // Specifically, sloped areass are used in 'moveUpFromModel' to prevent a stair step happening over an area where there isn't a slope. - // This part here only concerns the slope between two layers. This will be post-processed later on (see the other parallel loop below). - sloped_areas_per_layer[layer_idx] = - // Take the outer areas of the previous layer, where the outer areas are (mostly) just _inside_ the shape. - storage.getLayerOutlines(layer_idx - 1, no_support, no_prime_tower) - .tubeShape(sloped_area_detection_width, 10) - // Intersect those with the outer areas of the current layer, where the outer areas are (mostly) _outside_ the shape. - // This will detect every slope (and some/most vertical walls) between those two layers. - .intersection(outlines.tubeShape(10, sloped_area_detection_width)) - // Do an opening operation so we're not stuck with tiny patches. - // The later offset is extended with the line-width, so all patches are merged together if there's less than a line-width between them. - .offset(-10) - .offset(10 + sloped_area_detection_width); - // The sloped areas are now ready to be post-processed. - scripta::log("support_sloped_areas", sloped_areas_per_layer[layer_idx], SectionType::SUPPORT, layer_idx, - scripta::CellVDI{"sloped_area_detection_width", sloped_area_detection_width }); - - if (! is_support_mesh_place_holder) - { // don't compute overhang for support meshes - if (use_xy_distance_overhang) // Z overrides XY distance. - { - // we also want to use the min XY distance when the support is resting on a sloped surface so we calculate the area of the - // layer below that protrudes beyond the current layer's area and combine it with the current layer's overhang disallowed area - - Polygons minimum_xy_disallowed_areas = xy_disallowed_per_layer[layer_idx].offset(xy_distance_overhang); - Polygons varying_xy_disallowed_areas = generateVaryingXYDisallowedArea(mesh, infill_settings, layer_idx); - xy_disallowed_per_layer[layer_idx] = minimum_xy_disallowed_areas.unionPolygons(varying_xy_disallowed_areas); - scripta::log("support_xy_disallowed_areas", xy_disallowed_per_layer[layer_idx], SectionType::SUPPORT, layer_idx); - } - } - if (is_support_mesh_place_holder || ! use_xy_distance_overhang) - { - xy_disallowed_per_layer[layer_idx] = outlines.offset(xy_distance); - } - }); + cura::parallel_for( + 1, + layer_count, + [&](const size_t layer_idx) + { + const Polygons outlines = storage.getLayerOutlines(layer_idx, no_support, no_prime_tower); + + // Build sloped areas. We need this for the stair-stepping later on. + // Specifically, sloped areass are used in 'moveUpFromModel' to prevent a stair step happening over an area where there isn't a slope. + // This part here only concerns the slope between two layers. This will be post-processed later on (see the other parallel loop below). + sloped_areas_per_layer[layer_idx] = + // Take the outer areas of the previous layer, where the outer areas are (mostly) just _inside_ the shape. + storage.getLayerOutlines(layer_idx - 1, no_support, no_prime_tower) + .tubeShape(sloped_area_detection_width, 10) + // Intersect those with the outer areas of the current layer, where the outer areas are (mostly) _outside_ the shape. + // This will detect every slope (and some/most vertical walls) between those two layers. + .intersection(outlines.tubeShape(10, sloped_area_detection_width)) + // Do an opening operation so we're not stuck with tiny patches. + // The later offset is extended with the line-width, so all patches are merged together if there's less than a line-width between them. + .offset(-10) + .offset(10 + sloped_area_detection_width); + // The sloped areas are now ready to be post-processed. + scripta::log( + "support_sloped_areas", + sloped_areas_per_layer[layer_idx], + SectionType::SUPPORT, + layer_idx, + scripta::CellVDI{ "sloped_area_detection_width", sloped_area_detection_width }); + + if (! is_support_mesh_place_holder) + { // don't compute overhang for support meshes + if (use_xy_distance_overhang) // Z overrides XY distance. + { + // we also want to use the min XY distance when the support is resting on a sloped surface so we calculate the area of the + // layer below that protrudes beyond the current layer's area and combine it with the current layer's overhang disallowed area + + Polygons minimum_xy_disallowed_areas = xy_disallowed_per_layer[layer_idx].offset(xy_distance_overhang); + Polygons varying_xy_disallowed_areas = generateVaryingXYDisallowedArea(mesh, infill_settings, layer_idx); + xy_disallowed_per_layer[layer_idx] = minimum_xy_disallowed_areas.unionPolygons(varying_xy_disallowed_areas); + scripta::log("support_xy_disallowed_areas", xy_disallowed_per_layer[layer_idx], SectionType::SUPPORT, layer_idx); + } + } + if (is_support_mesh_place_holder || ! use_xy_distance_overhang) + { + xy_disallowed_per_layer[layer_idx] = outlines.offset(xy_distance); + } + }); std::vector tower_roofs; Polygons stair_removal; // polygons to subtract from support because of stair-stepping @@ -1155,7 +1164,8 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, const coord_t z_distance_bottom = ((mesh.settings.get("support_bottom_enable")) ? bottom_settings : infill_settings).get("support_bottom_distance"); const size_t bottom_empty_layer_count = round_up_divide(z_distance_bottom, layer_thickness); // number of empty layers between support and model const coord_t bottom_stair_step_height = std::max(static_cast(0), mesh.settings.get("support_bottom_stair_step_height")); - const size_t bottom_stair_step_layer_count = bottom_stair_step_height / layer_thickness + 1; // the difference in layers between two stair steps. One is normal support (not stair-like) + const size_t bottom_stair_step_layer_count + = bottom_stair_step_height / layer_thickness + 1; // the difference in layers between two stair steps. One is normal support (not stair-like) // Post-process the sloped areas's. (Skip if no stair-stepping anyway.) // The idea here is to 'add up' all the sloped 'areas' so they form actual areas per each stair-step height. @@ -1219,7 +1229,8 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, { // join with support from layer up const Polygons empty; const Polygons* layer_above = (layer_idx < support_areas.size()) ? &support_areas[layer_idx + 1] : ∅ - const Polygons model_mesh_on_layer = (layer_idx > 0) && ! is_support_mesh_nondrop_place_holder ? storage.getLayerOutlines(layer_idx, no_support, no_prime_tower) : empty; + const Polygons model_mesh_on_layer + = (layer_idx > 0) && ! is_support_mesh_nondrop_place_holder ? storage.getLayerOutlines(layer_idx, no_support, no_prime_tower) : empty; if (is_support_mesh_nondrop_place_holder) { layer_above = ∅ @@ -1233,9 +1244,7 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, { for (PolygonsPart poly : layer_this.splitIntoParts()) { - const auto polygon_part = poly.difference(xy_disallowed_per_layer[layer_idx]) - .offset(-half_min_feature_width) - .offset(half_min_feature_width); + const auto polygon_part = poly.difference(xy_disallowed_per_layer[layer_idx]).offset(-half_min_feature_width).offset(half_min_feature_width); const int64_t part_area = polygon_part.area(); if (part_area == 0 || part_area > max_tower_supported_diameter * max_tower_supported_diameter) @@ -1259,7 +1268,15 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, } // Move up from model, handle stair-stepping. - moveUpFromModel(storage, stair_removal, sloped_areas_per_layer[layer_idx], layer_this, layer_idx, bottom_empty_layer_count, bottom_stair_step_layer_count, bottom_stair_step_width); + moveUpFromModel( + storage, + stair_removal, + sloped_areas_per_layer[layer_idx], + layer_this, + layer_idx, + bottom_empty_layer_count, + bottom_stair_step_layer_count, + bottom_stair_step_width); support_areas[layer_idx] = layer_this; Progress::messageProgress(Progress::Stage::SUPPORT, layer_count * (mesh_idx + 1) - layer_idx, layer_count * storage.meshes.size()); @@ -1300,7 +1317,7 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, conical_support_offset = (tan(-conical_support_angle) - 0.01) * layer_thickness; } const bool conical_support = infill_settings.get("support_conical_enabled") && conical_support_angle != 0; - for (unsigned int layer_idx = 1; layer_idx < storage.support.supportLayers.size(); layer_idx++) + for (LayerIndex layer_idx = 1; layer_idx < storage.support.supportLayers.size(); layer_idx++) { const Polygons& layer = support_areas[layer_idx]; @@ -1335,24 +1352,26 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, // this is performed after the main support generation loop above, because it affects the joining of polygons // if this would be performed in the main loop then some support would not have been generated under the overhangs and consequently no support is generated for that, // meaning almost no support would be generated in some cases which definitely need support. - const int max_checking_layer_idx = std::max(0, std::min(static_cast(storage.support.supportLayers.size()), static_cast(layer_count - (layer_z_distance_top - 1)))); - - cura::parallel_for(0, - max_checking_layer_idx, - [&](const size_t layer_idx) - { - constexpr bool no_support = false; - constexpr bool no_prime_tower = false; - support_areas[layer_idx] = support_areas[layer_idx].difference(storage.getLayerOutlines(layer_idx + layer_z_distance_top - 1, no_support, no_prime_tower)); - }); + const int max_checking_layer_idx + = std::max(0, std::min(static_cast(storage.support.supportLayers.size()), static_cast(layer_count - (layer_z_distance_top - 1)))); + + cura::parallel_for( + 0, + max_checking_layer_idx, + [&](const size_t layer_idx) + { + constexpr bool no_support = false; + constexpr bool no_prime_tower = false; + support_areas[layer_idx] = support_areas[layer_idx].difference(storage.getLayerOutlines(layer_idx + layer_z_distance_top - 1, no_support, no_prime_tower)); + }); } // Procedure to remove floating support - for (size_t layer_idx = 1; layer_idx < layer_count - 1; layer_idx ++) + for (size_t layer_idx = 1; layer_idx < layer_count - 1; layer_idx++) { Polygons& layer_this = support_areas[layer_idx]; - if (!layer_this.empty()) + if (! layer_this.empty()) { Polygons& layer_below = support_areas[layer_idx - 1]; Polygons& layer_above = support_areas[layer_idx + 1]; @@ -1373,14 +1392,15 @@ void AreaSupport::generateSupportAreasForMesh(SliceDataStorage& storage, storage.support.generated = true; } -void AreaSupport::moveUpFromModel(const SliceDataStorage& storage, - Polygons& stair_removal, - Polygons& sloped_areas, - Polygons& support_areas, - const size_t layer_idx, - const size_t bottom_empty_layer_count, - const size_t bottom_stair_step_layer_count, - const coord_t support_bottom_stair_step_width) +void AreaSupport::moveUpFromModel( + const SliceDataStorage& storage, + Polygons& stair_removal, + Polygons& sloped_areas, + Polygons& support_areas, + const size_t layer_idx, + const size_t bottom_empty_layer_count, + const size_t bottom_stair_step_layer_count, + const coord_t support_bottom_stair_step_width) { // The idea behind support bottom stairs: // @@ -1416,7 +1436,8 @@ void AreaSupport::moveUpFromModel(const SliceDataStorage& storage, // support // ############################################################################ ┐ // AAAAAxxxxxxxxxxxxx │ - // CCCCCCCCCCCCCCCCCC########################################################## │ >>>>>>>>> result only applies stair step to first layer(s) of what woud normally be the stair step + // CCCCCCCCCCCCCCCCCC########################################################## │ >>>>>>>>> result only applies stair step to first layer(s) of what woud + // normally be the stair step // ^^--..__ │ // ^^--..__####################################################### ├> stair step height // ⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ ^^--..__ │ @@ -1480,14 +1501,14 @@ void AreaSupport::moveUpFromModel(const SliceDataStorage& storage, * ^^^^^^^^^ overhang extensions * ^^^^^^^^^^^^^^ overhang */ -std::pair AreaSupport::computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const unsigned int layer_idx) +std::pair AreaSupport::computeBasicAndFullOverhang(const SliceDataStorage& storage, const SliceMeshStorage& mesh, const LayerIndex& layer_idx) { const Polygons outlines = mesh.layers[layer_idx].getOutlines(); constexpr bool no_support = false; constexpr bool no_prime_tower = false; - constexpr double smooth_height = 0.4; //mm - const auto layers_below = static_cast(std::round(smooth_height / mesh.settings.get("layer_height"))); + constexpr double smooth_height = 0.4; // mm + const LayerIndex layers_below{ static_cast(std::round(smooth_height / mesh.settings.get("layer_height"))) }; const coord_t layer_height = mesh.settings.get("layer_height"); const AngleRadians support_angle = mesh.settings.get("support_angle"); @@ -1498,23 +1519,17 @@ std::pair AreaSupport::computeBasicAndFullOverhang(const Sli // To avoids generating support for textures on vertical surfaces, a moving average // is taken over smooth_height. The smooth_height is currently an educated guess // that we might want to expose to the frontend in the future. - Polygons outlines_below = - storage.getLayerOutlines(layer_idx - 1, no_support, no_prime_tower) - .offset(max_dist_from_lower_layer); - for (int layer_idx_offset = 2; layer_idx - layer_idx_offset >= 0 && layer_idx_offset <= layers_below; layer_idx_offset ++) + Polygons outlines_below = storage.getLayerOutlines(layer_idx - 1, no_support, no_prime_tower).offset(max_dist_from_lower_layer); + for (int layer_idx_offset = 2; layer_idx - layer_idx_offset >= 0 && layer_idx_offset <= layers_below; layer_idx_offset++) { - auto outlines_below_ = - storage.getLayerOutlines(layer_idx - layer_idx_offset, no_support, no_prime_tower) - .offset(max_dist_from_lower_layer * layer_idx_offset); + auto outlines_below_ = storage.getLayerOutlines(layer_idx - layer_idx_offset, no_support, no_prime_tower).offset(max_dist_from_lower_layer * layer_idx_offset); outlines_below = outlines_below.unionPolygons(outlines_below_); } - Polygons basic_overhang = - outlines - .difference(outlines_below); + Polygons basic_overhang = outlines.difference(outlines_below); const SupportLayer& support_layer = storage.support.supportLayers[layer_idx]; - if (!support_layer.anti_overhang.empty()) + if (! support_layer.anti_overhang.empty()) { // Merge anti overhang into one polygon, otherwise overlapping polygons // will create opposite effect. @@ -1523,10 +1538,9 @@ std::pair AreaSupport::computeBasicAndFullOverhang(const Sli basic_overhang = basic_overhang.difference(merged_polygons); } - Polygons overhang_extended = - basic_overhang - // +0.1mm for easier joining with support from layer above - .offset(max_dist_from_lower_layer * layers_below + MM2INT(0.1)); + Polygons overhang_extended = basic_overhang + // +0.1mm for easier joining with support from layer above + .offset(max_dist_from_lower_layer * layers_below + MM2INT(0.1)); Polygons full_overhang = overhang_extended.intersection(outlines); return std::make_pair(basic_overhang, full_overhang); @@ -1570,7 +1584,14 @@ void AreaSupport::detectOverhangPoints(const SliceDataStorage& storage, SliceMes } } -void AreaSupport::handleTowers(const Settings& settings, const Polygons& xy_disallowed_area, Polygons& supportLayer_this, std::vector& tower_roofs, std::vector>& overhang_points, LayerIndex layer_idx, size_t layer_count) +void AreaSupport::handleTowers( + const Settings& settings, + const Polygons& xy_disallowed_area, + Polygons& supportLayer_this, + std::vector& tower_roofs, + std::vector>& overhang_points, + LayerIndex layer_idx, + size_t layer_count) { LayerIndex layer_overhang_point = layer_idx + 1; // Start tower 1 layer below overhang point. if (layer_overhang_point >= static_cast(layer_count) - 1) @@ -1621,7 +1642,12 @@ void AreaSupport::handleTowers(const Settings& settings, const Polygons& xy_disa tower_roof_expansion_distance = layer_thickness / tan_tower_roof_angle; } - for (Polygons& tower_roof: tower_roofs | ranges::views::filter([](const auto& poly){ return ! poly.empty(); })) + for (Polygons& tower_roof : tower_roofs + | ranges::views::filter( + [](const auto& poly) + { + return ! poly.empty(); + })) { supportLayer_this = supportLayer_this.unionPolygons(tower_roof); @@ -1721,16 +1747,19 @@ void AreaSupport::generateSupportBottom(SliceDataStorage& storage, const SliceMe return; } const coord_t z_distance_bottom = round_up_divide(mesh.settings.get("support_bottom_distance"), layer_height); // Number of layers between support bottom and model. - const size_t skip_layer_count = std::max(uint64_t(1), round_divide(mesh.settings.get("support_interface_skip_height"), layer_height)); // Resolution of generating support bottoms above model. + const size_t skip_layer_count + = std::max(uint64_t(1), round_divide(mesh.settings.get("support_interface_skip_height"), layer_height)); // Resolution of generating support bottoms above model. const coord_t bottom_line_width = mesh_group_settings.get("support_bottom_extruder_nr").settings.get("support_bottom_line_width"); const coord_t bottom_outline_offset = mesh_group_settings.get("support_bottom_extruder_nr").settings.get("support_bottom_offset"); const size_t scan_count = std::max(size_t(1), (bottom_layer_count - 1) / skip_layer_count); // How many measurements to take to generate bottom areas. - const float z_skip = std::max(1.0f, float(bottom_layer_count - 1) / float(scan_count)); // How many layers to skip between measurements. Using float for better spread, but this is later rounded. + const float z_skip = std::max( + 1.0f, + float(bottom_layer_count - 1) / float(scan_count)); // How many layers to skip between measurements. Using float for better spread, but this is later rounded. const double minimum_bottom_area = mesh.settings.get("minimum_bottom_area"); std::vector& support_layers = storage.support.supportLayers; - for (unsigned int layer_idx = support_layers.size() - 1; static_cast(layer_idx) >= static_cast(z_distance_bottom); layer_idx--) + for (LayerIndex layer_idx = support_layers.size() - 1; layer_idx >= static_cast(z_distance_bottom); --layer_idx) { const unsigned int bottom_layer_idx_below = std::max(0, int(layer_idx) - int(bottom_layer_count) - int(z_distance_bottom)); Polygons mesh_outlines; @@ -1755,18 +1784,23 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh return; } const coord_t z_distance_top = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); // Number of layers between support roof and model. - const size_t skip_layer_count = std::max(uint64_t(1), round_divide(mesh.settings.get("support_interface_skip_height"), layer_height)); // Resolution of generating support roof below model. + const size_t skip_layer_count + = std::max(uint64_t(1), round_divide(mesh.settings.get("support_interface_skip_height"), layer_height)); // Resolution of generating support roof below model. const coord_t roof_line_width = mesh_group_settings.get("support_roof_extruder_nr").settings.get("support_roof_line_width"); const coord_t roof_outline_offset = mesh_group_settings.get("support_roof_extruder_nr").settings.get("support_roof_offset"); const size_t scan_count = std::max(size_t(1), (roof_layer_count - 1) / skip_layer_count); // How many measurements to take to generate roof areas. - const float z_skip = std::max(1.0f, float(roof_layer_count - 1) / float(scan_count)); // How many layers to skip between measurements. Using float for better spread, but this is later rounded. + const float z_skip = std::max( + 1.0f, + float(roof_layer_count - 1) / float(scan_count)); // How many layers to skip between measurements. Using float for better spread, but this is later rounded. const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); std::vector& support_layers = storage.support.supportLayers; - for (LayerIndex layer_idx = 0; layer_idx < static_cast(support_layers.size() - z_distance_top); layer_idx++) + for (LayerIndex layer_idx = static_cast(support_layers.size() - z_distance_top) - 1; layer_idx >= 0; --layer_idx) { - const LayerIndex top_layer_idx_above = std::min(static_cast(support_layers.size() - 1), layer_idx + roof_layer_count + z_distance_top); // Maximum layer of the model that generates support roof. + const LayerIndex top_layer_idx_above{ + std::min(LayerIndex{ support_layers.size() - 1 }, LayerIndex{ layer_idx + roof_layer_count + z_distance_top }) + }; // Maximum layer of the model that generates support roof. Polygons mesh_outlines; for (float layer_idx_above = top_layer_idx_above; layer_idx_above > layer_idx + z_distance_top; layer_idx_above -= z_skip) { @@ -1775,18 +1809,17 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh Polygons roofs; generateSupportInterfaceLayer(global_support_areas_per_layer[layer_idx], mesh_outlines, roof_line_width, roof_outline_offset, minimum_roof_area, roofs); support_layers[layer_idx].support_roof.add(roofs); + if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + { + support_layers[layer_idx].support_fractional_roof.add(roofs.difference(support_layers[layer_idx + 1].support_roof)); + } scripta::log("support_interface_roofs", roofs, SectionType::SUPPORT, layer_idx); } // Remove support in between the support roof and the model. Subtracts the roof polygons from the support polygons on the layers above it. - for (auto [layer_idx, support_layer] : support_layers - | ranges::views::enumerate - | ranges::views::drop(1) - | ranges::views::drop_last(z_distance_top)) + for (auto [layer_idx, support_layer] : support_layers | ranges::views::enumerate | ranges::views::drop(1) | ranges::views::drop_last(z_distance_top)) { - Polygons roof = support_layer.support_roof; - - if (roof.empty()) + if (support_layer.support_roof.empty()) { continue; } @@ -1795,12 +1828,18 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh int upper = std::min(static_cast(layer_idx + roof_layer_count + z_distance_top + 5), static_cast(global_support_areas_per_layer.size()) - 1); for (Polygons& global_support : global_support_areas_per_layer | ranges::views::slice(lower, upper)) { - global_support = global_support.difference(roof); + global_support = global_support.difference(support_layer.support_roof); } } } -void AreaSupport::generateSupportInterfaceLayer(Polygons& support_areas, const Polygons colliding_mesh_outlines, const coord_t safety_offset, const coord_t outline_offset, const double minimum_interface_area, Polygons& interface_polygons) +void AreaSupport::generateSupportInterfaceLayer( + Polygons& support_areas, + const Polygons colliding_mesh_outlines, + const coord_t safety_offset, + const coord_t outline_offset, + const double minimum_interface_area, + Polygons& interface_polygons) { Polygons model = colliding_mesh_outlines.unionPolygons(); interface_polygons = support_areas.offset(safety_offset / 2).intersection(model); diff --git a/src/timeEstimate.cpp b/src/timeEstimate.cpp index 3a535dfc13..3e2345a92f 100644 --- a/src/timeEstimate.cpp +++ b/src/timeEstimate.cpp @@ -1,15 +1,15 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher +#include "timeEstimate.h" + +#include "settings/Settings.h" +#include "utils/math.h" + +#include #include #include #include -#include - -#include "timeEstimate.h" -#include "utils/math.h" -#include "settings/Settings.h" -#include "settings/types/Ratio.h" namespace cura { @@ -44,7 +44,7 @@ void TimeEstimateCalculator::addTime(const Duration& time) extra_time += time; } -void TimeEstimateCalculator::setAcceleration(const Velocity& acc) +void TimeEstimateCalculator::setAcceleration(const Acceleration& acc) { acceleration = acc; } @@ -60,7 +60,7 @@ void TimeEstimateCalculator::reset() blocks.clear(); } -// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the +// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the // acceleration within the allotted distance. static inline Velocity maxAllowableSpeed(const Acceleration& acceleration, const Velocity& target_velocity, double distance) { @@ -77,7 +77,7 @@ static inline float estimateAccelerationDistance(const Velocity& initial_rate, c return (square(target_rate) - square(initial_rate)) / (2.0 * acceleration); } -// This function gives you the point at which you must start braking (at the rate of -acceleration) if +// This function gives you the point at which you must start braking (at the rate of -acceleration) if // you started at speed initial_rate and accelerated until this point and want to end at the final_rate after // a total travel of distance. This can be used to compute the intersection point between acceleration and // deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed) @@ -123,13 +123,13 @@ static inline double intersectionDistance(const Velocity& initial_rate, const Ve static inline double accelerationTimeFromDistance(const Velocity& initial_feedrate, const Velocity& distance, const Acceleration& acceleration) { double discriminant = square(initial_feedrate) - 2 * acceleration * -distance; - //If discriminant is negative, we're moving in the wrong direction. - //Making the discriminant 0 then gives the extremum of the parabola instead of the intersection. + // If discriminant is negative, we're moving in the wrong direction. + // Making the discriminant 0 then gives the extremum of the parabola instead of the intersection. discriminant = std::max(0.0, discriminant); return (-initial_feedrate + sqrt(discriminant)) / acceleration; } -void TimeEstimateCalculator::calculateTrapezoidForBlock(Block *block, const Ratio entry_factor, const Ratio exit_factor) +void TimeEstimateCalculator::calculateTrapezoidForBlock(Block* block, const Ratio entry_factor, const Ratio exit_factor) { const Velocity initial_feedrate = block->nominal_feedrate * entry_factor; const Velocity final_feedrate = block->nominal_feedrate * exit_factor; @@ -138,7 +138,7 @@ void TimeEstimateCalculator::calculateTrapezoidForBlock(Block *block, const Rati const double decelerate_distance = estimateAccelerationDistance(block->nominal_feedrate, final_feedrate, -block->acceleration); // Calculate the size of Plateau of Nominal Rate. - double plateau_distance = block->distance-accelerate_distance - decelerate_distance; + double plateau_distance = block->distance - accelerate_distance - decelerate_distance; // Is the Plateau of Nominal Rate smaller than nothing? That means no cruising, and we will // have to use intersection_distance() to calculate when to abort acceleration and start braking @@ -147,7 +147,7 @@ void TimeEstimateCalculator::calculateTrapezoidForBlock(Block *block, const Rati { accelerate_distance = intersectionDistance(initial_feedrate, final_feedrate, block->acceleration, block->distance); accelerate_distance = std::max(accelerate_distance, 0.0); // Check limits due to numerical round-off - accelerate_distance = std::min(accelerate_distance, block->distance);//(We can cast here to unsigned, because the above line ensures that we are above zero) + accelerate_distance = std::min(accelerate_distance, block->distance); //(We can cast here to unsigned, because the above line ensures that we are above zero) plateau_distance = 0; } @@ -164,8 +164,8 @@ void TimeEstimateCalculator::plan(Position newPos, Velocity feedrate, PrintFeatu block.feature = feature; - //block.maxTravel = 0; //Done by memset. - for(size_t n = 0; n < NUM_AXIS; n++) + // block.maxTravel = 0; //Done by memset. + for (size_t n = 0; n < NUM_AXIS; n++) { block.delta[n] = newPos[n] - currentPosition[n]; block.absDelta[n] = std::abs(block.delta[n]); @@ -185,11 +185,11 @@ void TimeEstimateCalculator::plan(Position newPos, Velocity feedrate, PrintFeatu block.distance = block.absDelta[3]; } block.nominal_feedrate = feedrate; - + Position current_feedrate; Position current_abs_feedrate; Ratio feedrate_factor = 1.0; - for(size_t n = 0; n < NUM_AXIS; n++) + for (size_t n = 0; n < NUM_AXIS; n++) { current_feedrate[n] = (block.delta[n] * feedrate) / block.distance; current_abs_feedrate[n] = std::abs(current_feedrate[n]); @@ -198,40 +198,40 @@ void TimeEstimateCalculator::plan(Position newPos, Velocity feedrate, PrintFeatu feedrate_factor = std::min(feedrate_factor, Ratio(max_feedrate[n] / current_abs_feedrate[n])); } } - //TODO: XY_FREQUENCY_LIMIT - + // TODO: XY_FREQUENCY_LIMIT + if (feedrate_factor < 1.0) { - for(size_t n = 0; n < NUM_AXIS; n++) + for (size_t n = 0; n < NUM_AXIS; n++) { current_feedrate[n] *= feedrate_factor; current_abs_feedrate[n] *= feedrate_factor; } block.nominal_feedrate *= feedrate_factor; } - + block.acceleration = acceleration; - for(size_t n = 0; n < NUM_AXIS; n++) + for (size_t n = 0; n < NUM_AXIS; n++) { if (block.acceleration * (block.absDelta[n] / block.distance) > max_acceleration[n]) { block.acceleration = max_acceleration[n]; } } - - Velocity vmax_junction = max_xy_jerk / 2; - Ratio vmax_junction_factor = 1.0; - if (current_abs_feedrate[Z_AXIS] > max_z_jerk / 2) + + Velocity vmax_junction{ max_xy_jerk / 2.0 }; + Ratio vmax_junction_factor{ 1.0 }; + if (current_abs_feedrate[Z_AXIS] > max_z_jerk / 2.0) { - vmax_junction = std::min(vmax_junction, max_z_jerk / 2); + vmax_junction = std::min(vmax_junction, Velocity{ max_z_jerk / 2.0 }); } - if (current_abs_feedrate[E_AXIS] > max_e_jerk / 2) + if (current_abs_feedrate[E_AXIS] > max_e_jerk / 2.0) { - vmax_junction = std::min(vmax_junction, max_e_jerk / 2); + vmax_junction = std::min(vmax_junction, Velocity{ max_e_jerk / 2.0 }); } vmax_junction = std::min(vmax_junction, block.nominal_feedrate); const Velocity safe_speed = vmax_junction; - + if ((blocks.size() > 0) && (previous_nominal_feedrate > 0.0001)) { const Velocity xy_jerk = sqrt(square(current_feedrate[X_AXIS] - previous_feedrate[X_AXIS]) + square(current_feedrate[Y_AXIS] - previous_feedrate[Y_AXIS])); @@ -250,7 +250,7 @@ void TimeEstimateCalculator::plan(Position newPos, Velocity feedrate, PrintFeatu { vmax_junction_factor = std::min(vmax_junction_factor, Ratio(max_e_jerk / e_jerk)); } - vmax_junction = std::min(previous_nominal_feedrate, vmax_junction * vmax_junction_factor); // Limit speed to max previous speed + vmax_junction = std::min(previous_nominal_feedrate, Velocity{ vmax_junction * vmax_junction_factor }); // Limit speed to max previous speed } block.max_entry_speed = vmax_junction; @@ -275,10 +275,10 @@ std::vector TimeEstimateCalculator::calculate() reversePass(); forwardPass(); recalculateTrapezoids(); - + std::vector totals(static_cast(PrintFeatureType::NumPrintFeatureTypes), 0.0); totals[static_cast(PrintFeatureType::NoneType)] = extra_time; // Extra time (pause for minimum layer time, etc) is marked as NoneType - for(unsigned int n = 0; n < blocks.size(); n++) + for (unsigned int n = 0; n < blocks.size(); n++) { const Block& block = blocks[n]; const double plateau_distance = block.decelerate_after - block.accelerate_until; @@ -290,10 +290,10 @@ std::vector TimeEstimateCalculator::calculate() return totals; } -void TimeEstimateCalculator::plannerReversePassKernel(Block *previous, Block *current, Block *next) +void TimeEstimateCalculator::plannerReversePassKernel(Block* previous, Block* current, Block* next) { (void)previous; - if (!current || !next) + if (! current || ! next) { return; } @@ -305,7 +305,7 @@ void TimeEstimateCalculator::plannerReversePassKernel(Block *previous, Block *cu { // If nominal length true, max junction speed is guaranteed to be reached. Only compute // for max allowable speed if block is decelerating and nominal length is false. - if ((!current->nominal_length_flag) && (current->max_entry_speed > next->entry_speed)) + if ((! current->nominal_length_flag) && (current->max_entry_speed > next->entry_speed)) { current->entry_speed = std::min(current->max_entry_speed, maxAllowableSpeed(-current->acceleration, next->entry_speed, current->distance)); } @@ -319,20 +319,20 @@ void TimeEstimateCalculator::plannerReversePassKernel(Block *previous, Block *cu void TimeEstimateCalculator::reversePass() { - Block* block[3] = {nullptr, nullptr, nullptr}; - for(size_t n = blocks.size() - 1; int(n) >= 0; n--) + Block* block[3] = { nullptr, nullptr, nullptr }; + for (size_t n = blocks.size() - 1; int(n) >= 0; n--) { - block[2]= block[1]; - block[1]= block[0]; + block[2] = block[1]; + block[1] = block[0]; block[0] = &blocks[n]; plannerReversePassKernel(block[0], block[1], block[2]); } } -void TimeEstimateCalculator::plannerForwardPassKernel(Block *previous, Block *current, Block *next) +void TimeEstimateCalculator::plannerForwardPassKernel(Block* previous, Block* current, Block* next) { (void)next; - if (!previous) + if (! previous) { return; } @@ -341,7 +341,7 @@ void TimeEstimateCalculator::plannerForwardPassKernel(Block *previous, Block *cu // full speed change within the block, we need to adjust the entry speed accordingly. Entry // speeds have already been reset, maximized, and reverse planned by reverse planner. // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. - if (!previous->nominal_length_flag) + if (! previous->nominal_length_flag) { if (previous->entry_speed < current->entry_speed) { @@ -359,11 +359,11 @@ void TimeEstimateCalculator::plannerForwardPassKernel(Block *previous, Block *cu void TimeEstimateCalculator::forwardPass() { - Block* block[3] = {nullptr, nullptr, nullptr}; - for(size_t n = 0; n < blocks.size(); n++) + Block* block[3] = { nullptr, nullptr, nullptr }; + for (size_t n = 0; n < blocks.size(); n++) { - block[0]= block[1]; - block[1]= block[2]; + block[0] = block[1]; + block[1] = block[2]; block[2] = &blocks[n]; plannerForwardPassKernel(block[0], block[1], block[2]); } @@ -372,10 +372,10 @@ void TimeEstimateCalculator::forwardPass() void TimeEstimateCalculator::recalculateTrapezoids() { - Block *current; - Block *next = nullptr; + Block* current; + Block* next = nullptr; - for(unsigned int n=0; n // atan2 -#include -#include // swap - #include "utils/IntPoint.h" // dot -namespace cura +#include // swap +#include +#include // atan2 + +namespace cura { float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c) @@ -20,10 +20,7 @@ float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c) const coord_t det = ba.X * bc.Y - ba.Y * bc.X; // determinant if (det == 0) { - if ( - (ba.X != 0 && (ba.X > 0) == (bc.X > 0)) - || (ba.X == 0 && (ba.Y > 0) == (bc.Y > 0)) - ) + if ((ba.X != 0 && (ba.X > 0) == (bc.X > 0)) || (ba.X == 0 && (ba.Y > 0) == (bc.Y > 0))) { return 0; // pointy bit } @@ -37,7 +34,7 @@ float LinearAlg2D::getAngleLeft(const Point& a, const Point& b, const Point& c) { return angle; } - else + else { return M_PI * 2 + angle; } @@ -55,7 +52,7 @@ bool LinearAlg2D::getPointOnLineWithDist(const Point& p, const Point& a, const P const Point ab = b - a; const coord_t ab_size = vSize(ab); const Point ap = p - a; - const coord_t ax_size = (ab_size < 50)? dot(normal(ab, 1000), ap) / 1000 : dot(ab, ap) / ab_size; + const coord_t ax_size = (ab_size < 50) ? dot(normal(ab, 1000), ap) / 1000 : dot(ab, ap) / ab_size; const coord_t ap_size2 = vSize2(ap); const coord_t px_size = sqrt(std::max(coord_t(0), ap_size2 - ax_size * ax_size)); if (px_size > dist) @@ -159,9 +156,10 @@ bool LinearAlg2D::lineSegmentsCollide(const Point& a_from_transformed, const Poi { assert(std::abs(a_from_transformed.Y - a_to_transformed.Y) < 2 && "line a is supposed to be transformed to be aligned with the X axis!"); assert(a_from_transformed.X - 2 <= a_to_transformed.X && "line a is supposed to be aligned with X axis in positive direction!"); - if ((b_from_transformed.Y >= a_from_transformed.Y && b_to_transformed.Y <= a_from_transformed.Y) || (b_to_transformed.Y >= a_from_transformed.Y && b_from_transformed.Y <= a_from_transformed.Y)) + if ((b_from_transformed.Y >= a_from_transformed.Y && b_to_transformed.Y <= a_from_transformed.Y) + || (b_to_transformed.Y >= a_from_transformed.Y && b_from_transformed.Y <= a_from_transformed.Y)) { - if(b_to_transformed.Y == b_from_transformed.Y) + if (b_to_transformed.Y == b_from_transformed.Y) { if (b_to_transformed.X < b_from_transformed.X) { @@ -179,7 +177,8 @@ bool LinearAlg2D::lineSegmentsCollide(const Point& a_from_transformed, const Poi } else { - const coord_t x = b_from_transformed.X + (b_to_transformed.X - b_from_transformed.X) * (a_from_transformed.Y - b_from_transformed.Y) / (b_to_transformed.Y - b_from_transformed.Y); + const coord_t x + = b_from_transformed.X + (b_to_transformed.X - b_from_transformed.X) * (a_from_transformed.Y - b_from_transformed.Y) / (b_to_transformed.Y - b_from_transformed.Y); if (x >= a_from_transformed.X && x <= a_to_transformed.X) { return true; @@ -191,43 +190,10 @@ bool LinearAlg2D::lineSegmentsCollide(const Point& a_from_transformed, const Poi coord_t LinearAlg2D::getDist2FromLine(const Point& p, const Point& a, const Point& b) { - constexpr coord_t SQRT_LLONG_MAX_FLOOR = 3037000499; - - // x.......a------------b - // : - // : - // p - // return px_size^2 (if there is no overflow) - const Point vab = b - a; - const Point vap = p - a; - const coord_t ab_size2 = vSize2(vab); - const coord_t ap_size2 = vSize2(vap); - coord_t px_size2; - if(ab_size2 == 0) //Line of 0 length. Assume it's a line perpendicular to the direction to p. - { - return ap_size2; - } - const coord_t dott = dot(vab, vap); - if (dott != 0 && std::abs(dott) > SQRT_LLONG_MAX_FLOOR) - { // dott * dott will overflow so calculate px_size2 via its square root - coord_t px_size = LinearAlg2D::getDistFromLine(p, a, b); - if (px_size <= SQRT_LLONG_MAX_FLOOR) - { - // Due to rounding and conversion errors, this multiplication may not be the exact value that would be - // produced via the dott product, but it should still be close enough - px_size2 = px_size * px_size; - } - else - { - px_size2 = std::numeric_limits::max(); - } - } - else - { - const coord_t ax_size2 = dott * dott / ab_size2; - px_size2 = std::max(coord_t(0), ap_size2 - ax_size2); - } - return px_size2; + // NOTE: The version that tried to do a faster calulation wasn't actually that much faster, and introduced errors. + // Use this for now, should we need this, we can reimplement later. + const auto dist = getDistFromLine(p, a, b); + return dist * dist; } bool LinearAlg2D::isInsideCorner(const Point a, const Point b, const Point c, const Point query_point) @@ -247,26 +213,25 @@ bool LinearAlg2D::isInsideCorner(const Point a, const Point b, const Point c, co */ - - constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. + constexpr coord_t normal_length = 10000; // Create a normal vector of reasonable length in order to reduce rounding error. const Point ba = normal(a - b, normal_length); const Point bc = normal(c - b, normal_length); const Point bq = query_point - b; - const Point perpendicular = turn90CCW(bq); //The query projects to this perpendicular to coordinate 0. - const coord_t project_a_perpendicular = dot(ba, perpendicular); //Project vertex A on the perpendicular line. - const coord_t project_c_perpendicular = dot(bc, perpendicular); //Project vertex C on the perpendicular line. - if ((project_a_perpendicular > 0) != (project_c_perpendicular > 0)) //Query is between A and C on the projection. + const Point perpendicular = turn90CCW(bq); // The query projects to this perpendicular to coordinate 0. + const coord_t project_a_perpendicular = dot(ba, perpendicular); // Project vertex A on the perpendicular line. + const coord_t project_c_perpendicular = dot(bc, perpendicular); // Project vertex C on the perpendicular line. + if ((project_a_perpendicular > 0) != (project_c_perpendicular > 0)) // Query is between A and C on the projection. { - return project_a_perpendicular > 0; //Due to the winding order of corner ABC, this means that the query is inside. + return project_a_perpendicular > 0; // Due to the winding order of corner ABC, this means that the query is inside. } - else //Beyond either A or C, but it could still be inside of the polygon. + else // Beyond either A or C, but it could still be inside of the polygon. { - const coord_t project_a_parallel = dot(ba, bq); //Project not on the perpendicular, but on the original. + const coord_t project_a_parallel = dot(ba, bq); // Project not on the perpendicular, but on the original. const coord_t project_c_parallel = dot(bc, bq); - //Either: - // * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or - // * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel). + // Either: + // * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or + // * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel). return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0); } } @@ -281,7 +246,7 @@ coord_t LinearAlg2D::getDistFromLine(const Point& p, const Point& a, const Point const Point vab = b - a; const Point vap = p - a; const double ab_size = vSize(vab); - if(ab_size == 0) //Line of 0 length. Assume it's a line perpendicular to the direction to p. + if (ab_size == 0) // Line of 0 length. Assume it's a line perpendicular to the direction to p. { return vSize(vap); } @@ -290,4 +255,11 @@ coord_t LinearAlg2D::getDistFromLine(const Point& p, const Point& a, const Point return px_size; } +Point LinearAlg2D::getBisectorVector(const Point& intersect, const Point& a, const Point& b, const coord_t vec_len) +{ + const auto a0 = a - intersect; + const auto b0 = b - intersect; + return (((a0 * vec_len) / std::max(1LL, vSize(a0))) + ((b0 * vec_len) / std::max(1LL, vSize(b0)))) / 2; +} + } // namespace cura diff --git a/src/utils/VoronoiUtils.cpp b/src/utils/VoronoiUtils.cpp index 4b7f01799b..0a134906da 100644 --- a/src/utils/VoronoiUtils.cpp +++ b/src/utils/VoronoiUtils.cpp @@ -1,15 +1,16 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include -#include - -#include - #include "utils/VoronoiUtils.h" + #include "utils/linearAlg2D.h" #include "utils/macros.h" +#include + +#include +#include + namespace cura { @@ -167,7 +168,7 @@ std::vector VoronoiUtils::discretizeParabola(const Point& p, const Segmen // are more than 10 microns away from the projected apex bool add_apex = (sx - px) * dir < -10 && (ex - px) * dir > 10; -// assert(! (add_marking_start && add_marking_end) || add_apex); + // assert(! (add_marking_start && add_marking_end) || add_apex); if (add_marking_start && add_marking_end && ! add_apex) { RUN_ONCE(spdlog::warn("Failing to discretize parabola! Must add an apex or one of the endpoints.")); diff --git a/src/utils/channel.cpp b/src/utils/channel.cpp new file mode 100644 index 0000000000..2a4c775767 --- /dev/null +++ b/src/utils/channel.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include "utils/channel.h" + +#include "utils/resources/certificate.pem.h" + +#include +#include + +namespace cura::utils +{ +namespace details +{ +inline constexpr static bool ALLOW_REMOTE_CHANNELS = ENABLE_REMOTE_PLUGINS; +} // namespace details + +std::shared_ptr createChannel(const ChannelSetupConfiguration& config) +{ + constexpr auto create_credentials = [](const ChannelSetupConfiguration& config) + { + if (config.host == "localhost" || config.host == "127.0.0.1") + { + spdlog::info("Create local channel on port {}.", config.port); + return grpc::InsecureChannelCredentials(); + } + if (details::ALLOW_REMOTE_CHANNELS) + { + spdlog::info("Create local channel on port {}.", config.port); + auto creds_config = grpc::SslCredentialsOptions(); + creds_config.pem_root_certs = resources::certificate; + return grpc::SslCredentials(creds_config); + } + // Create empty credentials, so it'll make a dummy channel where all operations fail. + // This is consistent with creating a channel with the wrong credentials as it where. + spdlog::warn("Remote plugins where disabled, will not connect to {}:{}.", config.host, config.port); + return std::shared_ptr(); + }; + grpc::ChannelArguments args; + args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 200 * 1000 /*200 sec*/); + args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 100 * 1000 /*100 sec*/); + args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1); + + return grpc::CreateCustomChannel(fmt::format("{}:{}", config.host, config.port), create_credentials(config), args); +} + +} // namespace cura::utils diff --git a/src/utils/gettime.cpp b/src/utils/gettime.cpp index bfd665abaa..a7d8303857 100644 --- a/src/utils/gettime.cpp +++ b/src/utils/gettime.cpp @@ -1,21 +1,31 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #include "utils/gettime.h" +#include + namespace cura { - + TimeKeeper::TimeKeeper() { - restart(); } double TimeKeeper::restart() { - double ret = getTime() - startTime; - startTime = getTime(); + double ret = watch.elapsed().count(); + watch.reset(); return ret; } -}//namespace cura \ No newline at end of file +void TimeKeeper::registerTime(const std::string& stage, double threshold) +{ + double duration = restart(); + if (duration >= threshold) + { + registered_times.emplace_back(RegisteredTime{ stage, duration }); + } +} + +} // namespace cura diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 8135507834..d1f4debe21 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1,27 +1,33 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include "infill.h" -#include "utils/SparsePointGridInclusive.h" -#include "utils/linearAlg2D.h" #include "utils/polygonUtils.h" #include #include #include #include + #include +#include "infill.h" +#include "utils/SparsePointGridInclusive.h" +#include "utils/linearAlg2D.h" + #ifdef DEBUG +#include + #include "utils/AABB.h" #include "utils/SVG.h" -#include #endif namespace cura { -const std::function PolygonUtils::no_penalty_function = [](Point) { return 0; }; +const std::function PolygonUtils::no_penalty_function = [](Point) +{ + return 0; +}; int64_t PolygonUtils::segmentLength(PolygonsPointIndex start, PolygonsPointIndex end) { @@ -98,7 +104,7 @@ std::vector PolygonUtils::spreadDotsArea(const Polygons& polygons, Point Infill infill_gen(EFillMethod::LINES, false, false, polygons, 0, grid_size.X, 0, 1, 0, 0, 0, 0, 0); Polygons result_polygons; Polygons result_lines; - infill_gen.generate(dummy_toolpaths, result_polygons, result_lines, dummy_settings, 0, SectionType::DOTS); // FIXME: @jellespijker make sure the propper layer nr is used + infill_gen.generate(dummy_toolpaths, result_polygons, result_lines, dummy_settings, 0, SectionType::DOTS); // FIXME: @jellespijker make sure the propper layer nr is used std::vector result; for (PolygonRef line : result_lines) { @@ -120,7 +126,13 @@ std::vector PolygonUtils::spreadDotsArea(const Polygons& polygons, Point return result; } -bool PolygonUtils::lineSegmentPolygonsIntersection(const Point& a, const Point& b, const Polygons& current_outlines, const LocToLineGrid& outline_locator, Point& result, const coord_t within_max_dist) +bool PolygonUtils::lineSegmentPolygonsIntersection( + const Point& a, + const Point& b, + const Polygons& current_outlines, + const LocToLineGrid& outline_locator, + Point& result, + const coord_t within_max_dist) { const coord_t within_max_dist2 = within_max_dist * within_max_dist; @@ -129,7 +141,8 @@ bool PolygonUtils::lineSegmentPolygonsIntersection(const Point& a, const Point& const auto processOnIntersect = [&result, &closest_dist2, &a, &b, &coll](const Point& p_start, const Point& p_end) { - if (LinearAlg2D::lineLineIntersection(a, b, p_start, p_end, coll) && LinearAlg2D::pointIsProjectedBeyondLine(coll, p_start, p_end) == 0 && LinearAlg2D::pointIsProjectedBeyondLine(coll, a, b) == 0) + if (LinearAlg2D::lineLineIntersection(a, b, p_start, p_end, coll) && LinearAlg2D::pointIsProjectedBeyondLine(coll, p_start, p_end) == 0 + && LinearAlg2D::pointIsProjectedBeyondLine(coll, a, b) == 0) { const coord_t dist2 = vSize2(b - coll); if (dist2 < closest_dist2) @@ -233,13 +246,14 @@ unsigned int PolygonUtils::moveOutside(const Polygons& polygons, Point& from, in return moveInside(polygons, from, -distance, maxDist2); } -ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& polygons, - Point& from, - const int distance, - const int64_t max_dist2, - const Polygons* loc_to_line_polygons, - const LocToLineGrid* loc_to_line_grid, - const std::function& penalty_function) +ClosestPolygonPoint PolygonUtils::moveInside2( + const Polygons& polygons, + Point& from, + const int distance, + const int64_t max_dist2, + const Polygons* loc_to_line_polygons, + const LocToLineGrid* loc_to_line_grid, + const std::function& penalty_function) { std::optional closest_polygon_point; if (loc_to_line_grid) @@ -253,8 +267,14 @@ ClosestPolygonPoint PolygonUtils::moveInside2(const Polygons& polygons, return _moveInside2(*closest_polygon_point, distance, from, max_dist2); } -ClosestPolygonPoint - PolygonUtils::moveInside2(const Polygons& loc_to_line_polygons, ConstPolygonRef polygon, Point& from, const int distance, const int64_t max_dist2, const LocToLineGrid* loc_to_line_grid, const std::function& penalty_function) +ClosestPolygonPoint PolygonUtils::moveInside2( + const Polygons& loc_to_line_polygons, + ConstPolygonRef polygon, + Point& from, + const int distance, + const int64_t max_dist2, + const LocToLineGrid* loc_to_line_grid, + const std::function& penalty_function) { std::optional closest_polygon_point; if (loc_to_line_grid) @@ -596,25 +616,27 @@ Point PolygonUtils::moveInside(const ClosestPolygonPoint& cpp, const int distanc } } -ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, - Point& from, - int preferred_dist_inside, - int64_t max_dist2, - const Polygons* loc_to_line_polygons, - const LocToLineGrid* loc_to_line_grid, - const std::function& penalty_function) +ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside( + const Polygons& polygons, + Point& from, + int preferred_dist_inside, + int64_t max_dist2, + const Polygons* loc_to_line_polygons, + const LocToLineGrid* loc_to_line_grid, + const std::function& penalty_function) { const ClosestPolygonPoint closest_polygon_point = moveInside2(polygons, from, preferred_dist_inside, max_dist2, loc_to_line_polygons, loc_to_line_grid, penalty_function); return ensureInsideOrOutside(polygons, from, closest_polygon_point, preferred_dist_inside, loc_to_line_polygons, loc_to_line_grid, penalty_function); } -ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons, - Point& from, - const ClosestPolygonPoint& closest_polygon_point, - int preferred_dist_inside, - const Polygons* loc_to_line_polygons, - const LocToLineGrid* loc_to_line_grid, - const std::function& penalty_function) +ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside( + const Polygons& polygons, + Point& from, + const ClosestPolygonPoint& closest_polygon_point, + int preferred_dist_inside, + const Polygons* loc_to_line_polygons, + const LocToLineGrid* loc_to_line_grid, + const std::function& penalty_function) { if (! closest_polygon_point.isValid()) { @@ -646,7 +668,8 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons else { const coord_t offset = (is_outside_boundary) ? -preferred_dist_inside : preferred_dist_inside; // perform inset on outer boundary and outset on holes - Polygons insetted = closest_poly.offset(offset / 2); // perform less inset, because chances are (thin parts of) the polygon will disappear, given that moveInside did an overshoot + Polygons insetted + = closest_poly.offset(offset / 2); // perform less inset, because chances are (thin parts of) the polygon will disappear, given that moveInside did an overshoot if (insetted.size() == 0) { return ClosestPolygonPoint(); // we couldn't move inside @@ -666,7 +689,7 @@ ClosestPolygonPoint PolygonUtils::ensureInsideOrOutside(const Polygons& polygons { #ifdef DEBUG static bool has_run = false; - if ( ! has_run) + if (! has_run) { try { @@ -755,7 +778,8 @@ void PolygonUtils::walkToNearestSmallestConnection(ClosestPolygonPoint& poly1_re // \-'| // o o >> should find connection here coord_t best_distance2 = vSize2(poly1_result.p() - poly2_result.p()); - auto check_neighboring_vert = [&best_distance2](ConstPolygonRef from_poly, ConstPolygonRef to_poly, ClosestPolygonPoint& from_poly_result, ClosestPolygonPoint& to_poly_result, bool vertex_after) + auto check_neighboring_vert + = [&best_distance2](ConstPolygonRef from_poly, ConstPolygonRef to_poly, ClosestPolygonPoint& from_poly_result, ClosestPolygonPoint& to_poly_result, bool vertex_after) { const Point after_poly2_result = to_poly[(to_poly_result.point_idx + vertex_after) % to_poly.size()]; const ClosestPolygonPoint poly1_after_poly2_result = findNearestClosest(after_poly2_result, from_poly, from_poly_result.point_idx); @@ -978,7 +1002,8 @@ std::unique_ptr PolygonUtils::createLocToLineGrid(const Polygons& * * We could skip the duplication by keeping a vector of vectors of bools. */ -std::optional PolygonUtils::findClose(Point from, const Polygons& polygons, const LocToLineGrid& loc_to_line, const std::function& penalty_function) +std::optional + PolygonUtils::findClose(Point from, const Polygons& polygons, const LocToLineGrid& loc_to_line, const std::function& penalty_function) { std::vector near_lines = loc_to_line.getNearby(from, loc_to_line.getCellSize()); @@ -1011,7 +1036,8 @@ std::optional PolygonUtils::findClose(Point from, const Pol } } -std::vector> PolygonUtils::findClose(ConstPolygonRef from, const Polygons& destination, const LocToLineGrid& destination_loc_to_line, const std::function& penalty_function) +std::vector> + PolygonUtils::findClose(ConstPolygonRef from, const Polygons& destination, const LocToLineGrid& destination_loc_to_line, const std::function& penalty_function) { std::vector> ret; int p0_idx = from.size() - 1; @@ -1166,7 +1192,8 @@ std::optional PolygonUtils::getNextParallelIntersection(con coord_t prev_projected = 0; for (unsigned int next_point_nr = 0; next_point_nr < poly.size(); next_point_nr++) { - const unsigned int next_point_idx = forward ? (start.point_idx + 1 + next_point_nr) % poly.size() : (static_cast(start.point_idx) - next_point_nr + poly.size()) % poly.size(); // cast in order to accomodate subtracting + const unsigned int next_point_idx = forward ? (start.point_idx + 1 + next_point_nr) % poly.size() + : (static_cast(start.point_idx) - next_point_nr + poly.size()) % poly.size(); // cast in order to accomodate subtracting const Point next_vert = poly[next_point_idx]; const Point so = next_vert - s; const coord_t projected = dot(shift, so) / dist; @@ -1176,7 +1203,8 @@ std::optional PolygonUtils::getNextParallelIntersection(con const coord_t segment_length = vSize(segment_vector); const coord_t projected_segment_length = std::abs(projected - prev_projected); const int16_t sign = (projected > 0) ? 1 : -1; - const coord_t projected_inter_segment_length = dist - sign * prev_projected; // add the prev_projected to dist if it is projected to the other side of the input line than where the intersection occurs. + const coord_t projected_inter_segment_length + = dist - sign * prev_projected; // add the prev_projected to dist if it is projected to the other side of the input line than where the intersection occurs. const coord_t inter_segment_length = segment_length * projected_inter_segment_length / projected_segment_length; const Point intersection = prev_vert + normal(next_vert - prev_vert, inter_segment_length); @@ -1212,7 +1240,8 @@ bool PolygonUtils::polygonCollidesWithLineSegment(const Point from, const Point PolygonsPointIndex result; - std::function process_elem_func = [transformed_from, transformed_to, &transformation_matrix, &result, &ret](const PolygonsPointIndex& line_start) + std::function process_elem_func + = [transformed_from, transformed_to, &transformation_matrix, &result, &ret](const PolygonsPointIndex& line_start) { Point p0 = transformation_matrix.apply(line_start.p()); Point p1 = transformation_matrix.apply(line_start.next().p()); @@ -1331,7 +1360,11 @@ bool PolygonUtils::polygonOutlinesAdjacent(const ConstPolygonRef inner_poly, con return false; } -void PolygonUtils::findAdjacentPolygons(std::vector& adjacent_poly_indices, const ConstPolygonRef& poly, const std::vector& possible_adjacent_polys, const coord_t max_gap) +void PolygonUtils::findAdjacentPolygons( + std::vector& adjacent_poly_indices, + const ConstPolygonRef& poly, + const std::vector& possible_adjacent_polys, + const coord_t max_gap) { // given a polygon, and a vector of polygons, return a vector containing the indices of the polygons that are adjacent to the given polygon for (unsigned poly_idx = 0; poly_idx < possible_adjacent_polys.size(); ++poly_idx) @@ -1497,7 +1530,7 @@ Polygons PolygonUtils::unionManySmall(const Polygons& p) Polygons a, b; a.paths.reserve(p.paths.size() / 2); b.paths.reserve(a.paths.size() + 1); - for (const auto& [i, path] : p.paths | ranges::view::enumerate) + for (const auto& [i, path] : p.paths | ranges::views::enumerate) { (i % 2 == 0 ? b : a).paths.push_back(path); } @@ -1526,7 +1559,11 @@ Polygons PolygonUtils::clipPolygonWithAABB(const Polygons& src, const AABB& aabb Bottom = 8 }; - auto sides = [aabb](const Point& p) { return int(p.X < aabb.min.X) * int(Side::Left) + int(p.X > aabb.max.X) * int(Side::Right) + int(p.Y < aabb.min.Y) * int(Side::Bottom) + int(p.Y > aabb.max.Y) * int(Side::Top); }; + auto sides = [aabb](const Point& p) + { + return int(p.X < aabb.min.X) * int(Side::Left) + int(p.X > aabb.max.X) * int(Side::Right) + int(p.Y < aabb.min.Y) * int(Side::Bottom) + + int(p.Y > aabb.max.Y) * int(Side::Top); + }; int sides_prev = sides(path.back()); int sides_this = sides(path.front()); @@ -1570,4 +1607,32 @@ Polygons PolygonUtils::clipPolygonWithAABB(const Polygons& src, const AABB& aabb return out; } +Polygons PolygonUtils::generateOutset(const Polygons& inner_poly, size_t count, coord_t line_width) +{ + Polygons outset; + + Polygons current_outset; + for (size_t index = 0; index < count; ++index) + { + current_outset = index == 0 ? inner_poly.offset(line_width / 2) : current_outset.offset(line_width); + outset.add(current_outset); + } + + return outset; +} + +Polygons PolygonUtils::generateInset(const Polygons& outer_poly, coord_t line_width, coord_t initial_inset) +{ + Polygons inset; + + Polygons current_inset = outer_poly.offset(-(initial_inset + line_width / 2)); + while (! current_inset.empty()) + { + inset.add(current_inset); + current_inset = current_inset.offset(-line_width); + } + + return inset; +} + } // namespace cura diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 54492ad402..92543609e6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,29 +35,12 @@ set(TESTS_SRC_UTILS PolygonTest PolygonUtilsTest SimplifyTest + SmoothTest SparseGridTest StringTest UnionFindTest ) -set(TESTS_HELPERS_SRC ReadTestSettings.cpp ReadTestPolygons.cpp) - -set(TESTS_SRC_ARCUS) -if (ENABLE_ARCUS) - list(APPEND TESTS_SRC_ARCUS - ArcusCommunicationTest - ArcusCommunicationPrivateTest) - list(APPEND TESTS_HELPERS_SRC arcus/MockSocket.cpp) -endif () - -add_library(test_helpers ${TESTS_HELPERS_SRC}) -target_compile_definitions(test_helpers PUBLIC $<$:BUILD_TESTS> $<$:ARCUS>) -target_include_directories(test_helpers PUBLIC "../include") -target_link_libraries(test_helpers PRIVATE _CuraEngine GTest::gtest GTest::gmock clipper::clipper) -if (ENABLE_ARCUS) - target_link_libraries(test_helpers PUBLIC arcus::arcus protobuf::libprotobuf) -endif () - foreach (test ${TESTS_SRC_BASE}) add_executable(${test} main.cpp ${test}.cpp) add_test(NAME ${test} COMMAND "${test}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/tests/ExtruderPlanTest.cpp b/tests/ExtruderPlanTest.cpp index 965829ac3e..d0d2772979 100644 --- a/tests/ExtruderPlanTest.cpp +++ b/tests/ExtruderPlanTest.cpp @@ -1,7 +1,9 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "LayerPlan.h" //Code under test. +#include "pathPlanning/SpeedDerivatives.h" + #include #include //For calculating averages. @@ -69,32 +71,59 @@ class ExtruderPlanTestPathCollection GCodePathConfig travel_config; ExtruderPlanTestPathCollection() - : extrusion_config(PrintFeatureType::OuterWall, 400, 100, 1.0_r, GCodePathConfig::SpeedDerivatives(50, 1000, 10)) - , travel_config(PrintFeatureType::MoveCombing, 0, 100, 0.0_r, GCodePathConfig::SpeedDerivatives(120, 5000, 30)) + : extrusion_config(GCodePathConfig{ .type = PrintFeatureType::OuterWall, .line_width = 400, .layer_thickness = 100, .flow = 1.0_r, .speed_derivatives = SpeedDerivatives { .speed = 50.0, .acceleration = 1000.0, .jerk = 10.0 } }) + , travel_config(GCodePathConfig{ .type = PrintFeatureType::MoveCombing, .line_width = 0, .layer_thickness = 100, .flow = 0.0_r, .speed_derivatives = SpeedDerivatives { .speed = 120.0, .acceleration = 5000.0, .jerk = 30.0 } }) { - const SliceMeshStorage* mesh = nullptr; + std::shared_ptr mesh = nullptr; constexpr Ratio flow_1 = 1.0_r; constexpr Ratio width_1 = 1.0_r; constexpr bool no_spiralize = false; constexpr Ratio speed_1 = 1.0_r; - square.assign({ GCodePath(extrusion_config, mesh, SpaceFillType::PolyLines, flow_1, no_spiralize, speed_1) }); - square.back().points = - { - Point(0, 0), - Point(1000, 0), - Point(1000, 1000), - Point(0, 1000), - Point(0, 0) - }; - - lines.assign - ({ - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1) - }); + square.assign({ GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::PolyLines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 } }); + + square.back().points = { Point(0, 0), Point(1000, 0), Point(1000, 1000), Point(0, 1000), Point(0, 0) }; + + lines.assign({ GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 } }); lines[0].points = { Point(0, 0), Point(1000, 0) }; lines[1].points = { Point(1000, 0), Point(1000, 400) }; lines[2].points = { Point(1000, 400), Point(0, 400) }; @@ -104,14 +133,41 @@ class ExtruderPlanTestPathCollection constexpr Ratio flow_12 = 1.2_r; constexpr Ratio flow_08 = 0.8_r; constexpr Ratio flow_04 = 0.4_r; - decreasing_flow.assign - ({ - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_12, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_08, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_04, no_spiralize, speed_1) - }); + decreasing_flow.assign({ GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_12, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_08, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_04, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 } }); decreasing_flow[0].points = { Point(0, 0), Point(1000, 0) }; decreasing_flow[1].points = { Point(1000, 0), Point(1000, 400) }; decreasing_flow[2].points = { Point(1000, 400), Point(0, 400) }; @@ -121,28 +177,90 @@ class ExtruderPlanTestPathCollection constexpr Ratio speed_12 = 1.2_r; constexpr Ratio speed_08 = 0.8_r; constexpr Ratio speed_04 = 0.4_r; - decreasing_speed.assign - ({ - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_12), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_08), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_04) - }); + decreasing_speed.assign({ GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_12 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_08 }, + GCodePath{ .config = travel_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_04 } }); decreasing_speed[0].points = { Point(0, 0), Point(1000, 0) }; decreasing_speed[1].points = { Point(1000, 0), Point(1000, 400) }; decreasing_speed[2].points = { Point(1000, 400), Point(0, 400) }; decreasing_speed[3].points = { Point(0, 400), Point(0, 800) }; decreasing_speed[4].points = { Point(0, 800), Point(1000, 800) }; - variable_width.assign - ({ - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.8_r, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.6_r, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.4_r, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.2_r, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.0_r, no_spiralize, speed_1), + variable_width.assign({ + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = flow_1, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = 0.8_r, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = 0.6_r, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = 0.4_r, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = 0.2_r, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, + GCodePath{ .config = extrusion_config, + .mesh = mesh, + .space_fill_type = SpaceFillType::Lines, + .flow = 0.0_r, + .width_factor = width_1, + .spiralize = no_spiralize, + .speed_factor = speed_1 }, }); variable_width[0].points = { Point(0, 0), Point(1000, 0) }; variable_width[1].points = { Point(1000, 0), Point(2000, 0) }; @@ -198,18 +316,20 @@ class ExtruderPlanPathsParameterizedTest : public testing::TestWithParamgetFlowRatio() / path.flow * path.config->getSpeed() * path.speed_back_pressure_factor; + return path.getExtrusionMM3perMM() / path.config.getFlowRatio() / path.flow * path.config.getSpeed() * path.speed_back_pressure_factor; } [[nodiscard]] static bool shouldCountPath(const GCodePath& path) { - return path.flow > 0.0 && path.width_factor > 0.0 && path.config->getFlowRatio() > 0.0 && path.config->getLineWidth() > 0 && ! path.config->isTravelPath() && ! path.config->isBridgePath(); + return path.flow > 0.0 && path.width_factor > 0.0 && path.config.getFlowRatio() > 0.0 && path.config.getLineWidth() > 0 && ! path.config.isTravelPath() + && ! path.config.isBridgePath(); } }; -INSTANTIATE_TEST_SUITE_P(ExtruderPlanTestInstantiation, - ExtruderPlanPathsParameterizedTest, - testing::Values(path_collection.square, path_collection.lines, path_collection.decreasing_flow, path_collection.decreasing_speed, path_collection.variable_width)); +INSTANTIATE_TEST_SUITE_P( + ExtruderPlanTestInstantiation, + ExtruderPlanPathsParameterizedTest, + testing::Values(path_collection.square, path_collection.lines, path_collection.decreasing_flow, path_collection.decreasing_speed, path_collection.variable_width)); /*! * A fixture for general test cases involving extruder plans. @@ -271,7 +391,13 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull) extruder_plan.paths = GetParam(); extruder_plan.applyBackPressureCompensation(1.0_r); - auto first_extrusion = std::find_if(extruder_plan.paths.begin(), extruder_plan.paths.end(), [&](GCodePath& path) { return shouldCountPath(path); }); + auto first_extrusion = std::find_if( + extruder_plan.paths.begin(), + extruder_plan.paths.end(), + [&](GCodePath& path) + { + return shouldCountPath(path); + }); if (first_extrusion == extruder_plan.paths.end()) // Only travel moves in this plan. { return; @@ -286,7 +412,8 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationFull) continue; // Ignore travel moves. } const double flow_mm3_per_sec = calculatePathWidth(path); - EXPECT_NEAR(flow_mm3_per_sec, first_flow_mm3_per_sec, error_margin) << "Every path must have a flow rate equal to the first, since the flow changes were completely compensated for."; + EXPECT_NEAR(flow_mm3_per_sec, first_flow_mm3_per_sec, error_margin) + << "Every path must have a flow rate equal to the first, since the flow changes were completely compensated for."; } } @@ -330,7 +457,8 @@ TEST_P(ExtruderPlanPathsParameterizedTest, BackPressureCompensationHalf) ASSERT_EQ(original_flows.size(), new_flows.size()) << "We need to have the same number of extrusion moves."; for (size_t i = 0; i < new_flows.size(); ++i) { - EXPECT_NEAR((original_flows[i] - original_average) / 2.0, new_flows[i] - new_average, error_margin) << "The differences in flow rate needs to be approximately halved, within margin of rounding errors."; + EXPECT_NEAR((original_flows[i] - original_average) / 2.0, new_flows[i] - new_average, error_margin) + << "The differences in flow rate needs to be approximately halved, within margin of rounding errors."; } } diff --git a/tests/GCodeExportTest.cpp b/tests/GCodeExportTest.cpp index 2fe477e021..3fb07c9be9 100644 --- a/tests/GCodeExportTest.cpp +++ b/tests/GCodeExportTest.cpp @@ -1,15 +1,16 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "gcodeExport.h" // The unit under test. + #include "Application.h" // To set up a slice with settings. #include "RetractionConfig.h" // For extruder switch tests. #include "Slice.h" // To set up a slice with settings. #include "WipeScriptConfig.h" // For wipe script tests. #include "arcus/MockCommunication.h" // To prevent calls to any missing Communication class. -#include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" #include "utils/Date.h" // To check the Griffin header. + #include // NOLINTBEGIN(*-magic-numbers) @@ -52,10 +53,10 @@ class GCodeExportTest : public testing::Test gcode.current_extruder = 0; gcode.current_fan_speed = -1; gcode.total_print_times = std::vector(static_cast(PrintFeatureType::NumPrintFeatureTypes), 0.0); - gcode.currentSpeed = 1; - gcode.current_print_acceleration = -1; - gcode.current_travel_acceleration = -1; - gcode.current_jerk = -1; + gcode.currentSpeed = 1.0; + gcode.current_print_acceleration = -1.0; + gcode.current_travel_acceleration = -1.0; + gcode.current_jerk = -1.0; gcode.is_z_hopped = 0; gcode.setFlavor(EGCodeFlavor::MARLIN); gcode.bed_temperature = 0; @@ -102,12 +103,13 @@ TEST_F(GCodeExportTest, CommentMultiLine) "You can honestly say\n" "You made on that day\n" "A Chilean chinchilla's chin chilly"); - EXPECT_EQ(std::string(";If you catch a chinchilla in Chile\n" - ";And cut off its beard, willy-nilly\n" - ";You can honestly say\n" - ";You made on that day\n" - ";A Chilean chinchilla's chin chilly\n"), - output.str()) + EXPECT_EQ( + std::string(";If you catch a chinchilla in Chile\n" + ";And cut off its beard, willy-nilly\n" + ";You can honestly say\n" + ";You made on that day\n" + ";A Chilean chinchilla's chin chilly\n"), + output.str()) << "Each line must be preceded by a semicolon."; } @@ -116,10 +118,11 @@ TEST_F(GCodeExportTest, CommentMultiple) gcode.writeComment("Thunderbolt and lightning"); gcode.writeComment("Very very frightening me"); gcode.writeComment(" - Galileo (1638)"); - EXPECT_EQ(std::string(";Thunderbolt and lightning\n" - ";Very very frightening me\n" - "; - Galileo (1638)\n"), - output.str()) + EXPECT_EQ( + std::string(";Thunderbolt and lightning\n" + ";Very very frightening me\n" + "; - Galileo (1638)\n"), + output.str()) << "Semicolon before each line, and newline in between."; } @@ -210,10 +213,10 @@ class GriffinHeaderTest : public testing::TestWithParam gcode.current_extruder = 0; gcode.current_fan_speed = -1; gcode.total_print_times = std::vector(static_cast(PrintFeatureType::NumPrintFeatureTypes), 0.0); - gcode.currentSpeed = 1; - gcode.current_print_acceleration = -1; - gcode.current_travel_acceleration = -1; - gcode.current_jerk = -1; + gcode.currentSpeed = 1.0; + gcode.current_print_acceleration = -1.0; + gcode.current_travel_acceleration = -1.0; + gcode.current_jerk = -1.0; gcode.is_z_hopped = 0; gcode.setFlavor(EGCodeFlavor::MARLIN); gcode.initial_bed_temp = 0; @@ -329,9 +332,10 @@ TEST_F(GCodeExportTest, HeaderUltiGCode) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:UltiGCode\n;TIME:1337\n;MATERIAL:100\n;MATERIAL2:200\n;NOZZLE_DIAMETER:0.4\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;" - "MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:UltiGCode\n;TIME:1337\n;MATERIAL:100\n;MATERIAL2:200\n;NOZZLE_DIAMETER:0.4\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;" + "MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderRepRap) @@ -348,9 +352,10 @@ TEST_F(GCodeExportTest, HeaderRepRap) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:RepRap\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:RepRap\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderMarlin) @@ -367,9 +372,10 @@ TEST_F(GCodeExportTest, HeaderMarlin) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:Marlin\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:Marlin\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderMarlinVolumetric) @@ -384,9 +390,10 @@ TEST_F(GCodeExportTest, HeaderMarlinVolumetric) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:Marlin(Volumetric)\n;TIME:1337\n;Filament used: 100mm3, 200mm3\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:Marlin(Volumetric)\n;TIME:1337\n;Filament used: 100mm3, 200mm3\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } /* @@ -406,8 +413,9 @@ TEST_F(GCodeExportTest, EVsMmVolumetric) "area of the filament to convert the volume to a length."; constexpr double mm_input = 33.0; - EXPECT_EQ(gcode.mmToE(mm_input), mm_input * filament_area) << "Since the input mm is linear but the E output must be volumetric, we need to multiply by the cross-sectional area to convert " - "length to volume."; + EXPECT_EQ(gcode.mmToE(mm_input), mm_input * filament_area) + << "Since the input mm is linear but the E output must be volumetric, we need to multiply by the cross-sectional area to convert " + "length to volume."; constexpr double e_input = 100.0; EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input) << "Since the E is volumetric and mm3 is also volumetric, the output needs to be the same."; @@ -432,8 +440,9 @@ TEST_F(GCodeExportTest, EVsMmLinear) } constexpr double mm3_input = 33.0; - EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input / filament_area) << "Since the input mm3 is volumetric but the E output must be linear, we need to divide by the cross-sectional area to convert " - "volume to length."; + EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input / filament_area) + << "Since the input mm3 is volumetric but the E output must be linear, we need to divide by the cross-sectional area to convert " + "volume to length."; constexpr double e_input = 100.0; EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input * filament_area) << "Since the input E is linear but the output must be volumetric, we " @@ -493,7 +502,7 @@ TEST_F(GCodeExportTest, WriteZHopStartCustomSpeed) Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); // 60mm/min. gcode.current_layer_z = 2000; constexpr coord_t hop_height = 3000; - constexpr Velocity speed = 4; // 240 mm/min. + constexpr Velocity speed{ 4.0 }; // 240 mm/min. gcode.writeZhopStart(hop_height, speed); EXPECT_EQ(std::string("G1 F240 Z5\n"), output.str()) << "Custom provided speed should be used."; } @@ -521,7 +530,7 @@ TEST_F(GCodeExportTest, WriteZHopEndCustomSpeed) Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); gcode.current_layer_z = 2000; gcode.is_z_hopped = 3000; - constexpr Velocity speed = 4; // 240 mm/min. + constexpr Velocity speed{ 4.0 }; // 240 mm/min. gcode.writeZhopEnd(speed); EXPECT_EQ(std::string("G1 F240 Z2\n"), output.str()) << "Custom provided speed should be used."; } @@ -539,7 +548,7 @@ TEST_F(GCodeExportTest, insertWipeScriptSingleMove) config.brush_pos_x = 2000; config.repeat_count = 1; config.move_distance = 500; - config.move_speed = 10; + config.move_speed = 10.0; config.pause = 0; EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3); @@ -571,7 +580,7 @@ TEST_F(GCodeExportTest, insertWipeScriptMultipleMoves) config.brush_pos_x = 2000; config.repeat_count = 4; config.move_distance = 500; - config.move_speed = 10; + config.move_speed = 10.0; config.pause = 0; EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(6); @@ -609,7 +618,7 @@ TEST_F(GCodeExportTest, insertWipeScriptOptionalDelay) config.brush_pos_x = 2000; config.repeat_count = 1; config.move_distance = 500; - config.move_speed = 10; + config.move_speed = 10.0; config.pause = 1.5; // 1.5 sec = 1500 ms. EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3); @@ -637,7 +646,7 @@ TEST_F(GCodeExportTest, insertWipeScriptRetractionEnable) gcode.current_extruder = 0; gcode.extruder_attr[0].filament_area = 10.0; gcode.relative_extrusion = false; - gcode.currentSpeed = 1; + gcode.currentSpeed = 1.0; Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2"); Application::getInstance().current_slice->scene.extruders.emplace_back(0, &Application::getInstance().current_slice->scene.current_mesh_group->settings); Application::getInstance().current_slice->scene.extruders.back().settings.add("machine_firmware_retract", "false"); @@ -645,8 +654,8 @@ TEST_F(GCodeExportTest, insertWipeScriptRetractionEnable) WipeScriptConfig config; config.retraction_enable = true; config.retraction_config.distance = 1; - config.retraction_config.speed = 2; // 120 mm/min. - config.retraction_config.primeSpeed = 3; // 180 mm/min. + config.retraction_config.speed = 2.0; // 120 mm/min. + config.retraction_config.primeSpeed = 3.0; // 180 mm/min. config.retraction_config.prime_volume = gcode.extruder_attr[0].filament_area * 4; // 4mm in linear dimensions config.retraction_config.retraction_count_max = 100; // Practically no limit. config.retraction_config.retraction_extrusion_window = 1; @@ -655,7 +664,7 @@ TEST_F(GCodeExportTest, insertWipeScriptRetractionEnable) config.brush_pos_x = 2000; config.repeat_count = 1; config.move_distance = 500; - config.move_speed = 10; + config.move_speed = 10.0; config.pause = 0; EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3); @@ -680,18 +689,18 @@ TEST_F(GCodeExportTest, insertWipeScriptHopEnable) gcode.currentPosition = Point3(1000, 1000, 1000); gcode.current_layer_z = 1000; gcode.use_extruder_offset_to_offset_coords = false; - gcode.currentSpeed = 1; + gcode.currentSpeed = 1.0; Application::getInstance().current_slice->scene.current_mesh_group->settings.add("layer_height", "0.2"); WipeScriptConfig config; config.retraction_enable = false; config.hop_enable = true; - config.hop_speed = 2; // 120 mm/min. + config.hop_speed = 2.0; // 120 mm/min. config.hop_amount = 300; config.brush_pos_x = 2000; config.repeat_count = 1; config.move_distance = 500; - config.move_speed = 10; + config.move_speed = 10.0; config.pause = 0; EXPECT_CALL(*mock_communication, sendLineTo(testing::_, testing::_, testing::_, testing::_, testing::_)).Times(3); @@ -711,4 +720,4 @@ TEST_F(GCodeExportTest, insertWipeScriptHopEnable) EXPECT_EQ(std::string(";WIPE_SCRIPT_END"), token) << "Wipe script should always end with tag."; } } // namespace cura -// NOLINTEND(*-magic-numbers) \ No newline at end of file +// NOLINTEND(*-magic-numbers) diff --git a/tests/InfillTest.cpp b/tests/InfillTest.cpp index 1560d1a2f2..8d95705934 100644 --- a/tests/InfillTest.cpp +++ b/tests/InfillTest.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -20,16 +21,6 @@ // NOLINTBEGIN(*-magic-numbers) namespace cura { -template -std::string makeName(const std::string& format_string, Ts... args) -{ - // FIXME: once we use spdlog, we can use fmt::format instead, see CURA-8258 - constexpr int buff_size = 1024; - char buff[buff_size]; - std::snprintf(buff, buff_size, format_string.c_str(), args...); - return std::string(buff); -} - coord_t getPatternMultiplier(const EFillMethod& pattern) { switch (pattern) @@ -66,8 +57,7 @@ struct InfillParameters , connect_polygons(connect_polygons) , line_distance(line_distance) { - // FIXME: Once we are using spdlog as logger, we'll also use fmt::format() here, see CURA-8258. - name = makeName("InfillParameters_%d_%d_%d_%lld", static_cast(pattern), static_cast(zig_zagify), static_cast(connect_polygons), line_distance); + name = fmt::format("InfillParameters_{:d}_{:d}_{:d}_{:d}", static_cast(pattern), zig_zagify, connect_polygons, line_distance); } }; @@ -99,8 +89,7 @@ class InfillTestParameters , result_lines(std::move(result_lines)) , result_polygons(std::move(result_polygons)) { - // FIXME: Once we are using spdlog as logger, we'll also use fmt::format() here, see CURA-8258. - name = makeName("InfillTestParameters_P%d_Z%d_C%d_L%lld__%lld", static_cast(params.pattern), static_cast(params.zig_zagify), static_cast(params.connect_polygons), params.line_distance, test_polygon_id); + name = fmt::format("InfillTestParameters_P{:d}_Z{:d}_C{:d}_L{:d}__{:d}", static_cast(params.pattern), params.zig_zagify, params.connect_polygons, params.line_distance, test_polygon_id); } friend std::ostream& operator<<(std::ostream& os, const InfillTestParameters& params) diff --git a/tests/LayerPlanTest.cpp b/tests/LayerPlanTest.cpp index b583005053..e7428f421c 100644 --- a/tests/LayerPlanTest.cpp +++ b/tests/LayerPlanTest.cpp @@ -169,6 +169,7 @@ class LayerPlanTest : public testing::Test settings->add("support_roof_extruder_nr", "0"); settings->add("support_roof_line_width", "0.404"); settings->add("support_roof_material_flow", "104"); + settings->add("support_top_distance", "200"); settings->add("wall_line_count", "3"); settings->add("wall_line_width_x", "0.3"); settings->add("wall_line_width_0", "0.301"); diff --git a/tests/TimeEstimateCalculatorTest.cpp b/tests/TimeEstimateCalculatorTest.cpp index 68a88f3b75..e5ce2a6b1d 100644 --- a/tests/TimeEstimateCalculatorTest.cpp +++ b/tests/TimeEstimateCalculatorTest.cpp @@ -1,5 +1,5 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "PrintFeature.h" //We get time estimates per print feature. #include "settings/Settings.h" //To set firmware settings. @@ -144,7 +144,7 @@ TEST_F(TimeEstimateCalculatorTest, MoveToCurrentLocation) Duration estimate = std::accumulate(result.begin(), result.end(), Duration(0.0)); EXPECT_NEAR(Duration(0.0), estimate, EPSILON) << "setPosition should not add any time to the estimate."; - calculator.plan(position, Velocity(10), PrintFeatureType::Infill); + calculator.plan(position, Velocity { 10.0 }, PrintFeatureType::Infill); result = calculator.calculate(); estimate = std::accumulate(result.begin(), result.end(), Duration(0.0)); diff --git a/tests/WallsComputationTest.cpp b/tests/WallsComputationTest.cpp index 90c9748204..a45cb5464a 100644 --- a/tests/WallsComputationTest.cpp +++ b/tests/WallsComputationTest.cpp @@ -73,6 +73,7 @@ class WallsComputationTest : public testing::Test settings.add("magic_spiralize", "false"); settings.add("meshfix_maximum_deviation", "0.1"); settings.add("meshfix_maximum_extrusion_area_deviation", "0.01"); + settings.add("meshfix_fluid_motion_enabled", "false"); settings.add("meshfix_maximum_resolution", "0.01"); settings.add("min_wall_line_width", "0.3"); settings.add("min_bead_width", "0"); diff --git a/tests/arcus/ArcusCommunicationPrivateTest.cpp b/tests/arcus/ArcusCommunicationPrivateTest.cpp index 88a7b99b6d..b96dd840ff 100644 --- a/tests/arcus/ArcusCommunicationPrivateTest.cpp +++ b/tests/arcus/ArcusCommunicationPrivateTest.cpp @@ -225,7 +225,7 @@ TEST_F(ArcusCommunicationPrivateTest, ReadMeshGroupMessage) const size_t num_vertex = raw_vertices.size(); for (size_t i_coord = 0; i_coord < num_vertex; ++i_coord) { - auto micrometers = static_cast(raw_vertices[i_coord] * 1000.F); + auto micrometers = static_cast(std::llrintf(raw_vertices[i_coord] * 1000.F)); raw_min_coords[i_coord % 3] = std::min(micrometers, raw_min_coords[i_coord % 3]); raw_max_coords[i_coord % 3] = std::max(micrometers, raw_max_coords[i_coord % 3]); } diff --git a/tests/arcus/MockCommunication.h b/tests/arcus/MockCommunication.h index 563c98ef31..1db835ffb7 100644 --- a/tests/arcus/MockCommunication.h +++ b/tests/arcus/MockCommunication.h @@ -8,6 +8,7 @@ #include "utils/Coord_t.h" #include "utils/polygon.h" //In the signature of Communication. #include +#include "settings/types/LayerIndex.h" namespace cura { @@ -21,7 +22,7 @@ class MockCommunication : public Communication MOCK_CONST_METHOD0(hasSlice, bool()); MOCK_CONST_METHOD0(isSequential, bool()); MOCK_CONST_METHOD1(sendProgress, void(const float& progress)); - MOCK_METHOD3(sendLayerComplete, void(const LayerIndex& layer_nr, const coord_t& z, const coord_t& thickness)); + MOCK_METHOD3(sendLayerComplete, void(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness)); MOCK_METHOD5(sendPolygons, void(const PrintFeatureType& type, const Polygons& polygons, @@ -42,7 +43,7 @@ class MockCommunication : public Communication const Velocity& velocity)); MOCK_METHOD1(sendCurrentPosition, void(const Point& position)); MOCK_METHOD1(setExtruderForSend, void(const ExtruderTrain& extruder)); - MOCK_METHOD1(setLayerForSend, void(const LayerIndex& layer_nr)); + MOCK_METHOD1(setLayerForSend, void(const LayerIndex::value_type& layer_nr)); MOCK_METHOD0(sendOptimizedLayerData, void()); MOCK_CONST_METHOD0(sendPrintTimeMaterialEstimates, void()); MOCK_METHOD0(beginGCode, void()); diff --git a/tests/integration/SlicePhaseTest.cpp b/tests/integration/SlicePhaseTest.cpp index c90a96fabe..75d66899b8 100644 --- a/tests/integration/SlicePhaseTest.cpp +++ b/tests/integration/SlicePhaseTest.cpp @@ -1,6 +1,10 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher +#include + +#include + #include "Application.h" // To set up a slice with settings. #include "Slice.h" // To set up a scene to slice. #include "slicer.h" // Starts the slicing phase that we want to test. @@ -8,8 +12,6 @@ #include "utils/FMatrix4x3.h" // To load STL files. #include "utils/polygon.h" // Creating polygons to compare to sliced layers. #include "utils/polygonUtils.h" // Comparing similarity of polygons. -#include -#include namespace cura { @@ -26,6 +28,7 @@ class SlicePhaseTest : public testing::Test { // Start the thread pool Application::getInstance().startThreadPool(); + // Set up a scene so that we may request settings. Application::getInstance().current_slice = new Slice(1); @@ -34,6 +37,13 @@ class SlicePhaseTest : public testing::Test scene.settings.add("slicing_tolerance", "middle"); scene.settings.add("layer_height_0", "0.2"); scene.settings.add("layer_height", "0.1"); + scene.settings.add("layer_0_z_overlap", "0.0"); + scene.settings.add("raft_airgap", "0.0"); + scene.settings.add("raft_base_thickness", "0.2"); + scene.settings.add("raft_interface_thickness", "0.2"); + scene.settings.add("raft_interface_layers", "1"); + scene.settings.add("raft_surface_thickness", "0.2"); + scene.settings.add("raft_surface_layers", "1"); scene.settings.add("magic_mesh_surface_mode", "normal"); scene.settings.add("meshfix_extensive_stitching", "false"); scene.settings.add("meshfix_keep_open_polygons", "false"); @@ -41,6 +51,7 @@ class SlicePhaseTest : public testing::Test scene.settings.add("meshfix_maximum_resolution", "0.04"); scene.settings.add("meshfix_maximum_deviation", "0.02"); scene.settings.add("meshfix_maximum_extrusion_area_deviation", "2000"); + scene.settings.add("wall_transition_angle", "10"); scene.settings.add("xy_offset", "0"); scene.settings.add("xy_offset_layer_0", "0"); scene.settings.add("hole_xy_offset", "0"); @@ -49,6 +60,7 @@ class SlicePhaseTest : public testing::Test scene.settings.add("anti_overhang_mesh", "false"); scene.settings.add("cutting_mesh", "false"); scene.settings.add("infill_mesh", "false"); + scene.settings.add("adhesion_type", "none"); } }; @@ -119,7 +131,8 @@ TEST_F(SlicePhaseTest, Cylinder1000) const FMatrix4x3 transformation; // Path to cylinder1000.stl is relative to CMAKE_CURRENT_SOURCE_DIR/tests. - ASSERT_TRUE(loadMeshIntoMeshGroup(&mesh_group, std::filesystem::path(__FILE__).parent_path().append("resources/cylinder1000.stl").string().c_str(), transformation, scene.settings)); + ASSERT_TRUE( + loadMeshIntoMeshGroup(&mesh_group, std::filesystem::path(__FILE__).parent_path().append("resources/cylinder1000.stl").string().c_str(), transformation, scene.settings)); EXPECT_EQ(mesh_group.meshes.size(), 1); Mesh& cylinder_mesh = mesh_group.meshes[0]; @@ -160,4 +173,4 @@ TEST_F(SlicePhaseTest, Cylinder1000) } } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/tests/resources/slice_polygon_1.txt b/tests/resources/slice_polygon_1.txt new file mode 100644 index 0000000000..d07ee348a1 --- /dev/null +++ b/tests/resources/slice_polygon_1.txt @@ -0,0 +1,47 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +# + +A polygon from a real world example. This polygon is generated from tree support diff --git a/tests/resources/slice_polygon_2.txt b/tests/resources/slice_polygon_2.txt new file mode 100644 index 0000000000..a36c67170c --- /dev/null +++ b/tests/resources/slice_polygon_2.txt @@ -0,0 +1,117 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +# + +A polygon from a real world example. This polygon is generated from tree support diff --git a/tests/resources/slice_polygon_3.txt b/tests/resources/slice_polygon_3.txt new file mode 100644 index 0000000000..18d86abf5e --- /dev/null +++ b/tests/resources/slice_polygon_3.txt @@ -0,0 +1,233 @@ +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 diff --git a/tests/resources/slice_polygon_4.txt b/tests/resources/slice_polygon_4.txt new file mode 100644 index 0000000000..a6ce356288 --- /dev/null +++ b/tests/resources/slice_polygon_4.txt @@ -0,0 +1,5045 @@ +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 +x +v 127470 164447 +v 127806 164494 +v 128024 164722 +v 128075 165037 +v 127933 165382 +v 127468 165831 +v 127067 165975 +v 126530 165890 +v 126607 166299 +v 126462 166638 +v 126341 166754 +v 126682 167110 +v 126822 167452 +v 126776 167780 +v 126558 168001 +v 126227 168048 +v 125897 167909 +v 125594 167606 +v 125323 167879 +v 124886 168043 +v 124559 167994 +v 124316 167776 +v 124258 167468 +v 124401 167144 +v 124774 166786 +v 124586 166599 +v 124446 166266 +v 124497 165952 +v 124714 165719 +v 125045 165671 +v 125396 165812 +v 125532 165952 +v 125628 165856 +v 125980 165724 +v 126520 165797 +v 126442 165399 +v 126576 165081 +v 126726 164935 +v 126756 164822 +v 126986 164538 +v 127314 164412 +x +v 117979 155034 +v 118691 155162 +v 119350 155356 +v 119957 155618 +v 120564 155959 +v 121103 156355 +v 121609 156815 +v 122090 157380 +v 122445 157897 +v 122782 158541 +v 123021 159165 +v 123183 159797 +v 123284 160451 +v 123305 161000 +v 123305 161822 +v 123344 162220 +v 123457 162604 +v 123645 162964 +v 123891 163269 +v 124194 163522 +v 124540 163710 +v 124915 163825 +v 125305 163865 +v 147845 163861 +v 147845 166410 +v 79916 166411 +v 79356 166378 +v 78804 166282 +v 78265 166123 +v 77747 165904 +v 77256 165628 +v 76799 165297 +v 76381 164917 +v 72155 160597 +v 72155 156966 +v 79718 161223 +v 82189 162602 +v 82938 162982 +v 83715 163295 +v 84515 163541 +v 85333 163717 +v 86162 163825 +v 86997 163860 +v 109305 163865 +v 109695 163825 +v 110070 163710 +v 110416 163522 +v 110719 163269 +v 110970 162956 +v 111153 162604 +v 111267 162220 +v 111305 161823 +v 111305 161000 +v 111322 160543 +v 111385 160031 +v 111440 159736 +v 111578 159211 +v 111824 158543 +v 112171 157895 +v 112530 157368 +v 112970 156847 +v 113466 156389 +v 114045 155959 +v 114683 155600 +v 115327 155336 +v 115909 155165 +v 116608 155040 +v 117301 155000 +x +v 116798 157534 +v 116267 157654 +v 115775 157853 +v 115362 158089 +v 114935 158425 +v 114551 158841 +v 114282 159238 +v 114041 159725 +v 113883 160269 +v 113815 160744 +v 113815 161266 +v 113882 161746 +v 114048 162283 +v 114250 162707 +v 114554 163171 +v 114940 163584 +v 115371 163920 +v 115776 164148 +v 116295 164358 +v 116837 164472 +v 117305 164500 +v 117829 164466 +v 118339 164348 +v 118834 164149 +v 119279 163890 +v 119671 163585 +v 120057 163170 +v 120359 162708 +v 120561 162284 +v 120727 161747 +v 120794 161267 +v 120796 160745 +v 120721 160219 +v 120567 159719 +v 120329 159239 +v 120037 158807 +v 119677 158426 +v 119249 158090 +v 118837 157854 +v 118342 157654 +v 117813 157535 +v 117305 157500 +x +v 87502 90642 +v 87560 90841 +v 87699 90816 +v 88009 90915 +v 88376 90819 +v 88584 90852 +v 88648 90631 +v 88682 90595 +v 89550 90595 +v 89704 90718 +v 89827 90968 +v 89816 90968 +v 89758 91188 +v 89553 91335 +v 89251 91372 +v 89117 91332 +v 88963 91691 +v 88757 91854 +v 88794 92050 +v 88997 92113 +v 89094 92091 +v 89413 92113 +v 89986 92258 +v 90409 92644 +v 90519 92963 +v 90768 92950 +v 90866 92526 +v 91135 92253 +v 91195 91929 +v 90920 91688 +v 90770 91319 +v 90635 91357 +v 90352 91318 +v 90127 91161 +v 90065 90962 +v 90041 90963 +v 90169 90704 +v 90309 90595 +v 91204 90595 +v 91242 90634 +v 91302 90842 +v 91449 90817 +v 91756 90914 +v 92123 90820 +v 92332 90854 +v 92395 90631 +v 92430 90595 +v 93524 90595 +v 93576 90759 +v 93583 90759 +v 93461 91010 +v 93229 91184 +v 92889 91274 +v 92711 91690 +v 92409 91930 +v 92513 92055 +v 92652 92391 +v 93224 92227 +v 93787 92346 +v 94184 92718 +v 94327 93215 +v 94143 93731 +v 93894 93953 +v 94057 94459 +v 95000 93476 +v 94786 93272 +v 94615 92820 +v 94717 92381 +v 94893 92203 +v 94937 91969 +v 94623 91651 +v 94566 91294 +v 94204 91159 +v 93956 90960 +v 93834 90720 +v 93862 90595 +v 94938 90595 +v 94996 90663 +v 95062 90595 +v 96022 90595 +v 96126 90689 +v 96200 90595 +v 97045 90595 +v 97199 90718 +v 97322 90968 +v 97311 90968 +v 97253 91188 +v 97048 91335 +v 96746 91372 +v 96560 91318 +v 96511 91662 +v 96196 91979 +v 96308 92109 +v 96443 92526 +v 97010 92365 +v 97578 92484 +v 97939 92820 +v 98093 92422 +v 98172 92350 +v 98241 92157 +v 98558 91811 +v 98415 91688 +v 98266 91319 +v 98130 91357 +v 97847 91318 +v 97622 91161 +v 97560 90962 +v 97536 90963 +v 97664 90704 +v 97804 90595 +v 98699 90595 +v 98737 90634 +v 98797 90842 +v 98944 90817 +v 99252 90915 +v 99619 90819 +v 99827 90852 +v 99891 90631 +v 99926 90595 +v 101020 90595 +v 101071 90757 +v 101079 90757 +v 100958 91010 +v 100719 91187 +v 100384 91275 +v 100206 91692 +v 99851 91972 +v 100093 92200 +v 100207 92631 +v 100180 92706 +v 100329 92854 +v 100987 92614 +v 101511 92737 +v 101866 93141 +v 101978 93669 +v 101792 94240 +v 101545 94541 +v 101630 94942 +v 101563 95229 +v 101475 95547 +v 101634 96158 +v 101229 97596 +v 100444 98425 +v 99369 98762 +v 98344 98463 +v 97545 97615 +v 97257 96503 +v 97072 96453 +v 96681 96842 +v 95693 97154 +v 94771 96920 +v 94068 96221 +v 93794 95154 +v 93273 95601 +v 93187 95623 +v 93005 95943 +v 92269 96703 +v 91311 97012 +v 90412 96772 +v 89715 96051 +v 89482 95123 +v 89570 94841 +v 89005 94952 +v 88957 94936 +v 88540 95062 +v 87813 94885 +v 87441 94530 +v 87016 94665 +v 86658 94585 +v 86538 94799 +v 87488 95788 +v 87812 96787 +v 87559 97809 +v 86839 98526 +v 85816 98780 +v 84782 98446 +v 84039 98685 +v 83252 98491 +v 82654 97897 +v 82459 97114 +v 82726 96318 +v 82902 96134 +v 82922 96080 +v 83274 95733 +v 83316 95549 +v 83684 95128 +v 83347 95048 +v 83010 94773 +v 82958 94629 +v 82896 94578 +v 82739 94148 +v 82874 93728 +v 83234 93405 +v 83739 93294 +v 83981 93340 +v 84235 93364 +v 84245 93089 +v 84336 92724 +v 84104 92655 +v 83751 92327 +v 84069 92036 +v 84183 91898 +v 84584 91530 +v 84674 91555 +v 84820 91708 +v 85168 91841 +v 85391 92017 +v 85638 92261 +v 85749 92561 +v 85676 92869 +v 85883 93095 +v 86499 92486 +v 86587 92458 +v 86657 92382 +v 87407 92158 +v 87454 91935 +v 87173 91688 +v 87059 91413 +v 86896 91480 +v 86634 91452 +v 86444 91284 +v 86408 91042 +v 86531 90786 +v 86693 90633 +v 86716 90630 +v 87020 90595 +v 87457 90595 +x +v 85773 94292 +v 85528 94548 +v 86309 94777 +v 86438 94550 +v 85894 94043 + +# Big polygon \ No newline at end of file diff --git a/tests/settings/SettingsTest.cpp b/tests/settings/SettingsTest.cpp index a59acbda1b..4ef0a12e10 100644 --- a/tests/settings/SettingsTest.cpp +++ b/tests/settings/SettingsTest.cpp @@ -1,10 +1,12 @@ -// Copyright (c) 2022 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher #include "settings/Settings.h" //The class under test. + #include "Application.h" //To test extruder train settings. #include "ExtruderTrain.h" #include "Slice.h" +#include "settings/EnumSettings.h" #include "settings/FlowTempGraph.h" #include "settings/types/Angle.h" #include "settings/types/Duration.h" @@ -14,6 +16,7 @@ #include "settings/types/Velocity.h" #include "utils/Coord_t.h" #include "utils/FMatrix4x3.h" //Testing matrix transformation settings. + #include //For M_PI. #include #include //For shared_ptr. @@ -139,25 +142,25 @@ TEST_F(SettingsTest, AddSettingTemperature) TEST_F(SettingsTest, AddSettingVelocity) { settings.add("test_setting", "12.345"); - EXPECT_DOUBLE_EQ(Velocity(12.345), settings.get("test_setting")); + EXPECT_DOUBLE_EQ(Velocity { 12.345 }, settings.get("test_setting")); settings.add("test_setting", "-78"); - EXPECT_DOUBLE_EQ(Velocity(-78), settings.get("test_setting")); + EXPECT_DOUBLE_EQ(Velocity{ -78.0 }, settings.get("test_setting")); } TEST_F(SettingsTest, AddSettingRatio) { settings.add("test_setting", "1.618"); - EXPECT_DOUBLE_EQ(Ratio(0.01618), settings.get("test_setting")) << "With ratios, the input is interpreted in percentages."; + EXPECT_DOUBLE_EQ(Ratio { 0.01618 }, settings.get("test_setting")) << "With ratios, the input is interpreted in percentages."; } TEST_F(SettingsTest, AddSettingDuration) { settings.add("test_setting", "1234.5678"); - EXPECT_DOUBLE_EQ(Duration(1234.5678), settings.get("test_setting")); + EXPECT_DOUBLE_EQ(Duration { 1234.5678 }, settings.get("test_setting")); settings.add("test_setting", "-1234.5678"); - EXPECT_DOUBLE_EQ(Duration(0), settings.get("test_setting")) << "Negative duration doesn't exist, so it gets rounded to 0."; + EXPECT_DOUBLE_EQ(Duration { 0 }, settings.get("test_setting")) << "Negative duration doesn't exist, so it gets rounded to 0."; } TEST_F(SettingsTest, AddSettingFlowTempGraph) @@ -250,5 +253,17 @@ TEST_F(SettingsTest, LimitToExtruder) EXPECT_EQ(limit_extruder_value, settings.get("test_setting")); } +TEST_F(SettingsTest, PluginExtendedEnum) +{ + settings.add("infill_type", "PLUGIN::plugin_1::MOZAIC"); + EXPECT_EQ(settings.get("infill_type"), EFillMethod::PLUGIN); +} + +TEST_F(SettingsTest, EnumStringSwitch) +{ + settings.add("infill_type", "lightning"); + EXPECT_EQ(settings.get("infill_type"), EFillMethod::LIGHTNING); +} + } // namespace cura // NOLINTEND(*-magic-numbers) diff --git a/tests/test_global_settings.txt b/tests/test_global_settings.txt index 684e94a160..cb0c373c6a 100644 --- a/tests/test_global_settings.txt +++ b/tests/test_global_settings.txt @@ -478,4 +478,8 @@ raft_interface_thickness=0.30000000000000004 retraction_hop_only_when_collides=True ironing_flow=10.0 material_shrinkage_percentage_z=100 -material_shrinkage_percentage_xy=100 \ No newline at end of file +material_shrinkage_percentage_xy=100 +meshfix_fluid_motion_enabled=True +meshfix_fluid_motion_shift_distance=0.03 +meshfix_fluid_motion_small_distance=0.001 +meshfix_fluid_motion_angle=5 diff --git a/tests/utils/LinearAlg2DTest.cpp b/tests/utils/LinearAlg2DTest.cpp index 7f8f857237..4b2e883b8a 100644 --- a/tests/utils/LinearAlg2DTest.cpp +++ b/tests/utils/LinearAlg2DTest.cpp @@ -297,5 +297,35 @@ INSTANTIATE_TEST_SUITE_P(RotateAroundInstantiation, RotateAroundParameters(Point(-67, 14), Point(50, 50), 12, Point(-57, -9)) // 12 degrees rotation. Actually ends up at [-57, -9.5]! )); +class Temp {}; + +TEST(Temp, LineDistTests) +{ + std::srand(987); + for (int z = 0; z < 100; ++z) + { + const Point p{ 500000 + (std::rand() % 4000) - 2000, 500000 + (std::rand() % 4000) - 2000 }; + + const coord_t d = (std::rand() % 2000) - 1000 /2; + const double rang = std::rand() / (static_cast(RAND_MAX) / 6.29); + const Point x{ p.X + static_cast(d * std::cos(rang)), p.Y - static_cast(d * std::sin(rang)) }; + + // Use positive lengths here, so line and line-segment should give the same answers. + coord_t len = std::rand() % 1000; + const Point a{ x.X + static_cast(len * std::sin(rang)), x.Y + static_cast(len * std::cos(rang)) }; + len = std::rand() % 1000; + const Point b{ x.X - static_cast(len * std::sin(rang)), x.Y - static_cast(len * std::cos(rang)) }; + + const coord_t abs_d = std::abs(d); + ASSERT_NEAR(LinearAlg2D::getDistFromLine(p, a, b), abs_d, 5); + ASSERT_NEAR(vSize(LinearAlg2D::getClosestOnLine(p, a, b) - x), 0, 5); + ASSERT_NEAR(vSize(LinearAlg2D::getClosestOnLineSegment(p, a, b) - x), 0, 5); + ASSERT_NEAR(std::sqrt(LinearAlg2D::getDist2FromLine(p, a, b)), abs_d, 5); + ASSERT_NEAR(std::sqrt(LinearAlg2D::getDist2FromLineSegment(a, p, b)), abs_d, 5); + + ASSERT_NEAR(std::round(std::sqrt(LinearAlg2D::getDist2FromLine(p, a, b))), LinearAlg2D::getDistFromLine(p, a, b), 5); + } +} + } // namespace cura -// NOLINTEND(*-magic-numbers) \ No newline at end of file +// NOLINTEND(*-magic-numbers) diff --git a/tests/utils/SimplifyTest.cpp b/tests/utils/SimplifyTest.cpp index b65fed0f36..35d6689341 100644 --- a/tests/utils/SimplifyTest.cpp +++ b/tests/utils/SimplifyTest.cpp @@ -231,7 +231,7 @@ TEST_F(SimplifyTest, LimitedError) increasing_zigzag.add(Point(0, increasing_zigzag.size() * y_step)); } - size_t limit_vertex = 2 * simplifier.max_deviation / amplitude_step + 2; // 2 vertices per zag. Deviation/step zags. Add 2 since deviation equal to max is allowed. + size_t limit_vertex = 2 * simplifier.max_deviation / amplitude_step + 3; // 2 vertices per zag. Deviation/step zags. Add 3 since deviation equal to max +- epsilon is allowed. Polygon simplified = simplifier.polyline(increasing_zigzag); @@ -397,7 +397,7 @@ TEST_F(SimplifyTest, ToDegenerate) segment.add(Point(4, 0)); // Less than 5 micron long, so vertices would always be removed. segment = simplifier.polyline(segment); - EXPECT_EQ(segment.size(), 2) << "The segment did not get simplified because that would reduce its vertices to less than 2, making it degenerate."; + EXPECT_EQ(segment.size(), 0) << "The segment got removed entirely, because simplification would reduce its vertices to less than 2, making it degenerate."; } } // namespace cura diff --git a/tests/utils/SmoothTest.cpp b/tests/utils/SmoothTest.cpp new file mode 100644 index 0000000000..7efb08cbb2 --- /dev/null +++ b/tests/utils/SmoothTest.cpp @@ -0,0 +1,171 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#include + +#include + +#include + +#include "utils/IntPoint.h" +#include "utils/actions/smooth.h" +#include "utils/polygon.h" + +namespace cura +{ + +TEST(SmoothTest, TestSmooth) +{ + // test isSmooth utility function + cura::actions::smooth_fn smooth; + const auto FLUID_ANGLE = 15.; + const auto COS_FLUID_ANGLE = std::cos(FLUID_ANGLE * M_PI / 180.); + + { + /* + * + * A ------------- B + * | + * C --------------- D + * + */ + + auto A = cura::Point { 0, 0 }; + auto B = cura::Point { 0, 100 }; + auto C = cura::Point { 1, 100 }; + auto D = cura::Point { 1, 200 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, false); + } + + { + /* + * + * A ----------- B + * \ + * C + * | + * | + * | + * D + * + */ + auto A = cura::Point { 0, 0 }; + auto B = cura::Point { 100, 0 }; + auto C = cura::Point { 101, 1 }; + auto D = cura::Point { 101, 101 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, true); + } + + { + /* + * + * A ----------- B - C -------------D + * + */ + auto A = cura::Point { 0, 0 }; + auto B = cura::Point { 100, 0 }; + auto C = cura::Point { 101, 0 }; + auto D = cura::Point { 201, 0 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, true); + } + + { + /* + * + * D ----------- C - B -------------A + * + */ + auto A = cura::Point { 201, 0 }; + auto B = cura::Point { 101, 0 }; + auto C = cura::Point { 100, 0 }; + auto D = cura::Point { 0, 0 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, true); + } + + { + /* + * + * + * C + * \ + * A ----------- B + * \ + * \ + * \ + * \ + * \ + * D + * + */ + auto A = cura::Point { 0, 0 }; + auto B = cura::Point { 100, 0 }; + auto C = cura::Point { 99, -1 }; + auto D = cura::Point { 199, 99 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, false); + } + + { + /* + * + * D ----------- C + * \ + * B + * \ + * \ + * \ + * \ + * D + * + */ + auto A = cura::Point { 0, 0 }; + auto B = cura::Point { 100, 0 }; + auto C = cura::Point { 101, 1 }; + auto D = cura::Point { 201, 101 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, true); + } + + { + /* + * + * A ----------- B - C + * | + * | + * | + * | + * D + * + */ + cura::Point A = { 0, 0 }; + cura::Point B = { 100, 0 }; + cura::Point C = { 101, 0 }; + cura::Point D = { 101, 100 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, true); + } + + { + // real life example of a line that is clearly not smooth + auto A = cura::Point{ 148451, 162177 }; + auto B = cura::Point{ 148854, 162229 }; + auto C = cura::Point{ 148866, 162244 }; + auto D = cura::Point{ 149772, 162297 }; + + const auto is_smooth = smooth.isSmooth(A, B, C, D, COS_FLUID_ANGLE); + EXPECT_EQ(is_smooth, false); + }; + +} +} // namespace cura \ No newline at end of file