diff --git a/.clang-format b/.clang-format index 4196ee4bd5..5d90caa166 100644 --- a/.clang-format +++ b/.clang-format @@ -59,7 +59,7 @@ ForEachMacros: IncludeBlocks: Regroup IncludeCategories: - Priority: 2 - Regex: ^<(scripta|spdlog|range|fmt|Arcus)/ + Regex: ^<(scripta|spdlog|range|fmt|Arcus|agrpc|grpc|boost)/ - Priority: 3 Regex: ^(<|"(gtest|gmock|isl|json)/) - Priority: 1 diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 316a9e513f..e3b80ad8ce 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -76,7 +76,7 @@ jobs: - name: Setup Python and pip uses: actions/setup-python@v4 with: - python-version: '3.10.x' + python-version: '3.11.x' architecture: 'x64' cache: 'pip' cache-dependency-path: .github/workflows/requirements-conan-package.txt @@ -103,16 +103,19 @@ jobs: 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 + - name: Install GCC-132 on ubuntu 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 + 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 profile new default --detect + 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' }} @@ -161,7 +164,7 @@ jobs: tool: 'googlecpp' github-token: ${{ secrets.CURA_BENCHMARK_PAT }} auto-push: true - alert-threshold: '175%' -# summary-always: true -# comment-on-alert: 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 index 17ec5c436e..123aa31d1b 100644 --- a/.github/workflows/gcodeanalyzer.yml +++ b/.github/workflows/gcodeanalyzer.yml @@ -135,22 +135,25 @@ jobs: 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 + - name: Install GCC-132 on ubuntu 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 + 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 profile new default --detect + 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 + 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) @@ -303,7 +306,7 @@ jobs: 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 \ No newline at end of file + # alert-threshold: '110%' + # summary-always: true + # comment-on-alert: true + max-items-in-chart: 250 diff --git a/.github/workflows/lint-tidier.yml b/.github/workflows/lint-tidier.yml index 2504eeda4a..d178f52f3a 100644 --- a/.github/workflows/lint-tidier.yml +++ b/.github/workflows/lint-tidier.yml @@ -49,16 +49,19 @@ jobs: 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 + - name: Install GCC-132 on ubuntu 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 + 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 profile new default --detect 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 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9d8b9a0ed6..04e325df79 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -83,16 +83,19 @@ jobs: 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 + - name: Install GCC-132 on ubuntu 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 + 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 profile new default --detect + 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' }} @@ -108,7 +111,7 @@ jobs: key: conan-${{ 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 + 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 202fb5bf61..27fcdf5bd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,16 +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) -# Create Protobuf files if Arcus is used +# 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 () + 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 () @@ -30,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 @@ -97,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 @@ -147,9 +163,12 @@ 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> @@ -170,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) @@ -181,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) @@ -196,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) @@ -213,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) @@ -225,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/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 aca65ece35..7d4d5dc601 100644 --- a/conanfile.py +++ b/conanfile.py @@ -10,7 +10,7 @@ from conan.tools.build import check_min_cppstd from conan.tools.scm import Version -required_conan_version = ">=1.54 <=1.56.0 || >=1.58.0 <2.0.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.5.0-alpha" + 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,10 +106,15 @@ 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(): @@ -102,7 +123,7 @@ def generate(self): 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 self.options.enable_testing: + 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) @@ -115,7 +136,6 @@ def generate(self): 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 aa2264be8c..2d2e7c7162 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 @@ -7,6 +7,7 @@ #include "FanSpeedLayerTime.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" @@ -69,8 +70,6 @@ class FffGcodeWriter : public NoCopy std::vector> mesh_order_per_extruder; //!< For each extruder, the order of the meshes (first element is first mesh to be printed) - std::vector> extruder_prime_required_by_layer; //!< For each layer, indicates which extruders actually require to be primed - /*! * For each extruder on which layer the prime will be planned, * or a large negative number if it's already planned outside of \ref FffGcodeWriter::processLayer @@ -311,11 +310,7 @@ class FffGcodeWriter : public NoCopy * \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. @@ -325,7 +320,7 @@ class FffGcodeWriter : public NoCopy * \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. @@ -338,12 +333,8 @@ class FffGcodeWriter : public NoCopy * \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 SliceMeshStorage& mesh, 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. @@ -361,7 +352,7 @@ class FffGcodeWriter : public NoCopy const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part, LayerPlan& gcode_layer) const; @@ -381,7 +372,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; /*! @@ -401,7 +392,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; /*! @@ -419,7 +410,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; /*! @@ -437,7 +428,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcodeLayer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; /*! @@ -448,12 +439,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. @@ -471,7 +459,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const; /*! @@ -500,7 +488,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part) const; /*! @@ -519,7 +507,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const; @@ -540,7 +528,7 @@ class FffGcodeWriter : public NoCopy LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const; @@ -567,7 +555,7 @@ class FffGcodeWriter : public NoCopy const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, @@ -577,7 +565,8 @@ class FffGcodeWriter : public NoCopy const Ratio skin_density, const bool monotonic, bool& added_something, - double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; + double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, + const bool is_bridge_skin = false) const; /*! * see if we can avoid printing a lines or zig zag style skin part in multiple segments by moving to diff --git a/include/GCodePathConfig.h b/include/GCodePathConfig.h index 39cac0852a..1ee2003195 100644 --- a/include/GCodePathConfig.h +++ b/include/GCodePathConfig.h @@ -1,11 +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 G_CODE_PATH_CONFIG_H #define G_CODE_PATH_CONFIG_H #include "PrintFeature.h" -#include "settings/types/LayerIndex.h" +#include "pathPlanning/SpeedDerivatives.h" #include "settings/types/Ratio.h" #include "settings/types/Velocity.h" #include "utils/Coord_t.h" @@ -16,99 +16,57 @@ namespace cura /*! * 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 + PrintFeatureType type{}; //!< name of the feature type + coord_t line_width{}; //!< width of the line extruded + coord_t layer_thickness{}; //!< current layer height in micron + Ratio flow{}; //!< extrusion flow modifier. + SpeedDerivatives speed_derivatives{}; //!< The speed settings (and acceleration and jerk) of the extruded line. May be changed when smoothSpeed is called. + bool is_bridge_path{ false }; //!< whether current config is used when bridging + double fan_speed{ FAN_SPEED_DEFAULT }; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise + double extrusion_mm3_per_mm{ calculateExtrusion() }; //!< current mm^3 filament moved per mm line traversed static constexpr double FAN_SPEED_DEFAULT = -1; -private: - SpeedDerivatives speed_derivatives; //!< The speed settings (and acceleration and jerk) of the extruded line. May be changed when smoothSpeed is called. - const coord_t line_width; //!< width of the line extruded - const coord_t layer_thickness; //!< current layer height in micron - const Ratio flow; //!< extrusion flow modifier. - const double extrusion_mm3_per_mm; //!< current mm^3 filament moved per mm line traversed - const bool is_bridge_path; //!< whether current config is used when bridging - const double fan_speed; //!< fan speed override for this path, value should be within range 0-100 (inclusive) and ignored otherwise -public: - GCodePathConfig( - const PrintFeatureType& type, - const coord_t line_width, - const coord_t layer_height, - const Ratio& flow, - const SpeedDerivatives speed_derivatives, - const bool is_bridge_path = false, - const double fan_speed = FAN_SPEED_DEFAULT); - - /*! - * copy constructor - */ - GCodePathConfig(const GCodePathConfig& other); - - /*! - * 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(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; }; diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 64da17faa1..3eda752178 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -4,14 +4,7 @@ #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 "InsetOrderOptimizer.h" #include "PathOrderOptimizer.h" @@ -25,193 +18,23 @@ #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. @@ -226,7 +49,9 @@ class LayerPlanBuffer; // forward declaration to prevent circular dependency 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 @@ -245,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 @@ -413,7 +238,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. diff --git a/include/LayerPlanBuffer.h b/include/LayerPlanBuffer.h index c2e8aee899..db3d47ffa8 100644 --- a/include/LayerPlanBuffer.h +++ b/include/LayerPlanBuffer.h @@ -1,21 +1,24 @@ -// 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 "ExtruderPlan.h" +#include "LayerPlan.h" #include "Preheat.h" +#include "gcodeExport.h" #include "settings/Settings.h" #include "settings/types/Duration.h" #include +#include namespace cura { -class ExtruderPlan; + class GCodeExport; -class LayerPlan; /*! * 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. @@ -32,6 +35,8 @@ class LayerPlan; */ class LayerPlanBuffer { + friend class LayerPlan; + friend class LayerPlanBuffer; GCodeExport& gcode; Preheat preheat_config; //!< the nozzle and material temperature settings for each extruder train. @@ -207,7 +212,6 @@ class LayerPlanBuffer void handleStandbyTemp(std::vector& extruder_plans, unsigned int extruder_plan_idx, double standby_temp); }; - } // namespace cura #endif // LAYER_PLAN_BUFFER_H diff --git a/include/TopSurface.h b/include/TopSurface.h index 49dba4e203..77505bfa94 100644 --- a/include/TopSurface.h +++ b/include/TopSurface.h @@ -1,19 +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 { diff --git a/include/TreeSupport.h b/include/TreeSupport.h index 9f99c62010..6ad2a5fac4 100644 --- a/include/TreeSupport.h +++ b/include/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; diff --git a/include/TreeSupportTipGenerator.h b/include/TreeSupportTipGenerator.h index a22417dc3c..13c9dc6a80 100644 --- a/include/TreeSupportTipGenerator.h +++ b/include/TreeSupportTipGenerator.h @@ -1,3 +1,6 @@ +// Copyright (c) 2023 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + #ifndef TREESUPPORTTIPGENERATOR_H #define TREESUPPORTTIPGENERATOR_H @@ -23,14 +26,6 @@ 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 * @@ -117,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; /*! @@ -199,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 @@ -293,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. diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index f8ed1ef211..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 @@ -99,7 +102,7 @@ class TreeSupportUtils bool roof, LayerIndex layer_idx, coord_t support_infill_distance, - SierpinskiFillProvider* cross_fill_provider, + std::shared_ptr cross_fill_provider, bool include_walls, bool generate_support_supporting = false) { diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 47e423fb65..c332ef0f52 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -90,6 +90,8 @@ 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 @@ -461,6 +463,11 @@ class GCodeExport : public NoCopy */ 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: diff --git a/include/infill.h b/include/infill.h index b3c915ff4c..7c6a342499 100644 --- a/include/infill.h +++ b/include/infill.h @@ -9,14 +9,18 @@ #include "settings/EnumSettings.h" //For infill types. #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; @@ -24,40 +28,117 @@ 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() 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, - const Polygons& in_outline, + Polygons in_outline, coord_t infill_line_width, coord_t line_distance, coord_t infill_overlap, @@ -67,47 +148,41 @@ class Infill 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) + 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); } /*! @@ -128,10 +203,11 @@ class Infill const Settings& settings, int layer_idx, SectionType section_type, - const SierpinskiFillProvider* cross_fill_provider = nullptr, - const LightningLayer* lightning_layer = nullptr, + 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()); + const Polygons& prevent_small_exposed_to_air = Polygons(), + const bool is_bridge_skin = false); /*! * Generate the wall toolpaths of an infill area. It will return the inner contour and set the inner-contour. @@ -143,6 +219,7 @@ class Infill * \param line_width [in] The optimum wall line width of the walls * \param infill_overlap [in] The overlap of the infill * \param settings [in] A settings storage to use for generating variable-width walls. + * \param is_bridge_skin [in] Setting to filter out the extra skin walls while bridging * \return The inner contour of the wall toolpaths */ static Polygons generateWallToolPaths( @@ -153,7 +230,8 @@ class Infill const coord_t infill_overlap, const Settings& settings, int layer_idx, - SectionType section_type); + SectionType section_type, + const bool is_bridge_skin = false); private: /*! @@ -164,8 +242,8 @@ class Infill Polygons& result_polygons, Polygons& result_lines, const Settings& settings, - const SierpinskiFillProvider* cross_fill_pattern = nullptr, - const LightningLayer* lightning_layer = nullptr, + const std::shared_ptr cross_fill_pattern = nullptr, + const std::shared_ptr lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr); /*! @@ -193,9 +271,11 @@ 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) @@ -206,6 +286,13 @@ class Infill */ 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. * @@ -223,11 +310,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. * @@ -245,6 +344,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. @@ -263,6 +367,20 @@ class Infill * \param other The line segment to compare this line segment with. */ 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); }; /*! @@ -284,7 +402,7 @@ class Infill * 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 @@ -468,6 +586,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. * @@ -478,6 +619,7 @@ class Infill */ void connectLines(Polygons& result_lines); }; +static_assert(concepts::semiregular, "Infill should be semiregular"); } // namespace cura 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 diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index e845fd1df4..8c3c34dcfe 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -1,105 +1,90 @@ -//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 "GCodePathConfig.h" +#include "SpaceFillType.h" #include "TimeMaterialEstimates.h" +#include "settings/types/Ratio.h" +#include "sliceDataStorage.h" +#include "utils/IntPoint.h" -namespace cura -{ +#include +#include -class GCodePathConfig; +namespace cura +{ /*! * 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..a1816c7754 --- /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 '{}', slot range '{}' incompatible with plugin slot version '{}'", + plugin_info.plugin_name, + plugin_info.plugin_version, + plugin_info.peer, + slot_info.slot_id, + slot_info.version_range, + plugin_info.slot_version)) + { + } + + 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..fadc807d77 --- /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; + 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_range; + 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..102e76833f --- /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 + * SlotVersionRng - plugin version range + * 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.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_range = SlotVersionRng.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..84ba7846c3 --- /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 Slot The plugin slot ID. + * @tparam Validator The type used for validating the plugin. + * @tparam Stub The process stub type. + * @tparam Prepare The prepare type. + * @tparam Request The gRPC convertible request type. + * @tparam Response 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..5b99ab52b0 --- /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, + "<=1.0.0", + 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, + "<=1.0.0", + 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, + "<=1.0.0", + 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..0d18c074a3 --- /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(slot_info.version_range); + auto slot_version = semver::from_string(plugin_info.slot_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/settings/EnumSettings.h b/include/settings/EnumSettings.h index b91548afee..1ea3e1e099 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,7 +241,8 @@ 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, }; /*! @@ -257,6 +272,6 @@ enum class PrimeTowerMethod OPTIMIZED_CONSISTENT, }; -} //Cura namespace. +} // namespace cura -#endif //ENUMSETTINGS_H +#endif // ENUMSETTINGS_H diff --git a/include/settings/MeshPathConfigs.h b/include/settings/MeshPathConfigs.h new file mode 100644 index 0000000000..43457fbc8c --- /dev/null +++ b/include/settings/MeshPathConfigs.h @@ -0,0 +1,34 @@ +// 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 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 150f1a9fcb..e7bd54773a 100644 --- a/include/settings/PathConfigStorage.h +++ b/include/settings/PathConfigStorage.h @@ -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 #ifndef SETTINGS_PATH_CONFIGS_H #define SETTINGS_PATH_CONFIGS_H #include "GCodePathConfig.h" +#include "pathPlanning/SpeedDerivatives.h" +#include "settings/MeshPathConfigs.h" #include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" @@ -37,25 +39,6 @@ class PathConfigStorage 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); - }; - GCodePathConfig raft_base_config; GCodePathConfig raft_interface_config; GCodePathConfig raft_surface_config; diff --git a/include/settings/Settings.h b/include/settings/Settings.h index e0af78044c..2327e41141 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; + private: /*! * Optionally, a parent setting container to ask for the value of a setting @@ -119,7 +124,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/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 cad566d070..32a391c24c 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.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 SLICE_DATA_STORAGE_H #define SLICE_DATA_STORAGE_H @@ -19,6 +19,7 @@ #include "utils/polygon.h" #include +#include #include // libArachne @@ -237,7 +238,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(); @@ -265,10 +266,10 @@ class SliceMeshStorage //!< 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. @@ -281,8 +282,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 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/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/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..319f5d750c 100644 --- a/include/utils/gettime.h +++ b/include/utils/gettime.h @@ -1,19 +1,20 @@ -//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 +#define WIN32_LEAN_AND_MEAN 1 +#include #else - #ifdef USE_CPU_TIME - #include +#ifdef USE_CPU_TIME +#include #endif -#include -#include #include +#include +#include #endif namespace cura @@ -23,23 +24,23 @@ 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 +#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; +#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 +#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 // USE_CPU_TIME #endif // __WIN32 } @@ -47,11 +48,12 @@ class TimeKeeper { private: double startTime; + public: TimeKeeper(); - + double restart(); }; -}//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/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/char_range_literal.h b/include/utils/types/char_range_literal.h index 3d6e34c2e4..44a1d0b599 100644 --- a/include/utils/types/char_range_literal.h +++ b/include/utils/types/char_range_literal.h @@ -12,7 +12,7 @@ namespace cura::utils template struct CharRangeLiteral { - constexpr CharRangeLiteral(const char (&str)[N]) + constexpr CharRangeLiteral(const char (&str)[N]) noexcept { std::copy_n(str, N, value); } diff --git a/include/utils/types/generic.h b/include/utils/types/generic.h index 78dc919ad0..7d53dcfe86 100644 --- a/include/utils/types/generic.h +++ b/include/utils/types/generic.h @@ -19,6 +19,14 @@ 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 @@ -43,10 +51,7 @@ concept integral = std::is_same_v; template -concept floating_point = - std::is_same_v || - std::is_same_v || - std::is_same_v; +concept floating_point = std::is_same_v || std::is_same_v || std::is_same_v; #else template concept integral = std::integral; 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/src/Application.cpp b/src/Application.cpp index 2209118a2b..0074940150 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -6,10 +6,13 @@ #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 @@ -27,6 +30,7 @@ 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(); @@ -38,7 +42,7 @@ Application::Application() if (auto spdlog_val = spdlog::details::os::getenv("CURAENGINE_LOG_LEVEL"); ! spdlog_val.empty()) { spdlog::cfg::helpers::load_levels(spdlog_val); - } + }; } Application::~Application() @@ -148,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"); @@ -250,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/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 69bd0dd081..63c1d1b58f 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -25,10 +25,9 @@ #include #include -#include //For generating a UUID. -#include //For generating a UUID. #include // numeric_limits #include +#include #include #include @@ -38,7 +37,7 @@ namespace cura FffGcodeWriter::FffGcodeWriter() : max_object_height(0) , layer_plan_buffer(gcode) - , slice_uuid(boost::uuids::to_string(boost::uuids::random_generator()())) + , 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. @@ -1024,7 +1023,7 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn 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]; + 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! @@ -1117,16 +1116,6 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan } 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); @@ -1468,11 +1457,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) { @@ -1506,7 +1492,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode( 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()]; @@ -1517,7 +1503,7 @@ void FffGcodeWriter::addMeshLayerToGCode( const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, LayerPlan& gcode_layer) const { if (gcode_layer.getLayerNr() > mesh.layer_nr_max_filled_layer) @@ -1537,7 +1523,7 @@ void FffGcodeWriter::addMeshLayerToGCode( return; } - gcode_layer.setMesh(&mesh); + gcode_layer.setMesh(std::make_shared(mesh)); 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. @@ -1575,7 +1561,7 @@ void FffGcodeWriter::addMeshPartToGCode( const SliceDataStorage& storage, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part, LayerPlan& gcode_layer) const { @@ -1617,7 +1603,7 @@ bool FffGcodeWriter::processInfill( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) @@ -1634,7 +1620,7 @@ bool FffGcodeWriter::processMultiLayerInfill( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) @@ -1690,10 +1676,10 @@ bool FffGcodeWriter::processMultiLayerInfill( 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, @@ -1771,7 +1757,7 @@ bool FffGcodeWriter::processSingleLayerInfill( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const { if (extruder_nr != mesh.settings.get("infill_extruder_nr").extruder_nr) @@ -1886,10 +1872,10 @@ bool FffGcodeWriter::processSingleLayerInfill( 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. @@ -2235,7 +2221,7 @@ bool FffGcodeWriter::partitionInfillBySkinAbove( void FffGcodeWriter::processSpiralizedWall( const SliceDataStorage& storage, LayerPlan& gcode_layer, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part, const SliceMeshStorage& mesh) const { @@ -2273,7 +2259,7 @@ bool FffGcodeWriter::processInsets( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const { bool added_something = false; @@ -2518,7 +2504,7 @@ bool FffGcodeWriter::processSkin( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SliceLayerPart& part) const { const size_t top_bottom_extruder_nr = mesh.settings.get("top_bottom_extruder_nr").extruder_nr; @@ -2553,7 +2539,7 @@ bool FffGcodeWriter::processSkinPart( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part) const { bool added_something = false; @@ -2572,7 +2558,7 @@ void FffGcodeWriter::processRoofing( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const { @@ -2613,7 +2599,7 @@ void FffGcodeWriter::processTopBottom( LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const SkinPart& skin_part, bool& added_something) const { @@ -2787,14 +2773,15 @@ void FffGcodeWriter::processTopBottom( skin_density, monotonic, added_something, - fan_speed); + fan_speed, + is_bridge_skin); } void FffGcodeWriter::processSkinPrintFeature( const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, - const PathConfigStorage::MeshPathConfigs& mesh_config, + const MeshPathConfigs& mesh_config, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, @@ -2804,7 +2791,8 @@ void FffGcodeWriter::processSkinPrintFeature( const Ratio skin_density, const bool monotonic, bool& added_something, - double fan_speed) const + double fan_speed, + const bool is_bridge_skin) const { Polygons skin_polygons; Polygons skin_lines; @@ -2864,7 +2852,8 @@ void FffGcodeWriter::processSkinPrintFeature( nullptr, nullptr, nullptr, - small_areas_on_surface ? Polygons() : exposed_to_air); + small_areas_on_surface ? Polygons() : exposed_to_air, + is_bridge_skin); // add paths if (! skin_polygons.empty() || ! skin_lines.empty() || ! skin_paths.empty()) diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 3e2a9952e1..8d3223fe6a 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -682,7 +682,7 @@ void FffPolygonGenerator::processDerivedWallsSkinInfill(SliceMeshStorage& mesh) 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.cross_fill_provider = std::make_shared( mesh.bounding_box, mesh.settings.get("infill_line_distance"), mesh.settings.get("infill_line_width"), @@ -695,7 +695,7 @@ 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")); + = std::make_shared(mesh.bounding_box, mesh.settings.get("infill_line_distance"), mesh.settings.get("infill_line_width")); } } @@ -703,7 +703,7 @@ 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 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 46f8c97e44..006296fc68 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -93,7 +93,7 @@ bool InsetOrderOptimizer::addToLayer() constexpr bool detect_loops = false; constexpr Polygons* combing_boundary = nullptr; - constexpr bool group_outer_walls = true; + constexpr bool group_outer_walls = false; // 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 diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2da2fc8a04..600869daac 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -11,14 +11,17 @@ #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" #include +#include #include #include @@ -32,71 +35,6 @@ 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, @@ -107,12 +45,19 @@ GCodePath* LayerPlan::getLatestPathWithConfig( 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 + 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 { return &paths.back(); } - paths.emplace_back(config, current_mesh, space_fill_type, flow, width_factor, spiralize, speed_factor); + paths.emplace_back(GCodePath{ .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; @@ -218,7 +163,7 @@ Polygons LayerPlan::computeCombBoundary(const CombBoundary boundary_type) { 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; } @@ -337,7 +282,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; } @@ -574,7 +519,7 @@ void LayerPlan::addExtrusionMove( 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; } @@ -718,7 +663,7 @@ void LayerPlan::addWallLine( // 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) @@ -1590,7 +1535,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; @@ -1611,7 +1556,7 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ 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); + return std::min(target_speed / (path.config.getSpeed() * path.speed_factor), 1.0); } }; if (minExtrudeTime >= total_extrude_time_at_minimum_speed) @@ -1646,8 +1591,8 @@ void ExtruderPlan::forceMinimalLayerTime(double minTime, double time_other_extr_ 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); + 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 @@ -1675,7 +1620,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) @@ -1689,7 +1634,7 @@ TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point starting_pos std::numeric_limits::max(), [](double value, const GCodePath& path) { - return path.isTravelPath() ? value : std::min(value, path.config->getSpeed().value * path.speed_factor); + 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) @@ -1742,9 +1687,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; } @@ -1886,8 +1831,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) 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); @@ -1895,18 +1839,59 @@ 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]; + + 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); + + // Since the time/material estimates _may_ have changed during the plugin modify step we recalculate it + extruder_plan.computeNaiveTimeEstimates(gcode.getPositionXY()); + 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 }); + 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; @@ -2001,7 +1986,7 @@ 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 (! path.retract && path.config.isTravelPath() && path.points.size() == 1 && path.points[0] == gcode.getPositionXY() && z == gcode.getPositionZ()) { // ignore travel moves to the current location to avoid needless change of acceleration/jerk continue; @@ -2011,7 +1996,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; } @@ -2019,11 +2004,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 { @@ -2037,20 +2022,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 { @@ -2064,7 +2049,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } else { - gcode.writeJerk(paths[next_extrusion_idx].config->getJerk()); + gcode.writeJerk(paths[next_extrusion_idx].config.getJerk()); } } } @@ -2084,10 +2069,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"); } @@ -2099,7 +2085,7 @@ 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; @@ -2112,7 +2098,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)) { @@ -2158,8 +2144,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]; } @@ -2195,8 +2181,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 @@ -2207,7 +2193,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 } @@ -2228,6 +2214,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); @@ -2285,20 +2288,20 @@ bool LayerPlan::writePathWithCoasting( } 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 + = 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...) @@ -2375,13 +2378,13 @@ bool LayerPlan::writePathWithCoasting( 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 @@ -2391,7 +2394,7 @@ bool LayerPlan::writePathWithCoasting( 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]; diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index 1ef34d1e1b..6551543bdc 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -1,17 +1,18 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include +#include "LayerPlanBuffer.h" #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" +#include + namespace cura { @@ -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 || 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/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index 5d6babcdd6..c391b0d4f6 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -1240,7 +1240,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); @@ -1797,7 +1797,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]) { diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index b0ddb5a41b..99677f9c09 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -31,8 +31,8 @@ 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")) + , 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 @@ -378,7 +378,7 @@ 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) { @@ -399,12 +399,9 @@ 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; } @@ -976,6 +973,11 @@ void TreeSupportTipGenerator::generateTips( { for (Polygons& remaining_overhang_part : remaining_overhang.splitIntoParts(false)) { + if (remaining_overhang_part.area() <= MM2_2INT(minimum_support_area)) + { + continue; + } + 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. diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 8750ce2798..cf17d69454 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -13,11 +13,14 @@ #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. @@ -320,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); } @@ -339,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; @@ -372,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); } @@ -513,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/gcodeExport.cpp b/src/gcodeExport.cpp index 0b51882f45..a9bf55f7c1 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -93,6 +93,18 @@ void GCodeExport::preSetup(const size_t start_extruder) 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"); @@ -110,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) @@ -305,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; @@ -394,8 +415,7 @@ 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 @@ -706,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; @@ -713,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"); @@ -728,20 +761,7 @@ 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")) { @@ -1039,8 +1059,7 @@ 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. @@ -1145,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) { @@ -1502,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) { @@ -1513,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 4f4fd757e4..6bdd545aaa 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -12,11 +12,13 @@ #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 @@ -58,13 +60,14 @@ Polygons Infill::generateWallToolPaths( const coord_t infill_overlap, const Settings& settings, int layer_idx, - SectionType section_type) + SectionType section_type, + const bool is_bridge_skin) { outer_contour = outer_contour.offset(infill_overlap); scripta::log("infill_outer_contour", outer_contour, section_type, layer_idx, scripta::CellVDI{ "infill_overlap", infill_overlap }); Polygons inner_contour; - if (wall_line_count > 0) + if ((wall_line_count > 0) && (! is_bridge_skin)) { 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(outer_contour, line_width, wall_line_count, wall_0_inset, settings, layer_idx, section_type); @@ -85,17 +88,18 @@ void Infill::generate( const Settings& settings, int layer_idx, SectionType section_type, - const SierpinskiFillProvider* cross_fill_provider, - const LightningLayer* lightning_trees, + const std::shared_ptr cross_fill_provider, + const std::shared_ptr lightning_trees, const SliceMeshStorage* mesh, - const Polygons& prevent_small_exposed_to_air) + const Polygons& prevent_small_exposed_to_air, + const bool is_bridge_skin) { if (outer_contour.empty()) { return; } - inner_contour = generateWallToolPaths(toolpaths, outer_contour, wall_line_count, infill_line_width, infill_overlap, settings, layer_idx, section_type); + inner_contour = generateWallToolPaths(toolpaths, outer_contour, wall_line_count, infill_line_width, infill_overlap, settings, layer_idx, section_type, is_bridge_skin); scripta::log("infill_inner_contour_0", inner_contour, section_type, layer_idx); // It does not make sense to print a pattern in a small region. So the infill region @@ -180,6 +184,7 @@ void Infill::generate( 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); @@ -191,7 +196,9 @@ void Infill::generate( // 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); } @@ -248,8 +255,8 @@ void Infill::_generate( Polygons& result_polygons, Polygons& result_lines, const Settings& settings, - const SierpinskiFillProvider* cross_fill_provider, - const LightningLayer* lightning_trees, + const std::shared_ptr cross_fill_provider, + const std::shared_ptr lightning_trees, const SliceMeshStorage* mesh) { if (inner_contour.empty()) @@ -310,6 +317,17 @@ void Infill::_generate( 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; @@ -317,7 +335,7 @@ void Infill::_generate( 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); @@ -414,7 +432,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) @@ -782,6 +800,56 @@ void Infill::generateLinearBasedInfill( } } +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. @@ -799,6 +867,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]; @@ -824,10 +893,10 @@ void Infill::connectLines(Polygons& result_lines) [&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; + 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); }); @@ -844,55 +913,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; @@ -903,49 +965,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; } } @@ -970,26 +1005,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; } @@ -1003,4 +1046,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 87478fa204..d81b92d0e0 100644 --- a/src/infill/SubDivCube.cpp +++ b/src/infill/SubDivCube.cpp @@ -23,17 +23,6 @@ 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"); @@ -91,7 +80,7 @@ void SubDivCube::precomputeOctree(SliceMeshStorage& mesh, const Point& infill_or 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) @@ -189,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++; } } 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..33e7d4882e --- /dev/null +++ b/src/plugins/converters.cpp @@ -0,0 +1,479 @@ +// 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_range(slot_info.version_range.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 = message.slot_version(), + .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_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(), + .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/settings/MeshPathConfigs.cpp b/src/settings/MeshPathConfigs.cpp new file mode 100644 index 0000000000..942084a231 --- /dev/null +++ b/src/settings/MeshPathConfigs.cpp @@ -0,0 +1,126 @@ +// 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") } } + , 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..2f9dd2acea 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,29 +91,36 @@ 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()); @@ -215,16 +130,19 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye } 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"); @@ -234,50 +152,53 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye } } -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,24 +206,19 @@ 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 @@ -310,16 +226,14 @@ void cura::PathConfigStorage::handleInitialLayerSpeedup(const SliceDataStorage& { 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 41c3ed1f31..bcfb079e0c 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -18,7 +18,10 @@ #include "utils/FMatrix4x3.h" #include "utils/polygon.h" #include "utils/string.h" //For Escaped. +#include "utils/types/string_switch.h" //For string switch. +#include +#include #include #include @@ -135,8 +138,8 @@ std::vector Settings::get>(const std 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<> @@ -191,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; } } @@ -356,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; } } @@ -463,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; } } @@ -485,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; } } @@ -503,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; } } @@ -522,21 +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; } } @@ -545,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; } } @@ -571,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; } } @@ -588,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; } } @@ -601,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; } } @@ -615,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; } } @@ -640,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; } } @@ -654,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; } } @@ -672,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; } } @@ -805,4 +801,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 c29fec0b63..227591a8c4 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -146,11 +146,12 @@ void SkinInfillAreaComputation::generateSkinAndInfillAreas(SliceLayerPart& part) 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()) @@ -306,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); } diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 6dfe67a5e2..788ac2e45a 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include +#include "sliceDataStorage.h" #include "Application.h" //To get settings. #include "ExtruderTrain.h" @@ -12,24 +12,24 @@ #include "infill/SierpinskiFillProvider.h" #include "infill/SubDivCube.h" // For the destructor #include "raft.h" -#include "sliceDataStorage.h" #include "utils/math.h" //For PI. +#include + 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())) @@ -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()) @@ -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")) { diff --git a/src/slicer.cpp b/src/slicer.cpp index 3462573d88..6b1ac7ea4d 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -5,6 +5,7 @@ #include "Application.h" #include "Slice.h" +#include "plugins/slots.h" #include "settings/AdaptiveLayerHeights.h" #include "settings/EnumSettings.h" #include "settings/types/LayerIndex.h" @@ -786,7 +787,12 @@ void SlicerLayer::makePolygons(const Mesh* mesh) 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 diff --git a/src/support.cpp b/src/support.cpp index 45f9901d77..1adb0b1ae7 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -749,7 +749,7 @@ 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( + storage.support.cross_fill_provider = std::make_shared( aabb, infill_extruder.settings.get("support_line_distance"), infill_extruder.settings.get("support_line_width"), @@ -761,8 +761,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")); } } } diff --git a/src/utils/LinearAlg2D.cpp b/src/utils/LinearAlg2D.cpp index 6de830d72b..ae972d6438 100644 --- a/src/utils/LinearAlg2D.cpp +++ b/src/utils/LinearAlg2D.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Ultimaker B.V. +// Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher. #include "utils/linearAlg2D.h" @@ -255,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 b260a74c3a..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/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 7eebc8dc43..1985a56a35 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1566,7 +1566,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); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aca5134fc9..1e36893cee 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,24 +40,6 @@ set(TESTS_SRC_UTILS UnionFindTest ) -set(TESTS_HELPERS_SRC 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 190381ada3..d0d2772979 100644 --- a/tests/ExtruderPlanTest.cpp +++ b/tests/ExtruderPlanTest.cpp @@ -2,6 +2,8 @@ // 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.0, 1000.0, 10.0)) - , travel_config(PrintFeatureType::MoveCombing, 0, 100, 0.0_r, GCodePathConfig::SpeedDerivatives(120.0, 5000.0, 30.0)) + : 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, width_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, width_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, width_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, width_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_08, width_1, no_spiralize, speed_1), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_04, width_1, 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, width_1, no_spiralize, speed_12), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_08), - GCodePath(travel_config, mesh, SpaceFillType::Lines, flow_1, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, flow_1, width_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, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.8_r, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.6_r, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.4_r, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.2_r, width_1, no_spiralize, speed_1), - GCodePath(extrusion_config, mesh, SpaceFillType::Lines, 0.0_r, width_1, 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/integration/SlicePhaseTest.cpp b/tests/integration/SlicePhaseTest.cpp index 2717341ba6..34e46dc809 100644 --- a/tests/integration/SlicePhaseTest.cpp +++ b/tests/integration/SlicePhaseTest.cpp @@ -26,6 +26,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); 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 e468965646..4ef0a12e10 100644 --- a/tests/settings/SettingsTest.cpp +++ b/tests/settings/SettingsTest.cpp @@ -2,9 +2,11 @@ // 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. @@ -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)