diff --git a/.github/workflows/npm-package.yml b/.github/workflows/npm-package.yml new file mode 100644 index 0000000000..239334ff4d --- /dev/null +++ b/.github/workflows/npm-package.yml @@ -0,0 +1,100 @@ +name: NPM package + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to build' + required: true + default: 'main' + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Sync pip requirements + run: curl -O https://raw.githubusercontent.com/Ultimaker/cura-workflows/main/.github/workflows/requirements-runner.txt + working-directory: .github/workflows + + - name: Setup Python and pip + uses: actions/setup-python@v4 + with: + python-version: 3.11.x + cache: pip + cache-dependency-path: .github/workflows/requirements-runner.txt + + - name: Install Python requirements and Create default Conan profile + run: pip install -r .github/workflows/requirements-runner.txt + + - name: Install Linux system requirements for building + run: | + mkdir runner_scripts + cd runner_scripts + curl -O https://raw.githubusercontent.com/Ultimaker/cura-workflows/main/runner_scripts/ubuntu_setup.sh + chmod +x ubuntu_setup.sh + sudo ./ubuntu_setup.sh + + - name: Setup pipeline caches + run: | + mkdir -p /home/runner/.conan/downloads + mkdir -p /home/runner/.conan/data + + - name: Create default Conan profile + run: conan profile new default --detect + + # FIXME: Once merged to main: conan config install https://github.com/Ultimaker/conan-config.git -a "-b runner/${{ runner.os }}/${{ runner.arch }}" + - name: Get Conan configuration + run: | + conan config install https://github.com/Ultimaker/conan-config.git + conan config install https://github.com/Ultimaker/conan-config.git -a "-b NP-419" + + - name: Add runner credentials to cura remote + run: conan user -p ${{ secrets.CONAN_PASS }} -r cura ${{ secrets.CONAN_USER }} + + - name: Cache Conan packages + uses: actions/cache@v3 + with: + path: /home/runner/.conan/data + key: ${{ runner.os }}-conan-data-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-conan-data- + + - name: Cache Conan downloads + uses: actions/cache@v3 + with: + path: /home/runner/.conan/downloads + key: ${{ runner.os }}-conan-downloads-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-conan-downloads- + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + registry-url: 'https://npm.pkg.github.com' + scope: '@ultimaker' + + - name: Set npm config + run: | + npm run install_curaengine + npm ci + npm publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload the Package(s) + if: ${{ always() }} + run: | + conan remove "cura_private_data/*" --force + conan remove "fdm_materials/*" --force + conan upload "*" -r cura --all -c \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b2e7abc41..6102c84c0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(engine_SRCS # Except main.cpp. src/Mold.cpp src/multiVolumes.cpp src/path_ordering.cpp + src/PathAdapter.cpp src/Preheat.cpp src/PrimeTower/PrimeTower.cpp src/PrimeTower/PrimeTowerNormal.cpp @@ -142,7 +143,6 @@ set(engine_SRCS # Except main.cpp. src/utils/ListPolyIt.cpp src/utils/Matrix4x3D.cpp src/utils/MinimumSpanningTree.cpp - src/utils/Point3LL.cpp src/utils/PolygonConnector.cpp src/utils/PolygonsPointIndex.cpp src/utils/PolygonsSegmentIndex.cpp @@ -157,6 +157,14 @@ set(engine_SRCS # Except main.cpp. src/utils/VoxelUtils.cpp src/utils/MixedPolylineStitcher.cpp + src/utils/scoring/BestElementFinder.cpp + src/utils/scoring/CornerScoringCriterion.cpp + src/utils/scoring/DistanceScoringCriterion.cpp + src/utils/scoring/ExclusionAreaScoringCriterion.cpp + src/utils/scoring/RandomScoringCriterion.cpp + + src/geometry/Point2LL.cpp + src/geometry/Point3LL.cpp src/geometry/Polygon.cpp src/geometry/Shape.cpp src/geometry/PointsSet.cpp @@ -188,6 +196,7 @@ target_compile_definitions(_CuraEngine $<$,$>:ENABLE_REMOTE_PLUGINS> $<$:OLDER_APPLE_CLANG> CURA_ENGINE_VERSION=\"${CURA_ENGINE_VERSION}\" + CURA_ENGINE_HASH=\"${CURA_ENGINE_HASH}\" $<$:BUILD_TESTS> PRIVATE $<$:NOMINMAX> diff --git a/CuraEngineJS/package-lock.json b/CuraEngineJS/package-lock.json new file mode 100644 index 0000000000..f304245dd9 --- /dev/null +++ b/CuraEngineJS/package-lock.json @@ -0,0 +1,3181 @@ +{ + "name": "@ultimaker/curaenginejs", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ultimaker/curaenginejs", + "version": "0.1.0", + "dependencies": { + "emscripten": "^0.0.2-beta", + "observable-fns": "^0.6.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@types/emscripten": "^1.39.10", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "eslint": "^8.55.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "typescript": "^5.2.2" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/emscripten": { + "version": "1.39.13", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", + "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emscripten": { + "version": "0.0.2-beta", + "resolved": "https://registry.npmjs.org/emscripten/-/emscripten-0.0.2-beta.tgz", + "integrity": "sha512-bckLzZ9n/GhNB4NwE23frASTc0hB7EyrY9ykys+Ou5wKupOROtEzkeWqxoGmWYQVNbidSW5UYsHwo8hhCuqbKg==", + "hasInstallScript": true, + "bin": { + "emscripten": "index.js" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/observable-fns": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.6.1.tgz", + "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "peer": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} \ No newline at end of file diff --git a/CuraEngineJS/package.json b/CuraEngineJS/package.json new file mode 100644 index 0000000000..2b50c6e9d9 --- /dev/null +++ b/CuraEngineJS/package.json @@ -0,0 +1,47 @@ +{ + "name": "@ultimaker/curaenginejs", + "version": "0.2.0", + "description": "CuraEngineJS a TS component to run CuraEngine in a browser", + "main": "src/CuraEngine.js", + "scripts": { + "install_curaengine": "conan install ${npm_package_config_conan_package} -s build_type=Release --build=missing --update -c tools.build:skip_test=True -pr:h cura_wasm.jinja -if src && rm -f src/*conan*", + "build": "npm run install_curaengine" + }, + "config": { + "conan_package": "curaengine/5.9.0@_/_" + }, + "repository": { + "type": "git", + "url": "https://github.com/Ultimaker/CuraEngine.git" + }, + "keywords": [ + "Cura", + "CuraEngine", + "Slicer" + ], + "author": "UltiMaker", + "license": "", + "bugs": { + "url": "https://github.com/Ultimaker/CuraEngine/issues" + }, + "homepage": "https://github.com/Ultimaker/CuraEngine#readme", + "dependencies": { + "emscripten": "^0.0.2-beta", + "observable-fns": "^0.6.1", + "uuid": "^9.0.1" + }, + "devDependencies": { + "@types/emscripten": "^1.39.10", + "@types/uuid": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "eslint": "^8.55.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "typescript": "^5.2.2" + }, + "files": [ + "dist", + "package.json", + "README.md" + ] +} \ No newline at end of file diff --git a/benchmark/scoring_benchmark.h b/benchmark/scoring_benchmark.h new file mode 100644 index 0000000000..b526c94302 --- /dev/null +++ b/benchmark/scoring_benchmark.h @@ -0,0 +1,73 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_SCORING_BENCHMARK_H +#define CURAENGINE_SCORING_BENCHMARK_H + +#include + +#include "geometry/PointsSet.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" + +namespace cura +{ +class ScoringTestFixture : public benchmark::Fixture +{ +public: + PointsSet points; + + void SetUp(const ::benchmark::State& state) override + { + points.resize(state.range(0)); + constexpr double radius = 5000.0; + + for (size_t i = 0; i < points.size(); ++i) + { + const double angle = (i * std::numbers::pi * 2.0) / points.size(); + points[i] = Point2LL(std::cos(angle) * radius, std::sin(angle) * radius); + } + } + + void TearDown(const ::benchmark::State& state) override + { + } +}; + +BENCHMARK_DEFINE_F(ScoringTestFixture, ScoringTest_WorstCase)(benchmark::State& st) +{ + for (auto _ : st) + { + BestElementFinder best_element_finder; + + // Pass 1 : find corners + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; + + BestElementFinder::WeighedCriterion main_criterion; + main_criterion.criterion = std::make_shared(points, EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED); + main_criteria_pass.criteria.push_back(main_criterion); + + best_element_finder.appendCriteriaPass(main_criteria_pass); + + // Pass 2 : fallback to distance calculation + BestElementFinder::CriteriaPass fallback_criteria_pass; + BestElementFinder::WeighedCriterion fallback_criterion; + fallback_criterion.criterion = std::make_shared(points, Point2LL(1000, 0)); + + fallback_criteria_pass.criteria.push_back(fallback_criterion); + best_element_finder.appendCriteriaPass(fallback_criteria_pass); + + best_element_finder.findBestElement(points.size()); + } +} + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(1000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10)->Unit(benchmark::kMillisecond); + +} // namespace cura +#endif // CURAENGINE_SCORING_BENCHMARK_H diff --git a/conandata.yml b/conandata.yml index 502ffbbdc9..5c50e10233 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,10 +1,10 @@ -version: "5.9.0-alpha.0" - +version: "5.10.0-alpha.0" +commit: "unknown" requirements: - "scripta/[>=1.1.0]@ultimaker/cura_11622" requirements_arcus: - - "arcus/[>=5.4.0]@ultimaker/cura_11622" + - "arcus/5.4.1]@ultimaker/cura_11622" requirements_plugins: - - "curaengine_grpc_definitions/[>=0.2.0]@ultimaker/cura_11622" + - "curaengine_grpc_definitions/0.3.0@ultimaker/cura_11622" requirements_cura_resources: - - "cura_resources/(latest)@ultimaker/testing" + - "cura_resources/[*]@ultimaker/testing" diff --git a/conanfile.py b/conanfile.py index 2b314e0115..2043a7998f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -134,6 +134,7 @@ def generate(self): tc = CMakeToolchain(self) tc.variables["CURA_ENGINE_VERSION"] = self.version + tc.variables["CURA_ENGINE_HASH"] = self.conan_data["commit"] tc.variables["ENABLE_ARCUS"] = self.options.enable_arcus tc.variables["ENABLE_TESTING"] = not self.conf.get("tools.build:skip_test", False, check_type=bool) tc.variables["ENABLE_BENCHMARKS"] = self.options.enable_benchmarks diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index cc6687b5b5..e4d301969a 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -124,6 +124,11 @@ class ExtruderPlan */ void applyBackPressureCompensation(const Ratio back_pressure_compensation); + /*! + * Gets the mesh being printed first on this plan + */ + std::shared_ptr findFirstPrintedMesh() const; + 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) @@ -191,7 +196,7 @@ class ExtruderPlan /*! * @return distance between p0 and p1 as well as the time spend on the segment */ - std::pair getPointToPointTime(const Point2LL& p0, const Point2LL& p1, const GCodePath& path); + std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const; /*! * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. diff --git a/include/GCodePathConfig.h b/include/GCodePathConfig.h index 82a2ea78dd..27672049df 100644 --- a/include/GCodePathConfig.h +++ b/include/GCodePathConfig.h @@ -20,7 +20,7 @@ struct GCodePathConfig { static constexpr double FAN_SPEED_DEFAULT = -1.0; - coord_t z_offset{}; //& paths, const Point2LL& model_center_point, - const Shape& disallowed_areas_for_seams = {}); + const Shape& disallowed_areas_for_seams = {}, + const bool scarf_seam = false, + const bool smooth_speed = false, + const Shape& overhang_areas = Shape()); /*! * Adds the insets to the given layer plan. @@ -110,6 +113,9 @@ class InsetOrderOptimizer const LayerIndex layer_nr_; const Point2LL model_center_point_; // Center of the model (= all meshes) axis-aligned bounding-box. Shape disallowed_areas_for_seams_; + const bool scarf_seam_; + const bool smooth_speed_; + Shape overhang_areas_; std::vector> inset_polys_; // vector of vectors holding the inset polygons Shape retraction_region_; // After printing an outer wall, move into this region so that retractions do not leave visible blobs. Calculated lazily if needed (see @@ -124,7 +130,7 @@ class InsetOrderOptimizer * * \param closed_line The polygon to insert the seam point in. (It's assumed to be closed at least.) * - * \return The index of the inserted seam point, or std::nullopt if no seam point was inserted. + * \return The index of the inserted seam point, or the index of the closest point if an existing one can be used. */ std::optional insertSeamPoint(ExtrusionLine& closed_line); diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 50195d7b36..47cea1aa19 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -38,6 +38,9 @@ class Comb; class SliceDataStorage; class LayerPlanBuffer; +template +class PathAdapter; + /*! * The LayerPlan class stores multiple moves that are planned. * @@ -63,6 +66,21 @@ class LayerPlan : public NoCopy bool mode_skip_agressive_merge_; //!< Whether to give every new path the 'skip_agressive_merge_hint' property (see GCodePath); default is false. private: + // Indicates how coasting should be processed on the given path. + enum class ApplyCoasting + { + NoCoasting, // Do not apply coasting on this path, extrude it normally + CoastEntirePath, // Fully coast this path, i.e. replace it by travel moves + PartialCoasting // Extrude the first part of the path and coast the end + }; + + struct PathCoasting + { + ApplyCoasting apply_coasting{ ApplyCoasting::NoCoasting }; + size_t coasting_start_index{ 0 }; + Point3LL coasting_start_pos; + }; + const SliceDataStorage& storage_; //!< The polygon data obtained from FffPolygonProcessor const LayerIndex layer_nr_; //!< The layer number of this layer plan const bool is_initial_layer_; //!< Whether this is the first layer (which might be raft) @@ -290,6 +308,11 @@ class LayerPlan : public NoCopy */ void setSeamOverhangMask(const Shape& polys); + /*! + * Get the seam overhang mask, which contains the areas where we don't want to place the seam because they are overhanding + */ + const Shape& getSeamOverhangMask() const; + /*! * Set roofing_mask. * @@ -360,14 +383,15 @@ class LayerPlan : public NoCopy * \param fan_speed Fan speed override for this path. */ void addExtrusionMove( - const Point2LL p, + const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, const Ratio& flow = 1.0_r, const Ratio width_factor = 1.0_r, const bool spiralize = false, const Ratio speed_factor = 1.0_r, - const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, + const bool travel_to_z = true); /*! * Add polygon to the gcode starting at vertex \p startIdx @@ -379,16 +403,21 @@ class LayerPlan : public NoCopy * \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon * \param flow_ratio The ratio with which to multiply the extrusion amount * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygon( const Polygon& polygon, int startIdx, const bool reverse, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio& flow_ratio = 1.0_r, - bool always_retract = false); + bool always_retract = false, + bool scarf_seam = false, + bool smooth_speed = false); /*! * Add polygons to the gcode with optimized order. @@ -417,17 +446,22 @@ class LayerPlan : public NoCopy * \param reverse_order Adds polygons in reverse order. * \param start_near_location Start optimising the path near this location. * If unset, this causes it to start near the last planned location. + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, - const std::optional start_near_location = std::optional()); + const std::optional start_near_location = std::optional(), + bool scarf_seam = false, + bool smooth_acceleration = false); /*! * Add a single line that is part of a wall to the gcode. @@ -452,8 +486,8 @@ class LayerPlan : public NoCopy * the first bridge segment. */ void addWallLine( - const Point2LL& p0, - const Point2LL& p1, + const Point3LL& p0, + const Point3LL& p1, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -462,7 +496,8 @@ class LayerPlan : public NoCopy const Ratio width_factor, double& non_bridge_line_volume, Ratio speed_factor, - double distance_to_bridge_start); + double distance_to_bridge_start, + const bool travel_to_z = true); /*! * Add a wall to the g-code starting at vertex \p start_idx @@ -481,7 +516,7 @@ class LayerPlan : public NoCopy */ void addWall( const Polygon& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -510,10 +545,12 @@ class LayerPlan : public NoCopy * polyline). * \param is_reversed Whether to print this wall in reverse direction. * \param is_linked_path Whether the path is a continuation off the previous path + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addWall( const ExtrusionLine& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -523,7 +560,9 @@ class LayerPlan : public NoCopy bool always_retract, const bool is_closed, const bool is_reversed, - const bool is_linked_path); + const bool is_linked_path, + const bool scarf_seam = false, + const bool smooth_speed = false); /*! * Add an infill wall to the g-code @@ -665,50 +704,6 @@ class LayerPlan : public NoCopy const bool is_top_layer, const bool is_bottom_layer); - - /*! - * Given a wall polygon and a start vertex index, return the index of the first vertex that is supported (is not above air) - * - * Uses bridge_wall_mask and overhang_mask to determine where there is air below - * - * \param wall The wall polygon - * \param start_idx The index of the starting vertex of \p wall - * \return The index of the first supported vertex - if no vertices are supported, start_idx is returned - */ - template - unsigned locateFirstSupportedVertex(const T& wall, const unsigned start_idx) const - { - if (bridge_wall_mask_.empty() && seam_overhang_mask_.empty()) - { - return start_idx; - } - - const auto air_below = bridge_wall_mask_.unionPolygons(seam_overhang_mask_); - - unsigned curr_idx = start_idx; - - while (true) - { - const Point2LL& vertex = cura::make_point(wall[curr_idx]); - if (! air_below.inside(vertex, true)) - { - // vertex isn't above air so it's OK to use - return curr_idx; - } - - if (++curr_idx >= wall.size()) - { - curr_idx = 0; - } - - if (curr_idx == start_idx) - { - // no vertices are supported so just return the original index - return start_idx; - } - } - } - /*! * Write the planned paths to gcode * @@ -726,25 +721,6 @@ class LayerPlan : public NoCopy */ bool makeRetractSwitchRetract(unsigned int extruder_plan_idx, unsigned int path_idx); - /*! - * Writes a path to GCode and performs coasting, or returns false if it did nothing. - * - * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. - * - * \param gcode The gcode to write the planned paths to. - * \param extruder_plan_idx The index of the current extruder plan. - * \param path_idx The index into LayerPlan::paths for the next path to be - * written to GCode. - * \param layer_thickness The height of the current layer. - * \return Whether any GCode has been written for the path. - */ - bool writePathWithCoasting( - GCodeExport& gcode, - const size_t extruder_plan_idx, - const size_t path_idx, - const coord_t layer_thickness, - const std::function insertTempOnTime); - /*! * Applying speed corrections for minimal layer times and determine the fanSpeed. * @@ -785,6 +761,11 @@ class LayerPlan : public NoCopy */ void applyGradualFlow(); + /*! + * Gets the mesh being printed first on this layer + */ + std::shared_ptr findFirstPrintedMesh() const; + private: /*! * \brief Compute the preferred or minimum combing boundary @@ -820,6 +801,212 @@ class LayerPlan : public NoCopy const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed); + + /*! + * @brief Send a GCodePath line to the communication object, applying proper Z offsets + * @param path The path to be sent + * @param position The start position (which is not included in the path points) + * @param extrude_speed The actual used extrusion speed + */ + void sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed); + + /*! + * @brief Write a travel move and properly apply the various Z offsets + * @param gcode The actual GCode exporter + * @param position The position to move to. The Z coordinate is an offset to the current layer position + * @param speed The actual used speed + * @param path_z_offset The global path Z offset to be applied + * @note This function is to be used when dealing with 3D coordinates. If you have 2D coordinates, just call gcode.writeTravel() + */ + void writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset); + + /*! + * \brief Write an extrusion move and properly apply the various Z offsets + * \param gcode The actual GCode exporter + * \param position The position to move to. The Z coordinate is an offset to the current layer position + * \param speed The actual used speed + * \param path_z_offset The global path Z offset to be applied + * \param extrusion_mm3_per_mm The desired flow rate + * \param feature The current feature being printed + * \param update_extrusion_offset whether to update the extrusion offset to match the current flow rate + */ + void writeExtrusionRelativeZ( + GCodeExport& gcode, + const Point3LL& position, + const Velocity& speed, + const coord_t path_z_offset, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + bool update_extrusion_offset = false); + + /*! + * \brief Alias for a function definition that adds an extrusion segment + * \param start The start position of the segment + * \param end The end position of the segment + * \param speed_factor The speed factor to be applied when extruding this specific segment (relative to nominal speed for the entire path) + * \param flow_ratio The flow ratio to be applied when extruding this specific segment (relative to nominal flow for the entire path) + * \param line_width_ratio The line width ratio to be applied when extruding this specific segment (relative to nominal line width for the entire path) + * \param distance_to_bridge_start The calculate distance to the next bridge start, which may be irrelevant in some cases + */ + using AddExtrusionSegmentFunction = std::function; + + /*! + * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param wall The full wall to be added + * \param wall_length The pre-calculated full wall length + * \param start_idx The index of the point where to start printing the wall + * \param direction The direction along which to print the wall, which should be 1 or -1 + * \param max_index The last index to be used when iterating over the wall segments + * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air + * \param flow_ratio The ratio with which to multiply the extrusion amount + * \param nominal_line_width The nominal line width for the wall + * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move + * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param is_small_feature Indicates whether the wall is so small that it should be processed differently + * \param small_feature_speed_factor The speed factor to be applied to small feature walls + * \param max_area_deviation The maximum allowed area deviation to split a segment into pieces + * \param max_resolution The maximum resolution to split a segment into pieces + * \param scarf_seam_length The length of the scarf joint seam, which may be 0 if there is none + * \param scarf_seam_start_ratio The ratio of the line thickness to start the scarf seam with + * \param scarf_split_distance The maximum length of a segment to apply the scarf seam gradient, longer segments will be splitted + * \param scarf_max_z_offset The maximum Z offset te be applied at the lowest position of the scarf seam + * \param speed_split_distance The maximum length of a segment to apply the acceleration/deceleration gradient, longer segments will be splitted + * \param start_speed_ratio The ratio of the top speed to be applied when starting the segment, then accelerate gradually to full speed + * \param accelerate_length The pre-calculated length of the acceleration phase + * \param end_speed_ratio The ratio of the top speed to be applied when finishing a segment + * \param decelerate_length The pre-calculated length of the deceleration phase + * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or the normal first pass of the wall + * \param compute_distance_to_bridge_start Whether we should compute the distance to start of bridge. This is + * possible only if PathType is ExtrusionLine and will be ignored otherwise. + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + * \return The index of the last traversed point, and the final position with the scarf seam + */ + template + std::tuple addSplitWall( + const PathAdapter& wall, + const coord_t wall_length, + const size_t start_idx, + const size_t max_index, + const int direction, + const GCodePathConfig& default_config, + const bool always_retract, + const bool is_small_feature, + Ratio small_feature_speed_factor, + const coord_t max_area_deviation, + const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, + const auto scarf_seam_length, + const auto scarf_seam_start_ratio, + const auto scarf_split_distance, + const coord_t scarf_max_z_offset, + const coord_t speed_split_distance, + const Ratio start_speed_ratio, + const coord_t accelerate_length, + const Ratio end_speed_ratio, + const coord_t decelerate_length, + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const AddExtrusionSegmentFunction& func_add_segment); + + /*! + * \brief Add a wall to the gcode with optimized order, possibly adding a scarf seam / speed gradient according to settings + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param wall The full wall to be added + * \param start_idx The index of the point where to start printing the wall + * \param settings The settings which should apply to this wall added to the layer plan + * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air + * \param flow_ratio The ratio with which to multiply the extrusion amount + * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param is_closed Indicates whether the path is closed (or open) + * \param is_reversed Indicates if the path is to be processed backwards + * \param is_candidate_small_feature Indicates whether the path should be tested for being treated as a smell feature + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + * \return The index of the last traversed point, and the final position with the scarf seam + */ + template + std::tuple addWallWithScarfSeam( + const PathAdapter& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_candidate_small_feature, + const bool scarf_seam, + const bool smooth_speed, + const AddExtrusionSegmentFunction& func_add_segment); + + /*! + * \brief Add a wipe travel after the given path has been extruded + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param path The path that has just been extruded + * \param wipe_distance The length of the wipe move to be added + * \param backwards Indicates if the path has been processed backwards + * \param start_index The index of the point where o start printing the path + * \param last_path_position The actual last position of the extruder, which may be slightly forwards on the last printed segment + */ + template + void addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position); + + /*! + * Pre-calculates the coasting to be applied on the paths + * + * \param extruder_settings The current extruder settings + * \param paths The current set of paths to be written to GCode + * \param current_position The last position set in the gcode writer + * \return The list of coasting settings to be applied on the paths. It will always have the same size as paths. + */ + std::vector calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const; + + /*! + * Writes a path to GCode and performs coasting, or returns false if it did nothing. + * + * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. + * + * \param gcode The gcode to write the planned paths to. + * \param extruder_plan_idx The index of the current extruder plan. + * \param path_idx The index into LayerPlan::paths for the next path to be + * written to GCode. + * \param layer_thickness The height of the current layer. + * \param insertTempOnTime A function that inserts temperature changes at a given time. + * \param path_coasting The actual coasting to be applied to the path. + * \return Whether any GCode has been written for the path. + */ + bool writePathWithCoasting( + GCodeExport& gcode, + const size_t extruder_plan_idx, + const size_t path_idx, + const std::function insertTempOnTime, + const PathCoasting& path_coasting); + + /*! + * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment + * \param wall The currently processed wall + * \param current_index The index of the currently processed point + * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move + * \return The distance from the start of the current wall line to the first bridge segment + */ + coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const; + + /*! + * \brief Calculates whether the given segment is to be treated as overhanging + * \param p0 The start point of the segment + * \param p1 The end point of the segment + */ + bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const; }; } // namespace cura diff --git a/include/PathAdapter.h b/include/PathAdapter.h new file mode 100644 index 0000000000..21a71ee1da --- /dev/null +++ b/include/PathAdapter.h @@ -0,0 +1,63 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PATH_ADAPTER_H +#define PATH_ADAPTER_H + +#include + +#include "geometry/Point2LL.h" +#include "utils/Coord_t.h" + +namespace cura +{ + +/* Adapter class to allow extrusion-related functions to work with either an ExtrusionLine or a Polygon */ +template +class PathAdapter +{ +public: + /*! + * \brief Base constructor + * \param path The actual stored path + * \param fixed_line_width The fixed line width in case the stored path doesn't handle information about a variable + * line width. Can be omitted otherwise. + */ + PathAdapter(const PathType& path, coord_t fixed_line_width = 0) + : path_(path) + , fixed_line_width_(fixed_line_width) + { + } + + bool empty() const + { + return path_.empty(); + } + + size_t size() const + { + return path_.size(); + } + + coord_t length() const + { + return path_.length(); + } + + const Point2LL& pointAt(size_t index) const; + + coord_t lineWidthAt(size_t index) const; + + const PathType& getPath() const + { + return path_; + } + +private: + const PathType& path_; + const coord_t fixed_line_width_; +}; + +} // namespace cura + +#endif // PATH_ADAPTER_H diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index be8e61873b..a2e917fae8 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -22,7 +22,13 @@ #include "settings/EnumSettings.h" //To get the seam settings. #include "settings/ZSeamConfig.h" //To read the seam configuration. #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. +#include "utils/math.h" #include "utils/polygonUtils.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" +#include "utils/scoring/ExclusionAreaScoringCriterion.h" +#include "utils/scoring/RandomScoringCriterion.h" #include "utils/views/dfs.h" namespace cura @@ -113,7 +119,9 @@ class PathOrderOptimizer const bool reverse_direction = false, const std::unordered_multimap& order_requirements = no_order_requirements_, const bool group_outer_walls = false, - const Shape& disallowed_areas_for_seams = {}) + const Shape& disallowed_areas_for_seams = {}, + const bool use_shortest_for_inner_walls = false, + const Shape& overhang_areas = Shape()) : start_point_(start_point) , seam_config_(seam_config) , combing_boundary_((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) @@ -122,7 +130,8 @@ class PathOrderOptimizer , _group_outer_walls(group_outer_walls) , order_requirements_(&order_requirements) , disallowed_area_for_seams{ disallowed_areas_for_seams } - + , use_shortest_for_inner_walls_(use_shortest_for_inner_walls) + , overhang_areas_(overhang_areas) { } @@ -130,11 +139,12 @@ class PathOrderOptimizer * Add a new polygon to be optimized. * \param polygon The polygon to optimize. */ - void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt) + void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt, const bool is_outer_wall = false) { constexpr bool is_closed = true; paths_.emplace_back(polygon, is_closed); paths_.back().force_start_index_ = force_start_index; + paths_.back().is_outer_wall = is_outer_wall; } /*! @@ -180,6 +190,20 @@ class PathOrderOptimizer } } + // Set actual used start point calculation strategy for each path + for (auto& path : paths_) + { + if (use_shortest_for_inner_walls_ && ! path.is_outer_wall) + { + path.seam_config_ = ZSeamConfig(EZSeamType::SHORTEST); + path.force_start_index_ = std::nullopt; + } + else + { + path.seam_config_ = seam_config_; + } + } + // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); @@ -205,16 +229,19 @@ class PathOrderOptimizer // For some Z seam types the start position can be pre-computed. // This is faster since we don't need to re-compute the start position at each step then. - precompute_start &= seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (precompute_start) { for (auto& path : paths_) { - if (! path.is_closed_ || path.converted_->empty()) + if (path.seam_config_.type_ == EZSeamType::RANDOM || path.seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER) { - continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + if (! path.is_closed_ || path.converted_->empty()) + { + continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + } + path.start_vertex_ = findStartLocation(path, path.seam_config_.pos_); } - path.start_vertex_ = findStartLocation(path, seam_config_.pos_); } } @@ -298,6 +325,17 @@ class PathOrderOptimizer */ const std::unordered_multimap* order_requirements_; + /*! + * If true, we will compute the seam position of inner walls using a "shortest" seam configs, for inner walls that + * are directly following an outer wall. + */ + const bool use_shortest_for_inner_walls_; + + /*! + * Contains the overhang areas, where we would prefer not to place the start locations of walls + */ + const Shape overhang_areas_; + std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { std::vector optimized_order; // To store our result in. @@ -583,8 +621,8 @@ class PathOrderOptimizer continue; } - const bool precompute_start - = seam_config_.type_ == EZSeamType::RANDOM || seam_config_.type_ == EZSeamType::USER_SPECIFIED || seam_config_.type_ == EZSeamType::SHARPEST_CORNER; + const bool precompute_start = path->seam_config_.type_ == EZSeamType::RANDOM || path->seam_config_.type_ == EZSeamType::USER_SPECIFIED + || path->seam_config_.type_ == EZSeamType::SHARPEST_CORNER; if (! path->is_closed_ || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex_ = findStartLocation(*path, start_position); @@ -689,196 +727,84 @@ class PathOrderOptimizer } // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. + const PointsSet& points = *path.converted_; - if (seam_config_.type_ == EZSeamType::RANDOM) - { - size_t vert = getRandomPointInPolygon(*path.converted_); - return vert; - } + // ########## Step 1: define the main criteria to be applied and their weights + // Standard weight for the "main" selection criterion, depending on the selected strategy. There should be + // exactly one calculation using this criterion. + BestElementFinder best_candidate_finder; + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; - if (path.force_start_index_.has_value()) - { - // Start index already known, since we forced it, return. - return path.force_start_index_.value(); - } + BestElementFinder::WeighedCriterion main_criterion; - // Precompute segments lengths because we are going to need them multiple times - std::vector segments_sizes(path.converted_->size()); - coord_t total_length = 0; - for (size_t i = 0; i < path.converted_->size(); ++i) + if (path.force_start_index_.has_value()) // Actually handles EZSeamType::USER_SPECIFIED { - const Point2LL& here = path.converted_->at(i); - const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); - const coord_t segment_size = vSize(next - here); - segments_sizes[i] = segment_size; - total_length += segment_size; + // Use a much smaller distance divider because we want points around the forced points to be filtered out very easily + constexpr double distance_divider = 1.0; + constexpr auto distance_type = DistanceScoringCriterion::DistanceType::Euclidian; + main_criterion.criterion = std::make_shared(points, points.at(path.force_start_index_.value()), distance_type, distance_divider); } - - size_t best_i; - double best_score = std::numeric_limits::infinity(); - for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) + else { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - // For SHARPEST_CORNER, use a fixed starting score of 0. - const double score_distance = (seam_config_.type_ == EZSeamType::SHARPEST_CORNER && seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - ? MM2INT(10) - : vSize2(here - target_pos); - - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) - - double corner_shift; - - if (seam_config_.type_ == EZSeamType::SHORTEST) + if (path.seam_config_.type_ == EZSeamType::SHORTEST) { - // the more a corner satisfies our criteria, the closer it appears to be - // shift 10mm for a very acute corner - corner_shift = MM2INT(10) * MM2INT(10); + main_criterion.criterion = std::make_shared(points, target_pos); } - else + else if ( + path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)) { - // the larger the distance from prev_point to p1, the more a corner will "attract" the seam - // so the user has some control over where the seam will lie. - - // the divisor here may need adjusting to obtain the best results (TBD) - corner_shift = score_distance / 50; + main_criterion.criterion = std::make_shared(points, path.seam_config_.corner_pref_); } - - double score = score_distance; - switch (seam_config_.corner_pref_) + else if (path.seam_config_.type_ == EZSeamType::RANDOM) { - default: - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score += corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score -= corner_angle * corner_shift; - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - score -= std::abs(corner_angle) * corner_shift; // Still give sharper corners more advantage. - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: // Give sharper corners some advantage, but sharper concave corners even more. - { - double score_corner = std::abs(corner_angle) * corner_shift; - if (corner_angle < 0) // Concave corner. - { - score_corner *= 2; - } - score -= score_corner; - break; - } - } - - constexpr double EPSILON = 5.0; - if (std::abs(best_score - score) <= EPSILON) - { - // add breaker for two candidate starting location with similar score - // if we don't do this then we (can) get an un-even seam - // ties are broken by favouring points with lower x-coord - // if x-coord for both points are equal then break ties by - // favouring points with lower y-coord - const Point2LL& best_point = path.converted_->at(best_i); - if (std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) - { - best_score = std::min(best_score, score); - best_i = i; - } - } - else if (score < best_score) - { - best_i = i; - best_score = score; + main_criterion.criterion = std::make_shared(); } } - if (! disallowed_area_for_seams.empty()) + if (main_criterion.criterion) { - best_i = pathIfZseamIsInDisallowedArea(best_i, path, 0); + main_criteria_pass.criteria.push_back(main_criterion); + } + else + { + spdlog::warn("Missing main criterion calculator"); } - return best_i; - } - - /*! - * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point - * is computed by travelling on the path and stopping when the distance has been reached, For example: - * |------|---------|------|--------------*---| - * H A B C N D - * In this case, H is the start point of the path and ABCD are the actual following points of the path. - * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. - * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path - * at a given physical distance. - * \param path The vertex data of a path - * \param here The starting point index - * \param distance The distance we want to travel on the path, which may be positive to go forward - * or negative to go backward - * \param segments_sizes The pre-computed sizes of the segments - * \return The position of the path a the given distance from the reference point - */ - static Point2LL findNeighbourPoint(const OrderablePath& path, int here, coord_t distance, const std::vector& segments_sizes) - { - const int direction = distance > 0 ? 1 : -1; - const int size_delta = distance > 0 ? -1 : 0; - distance = std::abs(distance); - // Travel on the path until we reach the distance - int actual_delta = 0; - coord_t travelled_distance = 0; - coord_t segment_size = 0; - while (travelled_distance < distance) + // Second criterion with heigher weight to avoid overhanging areas + if (! overhang_areas_.empty()) { - actual_delta += direction; - segment_size = segments_sizes[(here + actual_delta + size_delta + path.converted_->size()) % path.converted_->size()]; - travelled_distance += segment_size; + BestElementFinder::WeighedCriterion overhang_criterion; + overhang_criterion.weight = 2.0; + overhang_criterion.criterion = std::make_shared(points, overhang_areas_); + main_criteria_pass.criteria.push_back(overhang_criterion); } - const Point2LL& next_pos = path.converted_->at((here + actual_delta + path.converted_->size()) % path.converted_->size()); + best_candidate_finder.appendCriteriaPass(main_criteria_pass); - if (travelled_distance > distance) [[likely]] - { - // We have overtaken the required distance, go backward on the last segment - int prev = (here + actual_delta - direction + path.converted_->size()) % path.converted_->size(); - const Point2LL& prev_pos = path.converted_->at(prev); + // ########## Step 2: add fallback passes for criteria with very similar scores (e.g. corner on a cylinder) + const AABB path_bounding_box(points); - const Point2LL vector = next_pos - prev_pos; - const Point2LL unit_vector = (vector * 1000) / segment_size; - const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); - return prev_pos + vector_delta / 1000; + { // First fallback strategy is to take points on the back-most position + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::YOnly); + constexpr double outsider_delta_threshold = 0.01; + best_candidate_finder.appendSingleCriterionPass(fallback_criterion, outsider_delta_threshold); } - else - { - // Luckily, the required distance stops exactly on an existing point - return next_pos; - } - } - /*! - * Some models have very sharp corners, but also have a high resolution. If a sharp corner - * consists of many points each point individual might have a shallow corner, but the - * collective angle of all nearby points is greater. To counter this the cornerAngle is - * calculated from two points within angle_query_distance of the query point, no matter - * what segment this leads us to - * \param path The vertex data of a path - * \param i index of the query point - * \param segments_sizes The pre-computed sizes of the segments - * \param total_length The path total length - * \param angle_query_distance query range (default to 1mm) - * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] - */ - static double cornerAngle(const OrderablePath& path, int i, const std::vector& segments_sizes, coord_t total_length, const coord_t angle_query_distance = 1000) - { - const coord_t bounded_distance = std::min(angle_query_distance, total_length / 2); - const Point2LL& here = path.converted_->at(i); - const Point2LL next = findNeighbourPoint(path, i, bounded_distance, segments_sizes); - const Point2LL previous = findNeighbourPoint(path, i, -bounded_distance, segments_sizes); + { // Second fallback strategy, in case we still have multiple points that are aligned on Y (e.g. cube), take the right-most point + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::XOnly); + best_candidate_finder.appendSingleCriterionPass(fallback_criterion); + } - double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; + // ########## Step 3: apply the criteria to find the vertex with the best global score + std::optional best_i = best_candidate_finder.findBestElement(points.size()); - return angle / std::numbers::pi; + if (! disallowed_area_for_seams.empty()) + { + best_i = pathIfZseamIsInDisallowedArea(best_i.value_or(0), path, 0); + } + return best_i.value_or(0); } /*! @@ -939,16 +865,6 @@ class PathOrderOptimizer return sum * sum; // Squared distance, for fair comparison with direct distance. } - /*! - * Get a random vertex of a polygon. - * \param polygon A polygon to get a random vertex of. - * \return A random index in that polygon. - */ - size_t getRandomPointInPolygon(const PointsSet& polygon) const - { - return rand() % polygon.size(); - } - bool isLoopingPolyline(const OrderablePath& path) { if (path.converted_->empty()) diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index f74b9cffe3..1c4cab4fc6 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -39,7 +39,7 @@ struct TreeSupportSettings , maximum_move_distance((angle < TAU / 4) ? std::llround(tan(angle) * layer_height) : std::numeric_limits::max()) , maximum_move_distance_slow((angle_slow < TAU / 4) ? std::llround(tan(angle_slow) * layer_height) : std::numeric_limits::max()) , support_bottom_layers(mesh_group_settings.get("support_bottom_enable") ? round_divide(mesh_group_settings.get("support_bottom_height"), layer_height) : 0) - , tip_layers(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height)) + , tip_layers(std::max(std::max((branch_radius - min_radius) / (support_line_width / 3), branch_radius / layer_height), 1LL)) , // Ensure lines always stack nicely even if layer height is large diameter_angle_scale_factor(sin(mesh_group_settings.get("support_tree_branch_diameter_angle")) * layer_height / branch_radius) , max_to_model_radius_increase(mesh_group_settings.get("support_tree_max_diameter_increase_by_merges_when_support_to_model") / 2) diff --git a/include/communication/ArcusCommunication.h b/include/communication/ArcusCommunication.h index 2638fed7e1..913c38418f 100644 --- a/include/communication/ArcusCommunication.h +++ b/include/communication/ArcusCommunication.h @@ -86,7 +86,7 @@ class ArcusCommunication : public Communication * This may indicate the starting position (or any other jump in the path). * \param position The current position to start the next line at. */ - void sendCurrentPosition(const Point2LL& position) override; + void sendCurrentPosition(const Point3LL& position) override; /* * \brief Sends a message to indicate that all the slicing is done. @@ -113,7 +113,7 @@ class ArcusCommunication : public Communication * visualisation of the layer. * * This will be called after all the polygons and lines of this layer are - * sent via sendPolygons, sendPolygon and sendLineTo. This will flush all + * sent via sendLineTo. This will flush all * visualised data for one layer in one go. * \param layer_nr The layer that was completed. * \param z The z-coordinate of the top side of the layer. @@ -132,7 +132,7 @@ class ArcusCommunication : public Communication * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - void sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; + void sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; /* * \brief Send the sliced layer data to the front-end after the optimisation @@ -142,34 +142,6 @@ class ArcusCommunication : public Communication */ void sendOptimizedLayerData() override; - /* - * \brief Send a polygon to the front-end to display in layer view. - * - * The polygons are not actually flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygon represents (infill, - * wall, support, etc). - * \param polygon The shape to visualise. - * \param line_width The width of the lines in this polygon. - * \param line_thickness The thickness (in the Z direction) of the polygon. - * \param velocity The velocity of printing this polygon. - */ - void sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; - - /* - * \brief Send polygons to the front-end to display in layer view. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygons represent (infill, - * wall, support, etc). - * \param polygons The shapes to visualise. - * \param line_width The width of the lines in these polygons. - * \param line_thickness The thickness (in the Z direction) of the polygons. - * \param velocity The velocity of printing these polygons. - */ - void sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) override; - /* * \brief Send an estimate of how long the print would take and how much * material it would use. @@ -182,15 +154,13 @@ class ArcusCommunication : public Communication void sendProgress(double progress) const override; /* - * \brief Set which extruder is being used for the following calls to - * ``sendPolygon``, ``sendPolygons`` and ``sendLineTo``. + * \brief Set which extruder is being used for the following calls to ``sendLineTo``. * \param extruder The new extruder to send data for. */ void setExtruderForSend(const ExtruderTrain& extruder) override; /* - * \brief Set which layer is being used for the following calls to - * ``sendPolygon``, ``sendPolygons`` and ``sendLineTo``. + * \brief Set which layer is being used for the following calls to ``sendLineTo``. * \param layer_nr The index of the layer to send data for. This is zero- * indexed but may be negative for raft layers. */ diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 15286e6049..395151d331 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -66,7 +66,7 @@ class CommandLine : public Communication * The command line doesn't do anything with the current position so this is * ignored. */ - void sendCurrentPosition(const Point2LL&) override; + void sendCurrentPosition(const Point3LL&) override; /* * \brief Indicate to the command line that we finished slicing. @@ -100,7 +100,7 @@ class CommandLine : public Communication * * The command line doesn't show any layer view so this is ignored. */ - void sendLineTo(const PrintFeatureType&, const Point2LL&, const coord_t&, const coord_t&, const Velocity&) override; + void sendLineTo(const PrintFeatureType&, const Point3LL&, const coord_t&, const coord_t&, const Velocity&) override; /* * \brief Complete a layer to show it in layer view. @@ -109,20 +109,6 @@ class CommandLine : public Communication */ void sendOptimizedLayerData() override; - /* - * \brief Send a polygon to show it in layer view. - * - * The command line doesn't show any layer view so this is ignored. - */ - void sendPolygon(const PrintFeatureType&, const Polygon&, const coord_t&, const coord_t&, const Velocity&) override; - - /* - * \brief Send a polygon to show it in layer view. - * - * The command line doesn't show any layer view so this is ignored. - */ - void sendPolygons(const PrintFeatureType&, const Shape&, const coord_t&, const coord_t&, const Velocity&) override; - /* * \brief Show an estimate of how long the print would take and how much * material it would use. diff --git a/include/communication/Communication.h b/include/communication/Communication.h index 9bc990c11e..1a70b15746 100644 --- a/include/communication/Communication.h +++ b/include/communication/Communication.h @@ -64,34 +64,6 @@ class Communication */ virtual void sendLayerComplete(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness) = 0; - /* - * \brief Send polygons to the user to visualise. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygons represent (infill, - * wall, support, etc). - * \param polygons The shapes to visualise. - * \param line_width The width of the lines in these polygons. - * \param line_thickness The thickness (in the Z direction) of the polygons. - * \param velocity The velocity of printing these polygons. - */ - virtual void sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; - - /* - * \brief Send a polygon to the user to visualise. - * - * The polygons may not actually be flushed until ``sendLayerComplete`` is - * called. - * \param type The type of print feature the polygon represents (infill, - * wall, support, etc). - * \param polygon The shape to visualise. - * \param line_width The width of the lines in this polygon. - * \param line_thickness The thickness (in the Z direction) of the polygon. - * \param velocity The velocity of printing this polygon. - */ - virtual void sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; - /* * \brief Send a line to the user to visualise. * @@ -104,7 +76,7 @@ class Communication * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - virtual void sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; + virtual void sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) = 0; /* * \brief Send the current position to visualise. @@ -112,7 +84,7 @@ class Communication * This may indicate the starting position (or any other jump in the path). * \param position The current position to start the next line at. */ - virtual void sendCurrentPosition(const Point2LL& position) = 0; + virtual void sendCurrentPosition(const Point3LL& position) = 0; /* * \brief Set which extruder is being used for the following calls to diff --git a/include/communication/EmscriptenCommunication.h b/include/communication/EmscriptenCommunication.h index 5144b96dc6..b1c7e7c903 100644 --- a/include/communication/EmscriptenCommunication.h +++ b/include/communication/EmscriptenCommunication.h @@ -23,12 +23,14 @@ class EmscriptenCommunication : public CommandLine std::string progress_handler_; ///< Handler for progress messages. std::string gcode_header_handler_; ///< Handler for getting the GCode handler. std::string slice_info_handler_; ///< Handler for slice information messages. - + std::string engine_info_handler_; ///< Handler for curaengine info : version and hash. /** * \brief Creates a message containing slice information. * \return A string containing the slice information message. */ [[nodiscard]] static std::string createSliceInfoMessage(); + [[nodiscard]] static std::string createEngineInfoMessage(); + public: /** @@ -48,6 +50,11 @@ class EmscriptenCommunication : public CommandLine */ void sendGCodePrefix(const std::string& prefix) const override; + /** + * \brief Indicate that we're beginning to send g-code. + */ + void beginGCode() override; + /** * \brief Initiates the slicing of the next item. */ diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 7b211f8516..0dd81541b8 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -409,6 +409,11 @@ class GCodeExport : public NoCopy */ bool needPrimeBlob() const; + /* + * Function is used to write the content of output_stream to the gcode file + */ + void flushOutputStream(); + private: /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. diff --git a/include/geometry/Point2LL.h b/include/geometry/Point2LL.h index 08a556a961..424ba212d2 100644 --- a/include/geometry/Point2LL.h +++ b/include/geometry/Point2LL.h @@ -1,8 +1,8 @@ // Copyright (c) 2024 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher. -#ifndef UTILS_INT_POINT_H -#define UTILS_INT_POINT_H +#ifndef GEOMETRY_POINT2LL_H +#define GEOMETRY_POINT2LL_H /** The integer point classes are used as soon as possible and represent microns in 2D or 3D space. @@ -10,11 +10,12 @@ Integer points are used to avoid floating point rounding errors, and because Cli */ #define INLINE static inline +#include #include #include #include -#include "geometry/Point3LL.h" +#include "utils/Coord_t.h" #include "utils/types/generic.h" #ifdef __GNUC__ @@ -30,6 +31,8 @@ Integer points are used to avoid floating point rounding errors, and because Cli namespace cura { +class Point3LL; + /* 64bit Points are used mostly throughout the code, these are the 2D points from ClipperLib */ using Point2LL = ClipperLib::IntPoint; @@ -207,39 +210,17 @@ INLINE const Point2LL& make_point(const Point2LL& p) return p; } -inline Point3LL operator+(const Point3LL& p3, const Point2LL& p2) -{ - return { p3.x_ + p2.X, p3.y_ + p2.Y, p3.z_ }; -} +Point2LL operator+(const Point2LL& p2, const Point3LL& p3); -inline Point3LL& operator+=(Point3LL& p3, const Point2LL& p2) -{ - p3.x_ += p2.X; - p3.y_ += p2.Y; - return p3; -} +Point3LL operator+(const Point3LL& p3, const Point2LL& p2); -inline Point2LL operator+(const Point2LL& p2, const Point3LL& p3) -{ - return { p3.x_ + p2.X, p3.y_ + p2.Y }; -} +Point3LL& operator+=(Point3LL& p3, const Point2LL& p2); -inline Point3LL operator-(const Point3LL& p3, const Point2LL& p2) -{ - return { p3.x_ - p2.X, p3.y_ - p2.Y, p3.z_ }; -} +Point3LL operator-(const Point3LL& p3, const Point2LL& p2); -inline Point3LL& operator-=(Point3LL& p3, const Point2LL& p2) -{ - p3.x_ -= p2.X; - p3.y_ -= p2.Y; - return p3; -} +Point3LL& operator-=(Point3LL& p3, const Point2LL& p2); -inline Point2LL operator-(const Point2LL& p2, const Point3LL& p3) -{ - return { p2.X - p3.x_, p2.Y - p3.y_ }; -} +Point2LL operator-(const Point2LL& p2, const Point3LL& p3); } // namespace cura @@ -259,4 +240,4 @@ struct hash }; } // namespace std -#endif // UTILS_INT_POINT_H +#endif // GEOMETRY_POINT2LL_H diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index fd220b05ef..604e93af3c 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -10,6 +10,7 @@ #include //For numeric_limits::min and max. #include // for operations on any arithmetic number type +#include "geometry/Point2LL.h" #include "utils/Coord_t.h" #include "utils/types/generic.h" @@ -20,9 +21,9 @@ namespace cura class Point3LL { public: - coord_t x_{}; - coord_t y_{}; - coord_t z_{}; + coord_t x_{ 0 }; + coord_t y_{ 0 }; + coord_t z_{ 0 }; Point3LL() = default; @@ -35,6 +36,8 @@ class Point3LL Point3LL(Point3LL&& point) = default; Point3LL(const Point3LL& point) = default; + Point3LL(const Point2LL& point); + Point3LL& operator=(const Point3LL& point) = default; Point3LL& operator=(Point3LL&& point) = default; @@ -130,6 +133,11 @@ class Point3LL return x_ * x_ + y_ * y_ + z_ * z_; } + [[nodiscard]] double vSize2f() + { + return static_cast(x_) * static_cast(x_) + static_cast(y_) * static_cast(y_) + static_cast(z_) * static_cast(z_); + } + [[nodiscard]] coord_t vSize() const { return std::llrint(sqrt(static_cast(vSize2()))); @@ -148,6 +156,10 @@ class Point3LL return x_ * p.x_ + y_ * p.y_ + z_ * p.z_; } + [[nodiscard]] Point2LL toPoint2LL() const; + + [[nodiscard]] Point3LL resized(coord_t length) const; + coord_t& operator[](const size_t index) { assert(index < 3); @@ -180,6 +192,11 @@ inline Point3LL operator*(const T i, const Point3LL& rhs) return rhs * i; } +inline Point3LL lerp(const Point3LL& a, const Point3LL& b, const double t) +{ + return Point3LL(cura::lerp(a.x_, b.x_, t), cura::lerp(a.y_, b.y_, t), cura::lerp(a.z_, b.z_, t)); +} + } // namespace cura diff --git a/include/gradual_flow/FlowLimitedPath.h b/include/gradual_flow/FlowLimitedPath.h index 0e3eab859d..49242f7130 100644 --- a/include/gradual_flow/FlowLimitedPath.h +++ b/include/gradual_flow/FlowLimitedPath.h @@ -30,7 +30,7 @@ enum class FlowState struct FlowLimitedPath { const GCodePath* original_gcode_path_data; - PointsSet points; + std::vector points{}; double speed{ targetSpeed() }; // um/s double flow_{ extrusionVolumePerMm() * speed }; // um/s double total_length{ totalLength() }; // um @@ -102,7 +102,7 @@ struct FlowLimitedPath for (auto point : points) { const auto identifier = is_first_point ? "M" : "L"; - path_data += fmt::format("{}{} {} ", identifier, point.X * 1e-3, point.Y * 1e-3); + path_data += fmt::format("{}{} {} ", identifier, point.x_ * 1e-3, point.y_ * 1e-3); is_first_point = false; } return path_data; @@ -135,11 +135,14 @@ struct FlowLimitedPath double totalLength() const // um { double path_length = 0; - auto last_point = points.front(); - for (const auto& point : points | ranges::views::drop(1)) + if (! points.empty()) { - path_length += std::hypot(point.X - last_point.X, point.Y - last_point.Y); - last_point = point; + auto last_point = points.front(); + for (const auto& point : points | ranges::views::drop(1)) + { + path_length += std::hypot(point.x_ - last_point.x_, point.y_ - last_point.y_); + last_point = point; + } } return path_length; } @@ -179,12 +182,12 @@ struct FlowLimitedPath auto current_partition_duration = 0.0; auto partition_index = direction == utils::Direction::Forward ? 0 : points.size() - 1; auto iteration_direction = direction == utils::Direction::Forward ? 1 : -1; - auto prev_point = points[partition_index]; + Point3LL prev_point = points[partition_index]; while (true) { - const auto next_point = points[partition_index + iteration_direction]; - const auto segment_length = std::hypot(next_point.X - prev_point.X, next_point.Y - prev_point.Y); + const Point3LL next_point = points[partition_index + iteration_direction]; + const auto segment_length = std::hypot(next_point.x_ - prev_point.x_, next_point.y_ - prev_point.y_); const auto segment_duration = segment_length / partition_speed; if (current_partition_duration + segment_duration < partition_duration) @@ -198,9 +201,10 @@ struct FlowLimitedPath const auto duration_left = partition_duration - current_partition_duration; auto segment_ratio = duration_left / segment_duration; assert(segment_ratio >= -1e-6 && segment_ratio <= 1. + 1e-6); - const auto partition_x = prev_point.X + static_cast(static_cast(next_point.X - prev_point.X) * segment_ratio); - const auto partition_y = prev_point.Y + static_cast(static_cast(next_point.Y - prev_point.Y) * segment_ratio); - const auto partition_point = ClipperLib::IntPoint(partition_x, partition_y); + const auto partition_x = prev_point.x_ + static_cast(static_cast(next_point.x_ - prev_point.x_) * segment_ratio); + const auto partition_y = prev_point.y_ + static_cast(static_cast(next_point.y_ - prev_point.y_) * segment_ratio); + const auto partition_z = prev_point.z_ + static_cast(static_cast(next_point.z_ - prev_point.z_) * segment_ratio); + const Point3LL partition_point(partition_x, partition_y, partition_z); /* * partition point @@ -227,7 +231,7 @@ struct FlowLimitedPath const auto partition_point_index = direction == utils::Direction::Forward ? partition_index + 1 : partition_index; // points left of the partition_index - PointsSet left_points; + std::vector left_points; for (unsigned int i = 0; i < partition_point_index; ++i) { left_points.emplace_back(points[i]); @@ -235,7 +239,7 @@ struct FlowLimitedPath left_points.emplace_back(partition_point); // points right of the partition_index - PointsSet right_points; + std::vector right_points; right_points.emplace_back(partition_point); for (unsigned int i = partition_point_index; i < points.size(); ++i) { @@ -323,7 +327,7 @@ struct GCodeState discretized_duration_remaining = 0; // set the current flow to the target end flow. When executing the backward pass we want to - // we start with this flow and gradually increase it to the target flow. However, if the + // start with this flow and gradually increase it to the target flow. However, if the // highest flow we can achieve is lower than this target flow we want to use that flow // instead. current_flow = std::min(current_flow, target_end_flow); diff --git a/include/gradual_flow/Processor.h b/include/gradual_flow/Processor.h index c9615d6ba2..c04b0c4734 100644 --- a/include/gradual_flow/Processor.h +++ b/include/gradual_flow/Processor.h @@ -32,7 +32,7 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ // Process first path for (const GCodePath& path : extruder_plan_paths | ranges::views::take(1)) { - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = PointsSet(path.points) }); + gcode_paths.push_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = path.points }); } /* Process remaining paths @@ -46,9 +46,10 @@ void process(std::vector& extruder_plan_paths, const size_t extruder_ */ for (const auto& path : extruder_plan_paths | ranges::views::drop(1)) { - PointsSet points{ gcode_paths.back().points.back() }; - points.push_back(PointsSet(path.points)); - gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = points }); + std::vector points{ gcode_paths.back().points.back() }; + points.insert(points.end(), path.points.begin(), path.points.end()); + + gcode_paths.emplace_back(FlowLimitedPath{ .original_gcode_path_data = &path, .points = std::move(points) }); } constexpr auto non_zero_flow_view = ranges::views::transform( diff --git a/include/infill/ImageBasedDensityProvider.h b/include/infill/ImageBasedDensityProvider.h index f8b215ecd7..c090ebf729 100644 --- a/include/infill/ImageBasedDensityProvider.h +++ b/include/infill/ImageBasedDensityProvider.h @@ -7,6 +7,7 @@ #include "../utils/AABB.h" #include "DensityProvider.h" +#include "geometry/Point3LL.h" namespace cura { diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 577cd8efcc..2eaf050d2e 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -29,7 +29,7 @@ namespace cura */ struct GCodePath { - coord_t z_offset{}; // 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 @@ -46,10 +46,11 @@ struct GCodePath bool perform_z_hop{ false }; //!< Whether to perform a z_hop in this path, which is assumed to be a travel path. bool perform_prime{ false }; //!< Whether this path is preceded by a prime (blob) bool skip_agressive_merge_hint{ false }; //!< Wheter this path needs to skip merging if any travel paths are in between the extrusions. - std::vector points{}; //!< The points constituting this path. + std::vector points{}; //!< The points constituting this path. The Z coordinate is an offset relative to the actual layer height, added to the global z_offset. 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 + bool travel_to_z{ true }; //! Indicates whether we should add a travel move to the Z height of the first point before processing the path /*! * Whether this config is the config of a travel path. diff --git a/include/path_ordering.h b/include/path_ordering.h index 0a7fb1af13..9366ba44c8 100644 --- a/include/path_ordering.h +++ b/include/path_ordering.h @@ -82,6 +82,16 @@ struct PathOrdering */ std::optional force_start_index_; + /*! + * The start point calculation strategy to be used for this path + */ + ZSeamConfig seam_config_; + + /*! + * Indicates whether this path is an outer (or inner) wall + */ + bool is_outer_wall{ false }; + /*! * Get vertex data from the custom path type. * diff --git a/include/plugins/converters.h b/include/plugins/converters.h index de947a850c..f2f77af66c 100644 --- a/include/plugins/converters.h +++ b/include/plugins/converters.h @@ -105,7 +105,7 @@ struct postprocess_response : public details::converter { - value_type operator()(const native_value_type& inner_contour, const std::string& pattern, const Settings& settings) const; + value_type operator()(const native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) const; }; struct infill_generate_response diff --git a/include/skin.h b/include/skin.h index fe7d555ef7..558ca59f64 100644 --- a/include/skin.h +++ b/include/skin.h @@ -135,14 +135,9 @@ class SkinInfillAreaComputation void generateRoofingFillAndSkinFill(SliceLayerPart& part); /*! - * Remove the areas which are directly under air in the top-most surface and directly above air in bottom-most - * surfaces from the \ref SkinPart::inner_infill and save them in the \ref SkinPart::bottom_most_surface_fill and - * \ref SkinPart::top_most_surface_fill (respectively) of the \p part. - * - * \param[in,out] part Where to get the SkinParts to get the outline info from and to store the top and bottom-most - * infill areas + * Generate the top and bottom-most surfaces of the given \p part, i.e. the surfaces that have nothing above or below */ - void generateTopAndBottomMostSkinFill(SliceLayerPart& part); + void generateTopAndBottomMostSurfaces(SliceLayerPart& part); /*! * Helper function to calculate and return the areas which are 'directly' under air. diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 4b434aa5ae..cb35355b4a 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -45,8 +45,6 @@ class SkinPart //!< roofing and non-roofing. Shape skin_fill; //!< The part of the skin which is not roofing. Shape roofing_fill; //!< The inner infill which has air directly above - Shape top_most_surface_fill; //!< The inner infill of the uppermost top layer which has air directly above. - Shape bottom_most_surface_fill; //!< The inner infill of the bottommost bottom layer which has air directly below. }; /*! @@ -69,6 +67,8 @@ class SliceLayerPart std::vector skin_parts; //!< The skin parts which are filled for 100% with lines and/or insets. std::vector wall_toolpaths; //!< toolpaths for walls, will replace(?) the insets. Binned by inset_idx. std::vector infill_wall_toolpaths; //!< toolpaths for the walls of the infill areas. Binned by inset_idx. + Shape top_most_surface; //!< Sub-part of the outline containing the area that is not covered by something above + Shape bottom_most_surface; //!< Sub-part of the outline containing the area that has nothing below /*! * The areas inside of the mesh. diff --git a/include/utils/AABB.h b/include/utils/AABB.h index e3a93c678b..6be6ba9b2c 100644 --- a/include/utils/AABB.h +++ b/include/utils/AABB.h @@ -9,6 +9,7 @@ namespace cura { +class PointsSet; class Polygon; class Shape; @@ -21,10 +22,10 @@ class AABB AABB(); //!< initializes with invalid min and max AABB(const Point2LL& min, const Point2LL& max); //!< initializes with given min and max AABB(const Shape& shape); //!< Computes the boundary box for the given shape - AABB(const Polygon& poly); //!< Computes the boundary box for the given polygons + AABB(const PointsSet& poly); //!< Computes the boundary box for the given polygons void calculate(const Shape& shape); //!< Calculates the aabb for the given shape (throws away old min and max data of this aabb) - void calculate(const Polygon& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) + void calculate(const PointsSet& poly); //!< Calculates the aabb for the given polygon (throws away old min and max data of this aabb) /*! * Whether the bounding box contains the specified point. @@ -80,7 +81,7 @@ class AABB */ void include(const Point2LL& point); - void include(const Polygon& polygon); + void include(const PointsSet& polygon); /*! * \brief Includes the specified bounding box in the bounding box. diff --git a/include/utils/AABB3D.h b/include/utils/AABB3D.h index 402135be66..e7d4b9eda5 100644 --- a/include/utils/AABB3D.h +++ b/include/utils/AABB3D.h @@ -5,6 +5,7 @@ #define UTILS_AABB3D_H #include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" #include "utils/AABB.h" namespace cura diff --git a/include/utils/Coord_t.h b/include/utils/Coord_t.h index bd8f3e088b..ac2e4741af 100644 --- a/include/utils/Coord_t.h +++ b/include/utils/Coord_t.h @@ -6,8 +6,11 @@ // Include Clipper to get the ClipperLib::IntPoint definition, which we reuse as Point definition. +#include #include +#include "utils/types/generic.h" + namespace cura { @@ -16,7 +19,7 @@ using coord_t = ClipperLib::cInt; static inline coord_t operator"" _mu(unsigned long long i) { return static_cast(i); -}; +} #define INT2MM(n) (static_cast(n) / 1000.0) #define INT2MM2(n) (static_cast(n) / 1000000.0) @@ -27,6 +30,12 @@ static inline coord_t operator"" _mu(unsigned long long i) #define INT2MICRON(n) ((n) / 1) #define MICRON2INT(n) ((n)*1) +template +[[nodiscard]] inline coord_t lerp(coord_t a, coord_t b, FactorType t) +{ + return std::llrint(std::lerp(static_cast(a), static_cast(b), t)); +} + } // namespace cura diff --git a/include/utils/Point3D.h b/include/utils/Point3D.h index 6000d9be76..fe3b7765f8 100644 --- a/include/utils/Point3D.h +++ b/include/utils/Point3D.h @@ -7,7 +7,7 @@ #include #include -#include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" namespace cura diff --git a/include/utils/SVG.h b/include/utils/SVG.h index 96bcfa0a45..7105468f10 100644 --- a/include/utils/SVG.h +++ b/include/utils/SVG.h @@ -62,6 +62,7 @@ class SVG : NoCopy private: std::string toString(const Color color) const; std::string toString(const ColorObject& color) const; + void handleFlush(const bool flush) const; FILE* out_; // the output file const AABB aabb_; // the boundary box to display @@ -122,7 +123,7 @@ class SVG : NoCopy */ void writeLines(const std::vector& polyline, const ColorObject color = Color::BLACK) const; - void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const double head_size = 5.0) const; @@ -148,10 +149,12 @@ class SVG : NoCopy void writePolygon(Polygon poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; - void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolylines(const Shape& polys, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; void writePolyline(const Polygon& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0) const; + void writePolyline(const Polyline& poly, const ColorObject color = Color::BLACK, const double stroke_width = 1.0, const bool flush = true) const; + /*! * Draw variable-width paths into the image. * @@ -183,7 +186,7 @@ class SVG : NoCopy * \param color The color to draw the line with. * \param width_factor A multiplicative factor on the line width. */ - void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0) const; + void writeLine(const ExtrusionLine& line, const ColorObject color = Color::BLACK, const double width_factor = 1.0, const bool flush = true) const; /*! * Draws a grid across the image and writes down coordinates. diff --git a/include/utils/VoxelUtils.h b/include/utils/VoxelUtils.h index 9eaca6e447..050d03ebb6 100644 --- a/include/utils/VoxelUtils.h +++ b/include/utils/VoxelUtils.h @@ -7,7 +7,7 @@ #include #include -#include "geometry/Point2LL.h" +#include "geometry/Point3LL.h" #include "geometry/Polygon.h" namespace cura diff --git a/include/utils/math.h b/include/utils/math.h index f1ad772c72..a0650f0e8f 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -108,5 +108,33 @@ template return (dividend + divisor - 1) / divisor; } +/*! + * \brief Calculates the "inverse linear interpolation" of a value over a range, i.e. given a range [min, max] the + * value "min" would give a result of 0.0 and the value "max" would give a result of 1.0, values in between will + * be interpolated linearly. + * \note The returned value may be out of the [0.0, 1.0] range if the given value is outside the [min, max] range, it is + * up to the caller to clamp the result if required + * \note The range_min value may be greater than the range_max, inverting the interpolation logic + */ +template +[[nodiscard]] inline double inverse_lerp(T range_min, T range_max, T value) +{ + if (range_min == range_max) + { + return 0.0; + } + + return static_cast(value - range_min) / (range_max - range_min); +} + +/*! + * \brief Get a random floating point number in the range [0.0, 1.0] + */ +template +[[nodiscard]] inline T randf() +{ + return static_cast(std::rand()) / static_cast(RAND_MAX); +} + } // namespace cura #endif // UTILS_MATH_H diff --git a/include/utils/scoring/BestElementFinder.h b/include/utils/scoring/BestElementFinder.h new file mode 100644 index 0000000000..e2ede975d5 --- /dev/null +++ b/include/utils/scoring/BestElementFinder.h @@ -0,0 +1,87 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_BESTCANDIDATEFINDER_H +#define UTILS_SCORING_BESTCANDIDATEFINDER_H + +#include +#include +#include +#include + +namespace cura +{ +class ScoringCriterion; + +/*! + * This class implements an algorithm to find an element amongst a list, regarding one or multiple scoring criteria. The + * criteria are implemented by subclassing the CriterionScoring class. It is also possible to setup multiple passes of + * criteria. Thus, if the first pass gives a few best candidates that are too close to each other, we keep only the best + * candidates and process a second pass with different criteria, and so on until we have a single outsider, or no more + * criteria. + */ +class BestElementFinder +{ +private: + /*! + * Contains the index of an element in the source list, and its calculated score + */ + struct Candidate + { + size_t candidate_index; + double score{ 0.0 }; + }; + +public: + /*! + * Contains a criterion to be processed to calculate the score of an element, and the weight is has on the global + * score calculation. + */ + struct WeighedCriterion + { + std::shared_ptr criterion; + + /*! + * The weight to be given when taking this criterion into the global score. A score that contributes "normally" + * to the global score should have a weight of 1.0, and others should be adjusted around this value, to give + * them more or less influence. + */ + double weight{ 1.0 }; + }; + + /*! + * Contains multiple criteria to be processed on each element in a single pass + */ + struct CriteriaPass + { + std::vector criteria; + + /*! + * Once we have calculated the global scores of each element for this pass, we calculate the score difference + * between the best candidate and the following ones. If the following ones have a score close enough to the + * best, within this threshold, they will also be considered outsiders and will be run for the next pass. + */ + double outsider_delta_threshold{ 0.0 }; + }; + +private: + std::vector criteria_; + +public: + explicit BestElementFinder() = default; + + void appendCriteriaPass(const CriteriaPass& pass) + { + criteria_.push_back(pass); + } + + /*! + * Convenience method to add a pass with a single criterion + */ + void appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold = 0.0); + + std::optional findBestElement(const size_t candidates_count); +}; + +} // namespace cura +#endif // UTILS_SCORING_BESTCANDIDATEFINDER_H diff --git a/include/utils/scoring/CornerScoringCriterion.h b/include/utils/scoring/CornerScoringCriterion.h new file mode 100644 index 0000000000..210416f56b --- /dev/null +++ b/include/utils/scoring/CornerScoringCriterion.h @@ -0,0 +1,67 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef CORNERSCORINGCRITERION_H +#define CORNERSCORINGCRITERION_H + +#include + +#include "geometry/Point2LL.h" +#include "settings/EnumSettings.h" +#include "utils/Coord_t.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to whether the point is creating a corner or lies on a flat line. + * Depending on the given preference, concave or convex corners may get a higher score. + */ +class CornerScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const EZSeamCornerPrefType corner_preference_; + std::vector segments_sizes_; + coord_t total_length_{ 0 }; + +public: + explicit CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference); + + virtual double computeScore(const size_t candidate_index) const override; + +private: + /*! + * Some models have very sharp corners, but also have a high resolution. If a sharp corner + * consists of many points each point individual might have a shallow corner, but the + * collective angle of all nearby points is greater. To counter this the cornerAngle is + * calculated from two points within angle_query_distance of the query point, no matter + * what segment this leads us to + * \param vertex_index index of the query point + * \param angle_query_distance query range (default to 1mm) + * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] + */ + double cornerAngle(size_t vertex_index, const coord_t angle_query_distance = 1000) const; + + /*! + * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point + * is computed by travelling on the path and stopping when the distance has been reached, For example: + * |------|---------|------|--------------*---| + * H A B C N D + * In this case, H is the start point of the path and ABCD are the actual following points of the path. + * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. + * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path + * at a given physical distance. + * \param vertex_index The starting point index + * \param distance The distance we want to travel on the path, which may be positive to go forward + * or negative to go backward + * \return The position of the path a the given distance from the reference point + */ + Point2LL findNeighbourPoint(size_t vertex_index, coord_t distance) const; +}; + +} // namespace cura + +#endif // CORNERSCORINGCRITERION_H diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h new file mode 100644 index 0000000000..800e0a8c72 --- /dev/null +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -0,0 +1,51 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef DISTANCESCORINGCRITERION_H +#define DISTANCESCORINGCRITERION_H + +#include "geometry/Point2LL.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to the distance from the point to a target point. Closer points will get + * a higher score. + */ +class DistanceScoringCriterion : public ScoringCriterion +{ +public: + enum class DistanceType + { + Euclidian, // Classic euclidian distance between the points + XOnly, // Only difference on X coordinate + YOnly, // Only difference on Y coordinate + }; + +private: + const PointsSet& points_; + const Point2LL& target_pos_; + const DistanceType distance_type_; + + /*! + * Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + * distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + */ + const double distance_divider_; + +public: + explicit DistanceScoringCriterion( + const PointsSet& points, + const Point2LL& target_pos, + DistanceType distance_type = DistanceType::Euclidian, + const double distance_divider = 20.0); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // DISTANCESCORINGCRITERION_H diff --git a/include/utils/scoring/ExclusionAreaScoringCriterion.h b/include/utils/scoring/ExclusionAreaScoringCriterion.h new file mode 100644 index 0000000000..84fd0ab7a0 --- /dev/null +++ b/include/utils/scoring/ExclusionAreaScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef EXCLUSIONAREASCORINGCRITERION_H +#define EXCLUSIONAREASCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; +class Shape; + +/*! + * Criterion that will give a score according to whether the point is located inside or outside of an exclusion area. + * This is currently a binary test and the score will be either 0 or 1. + */ +class ExclusionAreaScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const Shape& exclusion_area_; + +public: + explicit ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // EXCLUSIONAREASCORINGCRITERION_H diff --git a/include/utils/scoring/RandomScoringCriterion.h b/include/utils/scoring/RandomScoringCriterion.h new file mode 100644 index 0000000000..5a577be894 --- /dev/null +++ b/include/utils/scoring/RandomScoringCriterion.h @@ -0,0 +1,27 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef RANDOMSCORINGCRITERION_H +#define RANDOMSCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +/*! + * Criterion that will give a random score whatever the element is. + */ +class RandomScoringCriterion : public ScoringCriterion +{ +public: + explicit RandomScoringCriterion(); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // RANDOMSCORINGCRITERION_H diff --git a/include/utils/scoring/ScoringCriterion.h b/include/utils/scoring/ScoringCriterion.h new file mode 100644 index 0000000000..41280b0a82 --- /dev/null +++ b/include/utils/scoring/ScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_SCORINGCRITERION_H +#define UTILS_SCORING_SCORINGCRITERION_H + +#include + +namespace cura +{ + +/*! + * Base class for implementing a selection criterion when calculating a multi-criteria score to select the best element + * amongst a list. + */ +class ScoringCriterion +{ +public: + ScoringCriterion() = default; + + virtual ~ScoringCriterion() = default; + + /*! + * \brief Computes the score of an element regarding this criterion. To ensure a proper selection, this value must + * be contained in [0.0, 1.0] and the different given scores must be evenly distributed in this range. + * \param candidate_index The index of the candidate of the original list + * \return The raw score of the element regarding this criterion + */ + virtual double computeScore(const size_t candidate_index) const = 0; +}; + +} // namespace cura + +#endif // UTILS_SCORING_SCORINGCRITERION_H diff --git a/src/ExtruderPlan.cpp b/src/ExtruderPlan.cpp index b492abc23e..58a6bb3f86 100644 --- a/src/ExtruderPlan.cpp +++ b/src/ExtruderPlan.cpp @@ -70,4 +70,18 @@ void ExtruderPlan::applyBackPressureCompensation(const Ratio back_pressure_compe 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); } } + +std::shared_ptr ExtruderPlan::findFirstPrintedMesh() const +{ + for (const GCodePath& path : paths_) + { + if (path.mesh) + { + return path.mesh; + } + } + + return nullptr; +} + } // namespace cura diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 4f394d6f36..76b3d1b719 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -173,7 +173,7 @@ void FffGcodeWriter::writeGCode(SliceDataStorage& storage, TimeKeeper& time_keep { if (extruder_is_used_in_filler_layers) { - process_layer_starting_layer_nr = -Raft::getFillerLayerCount(); + process_layer_starting_layer_nr = -static_cast(Raft::getFillerLayerCount()); break; } } @@ -735,6 +735,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_base_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -895,6 +896,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_interface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1073,6 +1075,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_surface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1477,13 +1480,14 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { LayerIndex layer_nr = std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr()); - if (layer_nr == 0 && Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("adhesion_type") == EPlatformAdhesion::BRIM) + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; + if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::BRIM) { return; // ooze shield already generated by brim } if (storage.ooze_shield.size() > 0 && layer_nr < storage.ooze_shield.size()) { - gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } } @@ -1516,7 +1520,7 @@ void FffGcodeWriter::processDraftShield(const SliceDataStorage& storage, LayerPl } } - gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& storage) @@ -1723,7 +1727,26 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); const bool spiralize = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize"); - gcode_layer.addPolygonsByOptimizer(polygons, mesh_config.inset0_config, z_seam_config, mesh.settings.get("wall_0_wipe_dist"), spiralize); + constexpr Ratio flow_ratio = 1.0; + constexpr bool always_retract = false; + constexpr bool reverse_order = false; + const std::optional start_near_location = std::nullopt; + constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; + + gcode_layer.addPolygonsByOptimizer( + polygons, + mesh_config.inset0_config, + mesh.settings, + z_seam_config, + mesh.settings.get("wall_0_wipe_dist"), + spiralize, + flow_ratio, + always_retract, + reverse_order, + start_near_location, + scarf_seam, + smooth_speed); addMeshOpenPolyLinesToGCode(mesh, mesh_config, gcode_layer); } @@ -1966,7 +1989,7 @@ bool FffGcodeWriter::processMultiLayerInfill( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(infill_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx]); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx], mesh.settings); } if (! infill_lines.empty()) @@ -2064,14 +2087,10 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& // We do this because supporting lines are hanging over air, // and therefore print best as part of a continuous print move, // rather than having a travel move before and after them. -// -// We also double-insert most lines, since that allows the -// elimination of all travel moves, and overlap isn't an issue -// because the lines are hanging. void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& line_to_add) { // Returns the line index and the index of the point within an infill_line, null for no match found. - const auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](const Point2LL& p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { @@ -2101,59 +2120,76 @@ void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& lin { /* both ends intersect with the same line. * If the inserted line has ends x, y - * and the original line was A--(x)--B---C--(y)--D - * Then the new line will be A--x--y--C---B--x--y--D - * Note that the middle part of the line is reversed. + * and the original line was ...--A--(x)--B--...--C--(y)--D--... + * Then the new lines will be ...--A--x--y--C--...--B--x + * And line y--D--... + * Note that some parts of the line are reversed, + * and the last one is completly split apart. */ OpenPolyline& old_line = infill_lines[front_line_index]; - OpenPolyline new_line; + OpenPolyline new_line_start; + OpenPolyline new_line_end; Point2LL x, y; - size_t x_index, y_index; + std::ptrdiff_t x_index, y_index; if (front_point_index < back_point_index) { x = line_to_add.front(); y = line_to_add.back(); - x_index = front_point_index; - y_index = back_point_index; + x_index = static_cast(front_point_index); + y_index = static_cast(back_point_index); } else { y = line_to_add.front(); x = line_to_add.back(); - y_index = front_point_index; - x_index = back_point_index; + y_index = static_cast(front_point_index); + x_index = static_cast(back_point_index); } - new_line.insert(new_line.end(), old_line.begin(), old_line.begin() + x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.rend() - y_index, old_line.rend() - x_index); - new_line.push_back(x); - new_line.push_back(y); - new_line.insert(new_line.end(), old_line.begin() + y_index, old_line.end()); - old_line.setPoints(std::move(new_line.getPoints())); + + new_line_start.insert(new_line_start.end(), old_line.begin(), old_line.begin() + x_index); + new_line_start.push_back(x); + new_line_start.push_back(y); + new_line_start.insert(new_line_start.end(), old_line.rend() - y_index, old_line.rend() - x_index); + new_line_start.push_back(x); + + new_line_end.push_back(y); + new_line_end.insert(new_line_end.end(), old_line.begin() + y_index, old_line.end()); + + old_line.setPoints(std::move(new_line_start.getPoints())); + infill_lines.push_back(new_line_end); } else { /* Different lines * If the line_to_add has ends [front, back] - * Existing line (intersects front): A B front C D E - * Other existing line (intersects back): M N back O P Q - * Result is Line: A B front back O P Q - * And line: M N back front C D E + * Existing line (intersects front): ...--A--(x)--B--... + * Other existing line (intersects back): ...--C--(y)--D--... + * Result is Line: ...--A--x--y--D--... + * And line: x--B--... + * And line: ...--C--y */ OpenPolyline& old_front = infill_lines[front_line_index]; OpenPolyline& old_back = infill_lines[back_line_index]; - OpenPolyline new_front, new_back; - new_front.insert(new_front.end(), old_front.begin(), old_front.begin() + front_point_index); - new_front.push_back(line_to_add.front()); - new_front.push_back(line_to_add.back()); - new_front.insert(new_front.end(), old_back.begin() + back_point_index, old_back.end()); - new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + back_point_index); - new_back.push_back(line_to_add.back()); - new_back.push_back(line_to_add.front()); - new_back.insert(new_back.end(), old_front.begin() + front_point_index, old_front.end()); + OpenPolyline full_line, new_front, new_back; + const Point2LL x = line_to_add.front(); + const Point2LL y = line_to_add.back(); + const auto x_index = static_cast(front_point_index); + const auto y_index = static_cast(back_point_index); + + new_front.push_back(x); + new_front.insert(new_front.end(), old_front.begin() + x_index, old_front.end()); + + new_back.insert(new_back.end(), old_back.begin(), old_back.begin() + y_index); + new_back.push_back(y); + + full_line.insert(full_line.end(), old_front.begin(), old_front.begin() + x_index); + full_line.push_back(x); + full_line.push_back(y); + full_line.insert(full_line.end(), old_back.begin() + y_index, old_back.end()); + old_front.setPoints(std::move(new_front.getPoints())); old_back.setPoints(std::move(new_back.getPoints())); + infill_lines.push_back(full_line); } } else @@ -2173,7 +2209,7 @@ void wall_tool_paths2lines(const std::vector>& w { for (const ExtrusionLine& c : b) { - const Polygon& poly = c.toPolygon(); + const Polygon poly = c.toPolygon(); if (c.is_closed_) { result.push_back(poly.toPseudoOpenPolyline()); @@ -2330,12 +2366,12 @@ void addExtraLinesToSupportSurfacesAbove( // invert the supported_area by adding one huge polygon around the outside supported_area.push_back(AABB{ supported_area }.toPolygon()); - const Shape& inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); + const Shape inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); OpenLinesSet unsupported_line_segments = inv_supported_area.intersection(printed_lines_on_layer_above); // This is to work around a rounding issue in the shape library with border points. - const Shape& expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); + const Shape expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. infill_line_width, @@ -2345,7 +2381,7 @@ void addExtraLinesToSupportSurfacesAbove( for (const OpenPolyline& a : unsupported_line_segments) { - const OpenPolyline& simplified = s.polyline(a); + const OpenPolyline simplified = s.polyline(a); for (const Point2LL& point : simplified) { size_t idx = expanded_inv_supported_area.findInside(point); @@ -2710,7 +2746,7 @@ bool FffGcodeWriter::processSingleLayerInfill( constexpr bool force_comb_retract = false; // start the infill polygons at the nearest vertex to the current location gcode_layer.addTravel(PolygonUtils::findNearestVert(gcode_layer.getLastPlannedPositionOrStartingPosition(), infill_polygons).p(), force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], mesh.settings, ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); } const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC @@ -2860,7 +2896,7 @@ size_t FffGcodeWriter::findUsedExtruderIndex(const SliceDataStorage& storage, co { return last ? extruder_use.back().extruder_nr : extruder_use.front().extruder_nr; } - else if (layer_nr <= -Raft::getTotalExtraLayers()) + else if (layer_nr <= -LayerIndex(Raft::getTotalExtraLayers())) { // Asking for extruder use below first layer, give first extruder return getStartExtruder(storage); @@ -2952,7 +2988,7 @@ bool FffGcodeWriter::processInsets( gcode_layer.addTravel(spiral_inset[spiral_start_vertex]); } int wall_0_wipe_dist(0); - gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, ZSeamConfig(), wall_0_wipe_dist); + gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, mesh.settings, ZSeamConfig(), wall_0_wipe_dist); } } // for non-spiralized layers, determine the shape of the unsupported areas below this part @@ -3123,6 +3159,9 @@ bool FffGcodeWriter::processInsets( mesh.getZSeamHint(), mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); + const Shape disallowed_areas_for_seams; + constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; InsetOrderOptimizer wall_orderer( *this, storage, @@ -3142,7 +3181,11 @@ bool FffGcodeWriter::processInsets( mesh.settings.get("wall_x_extruder_nr").extruder_nr_, z_seam_config, part.wall_toolpaths, - mesh.bounding_box.flatten().getMiddle()); + mesh.bounding_box.flatten().getMiddle(), + disallowed_areas_for_seams, + scarf_seam, + smooth_speed, + gcode_layer.getSeamOverhangMask()); added_something |= wall_orderer.addToLayer(); } return added_something; @@ -3576,7 +3619,7 @@ void FffGcodeWriter::processSkinPrintFeature( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(skin_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(skin_polygons, config); + gcode_layer.addPolygonsByOptimizer(skin_polygons, config, mesh.settings); } if (monotonic) @@ -3981,6 +4024,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer gcode_layer.addPolygonsByOptimizer( support_polygons, configs[combine_idx], + mesh_group_settings, z_seam_config, wall_0_wipe_dist, spiralize, @@ -4147,13 +4191,13 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support if (gcode_layer.getLayerNr() == 0) { - gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config, roof_extruder.settings_); } if (! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config, roof_extruder.settings_); } if (! roof_paths.empty()) { @@ -4267,7 +4311,7 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L { constexpr bool force_comb_retract = false; gcode_layer.addTravel(bottom_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config); + gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config, bottom_extruder.settings_); } if (! bottom_paths.empty()) { @@ -4417,6 +4461,7 @@ void FffGcodeWriter::finalize() } gcode.writeComment("End of Gcode"); + gcode.flushOutputStream(); /* the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB. gcode.writeCode("M25 ;Stop reading from this point on."); diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 0a0351e460..68db1cf1c1 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -213,15 +213,6 @@ bool FffPolygonGenerator::sliceModel(MeshGroup* meshgroup, TimeKeeper& timeKeepe slicerList.push_back(slicer); - /* - for(SlicerLayer& layer : slicer->layers) - { - //Reporting the outline here slows down the engine quite a bit, so only do so when debugging. - sendPolygons("outline", layer_nr, layer.z, layer.polygonList); - sendPolygons("openoutline", layer_nr, layer.openPolygonList); - } - */ - Progress::messageProgress(Progress::Stage::SLICING, mesh_idx + 1, meshgroup->meshes.size()); } diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index ad036f62b9..7c8723bb95 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -52,7 +52,10 @@ InsetOrderOptimizer::InsetOrderOptimizer( const ZSeamConfig& z_seam_config, const std::vector& paths, const Point2LL& model_center_point, - const Shape& disallowed_areas_for_seams) + const Shape& disallowed_areas_for_seams, + const bool scarf_seam, + const bool smooth_speed, + const Shape& overhang_areas) : gcode_writer_(gcode_writer) , storage_(storage) , gcode_layer_(gcode_layer) @@ -74,6 +77,9 @@ InsetOrderOptimizer::InsetOrderOptimizer( , layer_nr_(gcode_layer.getLayerNr()) , model_center_point_(model_center_point) , disallowed_areas_for_seams_{ disallowed_areas_for_seams } + , scarf_seam_(scarf_seam) + , smooth_speed_(smooth_speed) + , overhang_areas_(overhang_areas) { } @@ -89,6 +95,7 @@ bool InsetOrderOptimizer::addToLayer() const bool current_extruder_is_wall_x = wall_x_extruder_nr_ == extruder_nr_; const bool reverse = shouldReversePath(use_one_extruder, current_extruder_is_wall_x, outer_to_inner); + const bool use_shortest_for_inner_walls = outer_to_inner; auto walls_to_be_added = getWallsToBeAdded(reverse, use_one_extruder); const auto order = pack_by_inset ? getInsetOrder(walls_to_be_added, outer_to_inner) : getRegionOrder(walls_to_be_added, outer_to_inner); @@ -110,7 +117,9 @@ bool InsetOrderOptimizer::addToLayer() reverse, order, group_outer_walls, - disallowed_areas_for_seams_); + disallowed_areas_for_seams_, + use_shortest_for_inner_walls, + overhang_areas_); for (auto& line : walls_to_be_added) { @@ -122,7 +131,7 @@ bool InsetOrderOptimizer::addToLayer() // If the user indicated that we may deviate from the vertices for the seam, we can insert a seam point, if needed. force_start = insertSeamPoint(line); } - order_optimizer.addPolygon(&line, force_start); + order_optimizer.addPolygon(&line, force_start, line.is_outer_wall()); } else { @@ -146,6 +155,8 @@ bool InsetOrderOptimizer::addToLayer() const GCodePathConfig& bridge_config = is_outer_wall ? inset_0_bridge_config_ : inset_X_bridge_config_; const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist_ : wall_x_wipe_dist_; const bool retract_before = is_outer_wall ? retract_before_outer_wall_ : false; + const bool scarf_seam = scarf_seam_ && is_outer_wall; + const bool smooth_speed = smooth_speed_ && is_outer_wall; const bool revert_inset = alternate_walls && (path.vertices_->inset_idx_ % 2 != 0); const bool revert_layer = alternate_walls && (layer_nr_ % 2 != 0); @@ -166,7 +177,9 @@ bool InsetOrderOptimizer::addToLayer() retract_before, path.is_closed_, backwards, - linked_path); + linked_path, + scarf_seam, + smooth_speed); added_something = true; } return added_something; @@ -194,7 +207,7 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed Point2LL closest_point; size_t closest_junction_idx = 0; coord_t closest_distance_sqd = std::numeric_limits::max(); - bool should_reclaculate_closest = false; + bool should_recalculate_closest = false; if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED) { // For user-defined seams you usually don't _actually_ want the _closest_ point, per-se, @@ -240,24 +253,27 @@ std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed closest_junction_idx = i; } } - should_reclaculate_closest = true; + should_recalculate_closest = true; } const auto& start_pt = closed_line.junctions_[closest_junction_idx]; const auto& end_pt = closed_line.junctions_[(closest_junction_idx + 1) % closed_line.junctions_.size()]; - if (should_reclaculate_closest) + if (should_recalculate_closest) { // In the second case (see above) the closest point hasn't actually been calculated yet, // since in that case we'de need the start and end points. So do that here. closest_point = LinearAlg2D::getClosestOnLineSegment(request_point, start_pt.p_, end_pt.p_); } constexpr coord_t smallest_dist_sqd = 25; - if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd || vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd) { - // Early out if the closest point is too close to the start or end point. - // NOTE: Maybe return the index here anyway, since this is the point the current caller would want to force the seam to. - // However, then the index returned would have a caveat that it _can_ point to an already exisiting point then. - return std::nullopt; + // If the closest point is very close to the start point, just use it instead. + return closest_junction_idx; + } + if (vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) + { + // If the closest point is very close to the end point, just use it instead. + return (closest_junction_idx + 1) % closed_line.junctions_.size(); } // NOTE: This could also be done on a single axis (skipping the implied sqrt), but figuring out which one and then using the right values became a bit messy/verbose. diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index cd46b5e850..39f3e1b510 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -14,6 +14,7 @@ #include "Application.h" //To communicate layer view data. #include "ExtruderTrain.h" +#include "PathAdapter.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. #include "Slice.h" #include "WipeScriptConfig.h" @@ -24,6 +25,7 @@ #include "pathPlanning/CombPaths.h" #include "plugins/slots.h" #include "raft.h" // getTotalExtraLayers +#include "range/v3/view/chunk_by.hpp" #include "settings/types/Ratio.h" #include "sliceDataStorage.h" #include "utils/Simplify.h" @@ -188,14 +190,16 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) { continue; } + + constexpr coord_t extra_offset = 10; // Additional offset to avoid zero-width polygons remains coord_t offset; switch (boundary_type) { case CombBoundary::MINIMUM: - offset = -mesh.settings.get("machine_nozzle_size") / 2 - mesh.settings.get("wall_line_width_0") / 2; + offset = -mesh.settings.get("machine_nozzle_size") / 2 - mesh.settings.get("wall_line_width_0") / 2 - extra_offset; break; case CombBoundary::PREFERRED: - offset = -mesh.settings.get("machine_nozzle_size") * 3 / 2 - mesh.settings.get("wall_line_width_0") / 2; + offset = -mesh.settings.get("machine_nozzle_size") * 3 / 2 - mesh.settings.get("wall_line_width_0") / 2 - extra_offset; break; default: offset = 0; @@ -206,36 +210,37 @@ Shape LayerPlan::computeCombBoundary(const CombBoundary boundary_type) const CombingMode combing_mode = mesh.settings.get("retraction_combing"); for (const SliceLayerPart& part : layer.parts) { - if (combing_mode == CombingMode::ALL) // Add the increased outline offset (skin, infill and part of the inner walls) - { - comb_boundary.push_back(part.outline.offset(offset)); - } - else if (combing_mode == CombingMode::NO_SKIN) // Add the increased outline offset, subtract skin (infill and part of the inner walls) + Shape part_combing_boundary; + + if (combing_mode == CombingMode::INFILL) { - comb_boundary.push_back(part.outline.offset(offset).difference(part.inner_area.difference(part.infill_area))); + part_combing_boundary = part.infill_area; } - else if (combing_mode == CombingMode::NO_OUTER_SURFACES) + else { - Shape top_and_bottom_most_fill; - for (const SliceLayerPart& outer_surface_part : layer.parts) + part_combing_boundary = part.outline.offset(offset); + + if (combing_mode == CombingMode::NO_SKIN) // Add the increased outline offset, subtract skin (infill and part of the inner walls) + { + part_combing_boundary = part_combing_boundary.difference(part.inner_area.difference(part.infill_area)); + } + else if (combing_mode == CombingMode::NO_OUTER_SURFACES) { - for (const SkinPart& skin_part : outer_surface_part.skin_parts) + for (const SliceLayerPart& outer_surface_part : layer.parts) { - top_and_bottom_most_fill.push_back(skin_part.top_most_surface_fill); - top_and_bottom_most_fill.push_back(skin_part.bottom_most_surface_fill); + part_combing_boundary = part_combing_boundary.difference(outer_surface_part.top_most_surface); + part_combing_boundary = part_combing_boundary.difference(outer_surface_part.bottom_most_surface); } } - comb_boundary.push_back(part.outline.offset(offset).difference(top_and_bottom_most_fill)); - } - else if (combing_mode == CombingMode::INFILL) // Add the infill (infill only) - { - comb_boundary.push_back(part.infill_area); } + + comb_boundary.push_back(part_combing_boundary); } } break; } } + return comb_boundary; } @@ -448,7 +453,7 @@ GCodePath& LayerPlan::addTravel(const Point2LL& p, const bool force_retract, con } for (Point2LL& comb_point : combPath) { - if (path->points.empty() || vSize2(path->points.back() - comb_point) > maximum_travel_resolution * maximum_travel_resolution) + if (path->points.empty() || (path->points.back() - comb_point).vSize2() > maximum_travel_resolution * maximum_travel_resolution) { path->points.push_back(comb_point); distance += vSize(last_point - comb_point); @@ -531,73 +536,116 @@ void LayerPlan::planPrime(double prime_blob_wipe_length) } void LayerPlan::addExtrusionMove( - const Point2LL p, + const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, const bool spiralize, const Ratio speed_factor, - const double fan_speed) + const double fan_speed, + const bool travel_to_z) { GCodePath* path = getLatestPathWithConfig(config, space_fill_type, config.z_offset, flow, width_factor, spiralize, speed_factor); path->points.push_back(p); path->setFanSpeed(fan_speed); + path->travel_to_z = travel_to_z; if (! static_cast(first_extrusion_acc_jerk_)) { first_extrusion_acc_jerk_ = std::make_pair(path->config.getAcceleration(), path->config.getJerk()); } - last_planned_position_ = p; + last_planned_position_ = p.toPoint2LL(); +} + +template +void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position) +{ + if (path.size() >= 2 && wipe_distance > 0) + { + const int direction = backwards ? -1 : 1; + Point2LL p0 = last_path_position; + int distance_traversed = 0; + size_t index = start_index; + while (distance_traversed < wipe_distance) + { + index = static_cast((index + direction + path.size()) % path.size()); + if (index == start_index && distance_traversed == 0) + { + // Wall has a total circumference of 0. This loop would never end. + break; + } + + const Point2LL& p1 = path.pointAt(index); + const int p0p1_dist = vSize(p1 - p0); + if (distance_traversed + p0p1_dist >= wipe_distance) + { + Point2LL vector = p1 - p0; + Point2LL half_way = p0 + normal(vector, wipe_distance - distance_traversed); + addTravel_simple(half_way); + } + else + { + addTravel_simple(p1); + } + + distance_traversed += p0p1_dist; + p0 = p1; + } + + forceNewPathStart(); + } } void LayerPlan::addPolygon( const Polygon& polygon, int start_idx, const bool backwards, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio& flow_ratio, - bool always_retract) + bool always_retract, + bool scarf_seam, + bool smooth_speed) { - constexpr Ratio width_ratio = 1.0_r; // Not printed with variable line width. + constexpr bool is_closed = true; + constexpr bool is_candidate_small_feature = false; + const PathAdapter path_adapter(polygon, config.getLineWidth()); + Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); - const int direction = backwards ? -1 : 1; - for (size_t point_idx = 1; point_idx < polygon.size(); point_idx++) - { - Point2LL p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()]; - addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - p0 = p1; - } + + const std::tuple add_wall_result = addWallWithScarfSeam( + path_adapter, + start_idx, + settings, + config, + flow_ratio, + always_retract, + is_closed, + backwards, + is_candidate_small_feature, + scarf_seam, + smooth_speed, + [this, &config, &spiralize]( + const Point3LL& /*start*/, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t /*distance_to_bridge_start*/) + { + constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; + constexpr bool travel_to_z = false; + + addExtrusionMove(end, config, SpaceFillType::Polygons, actual_flow_ratio, line_width_ratio, spiralize, speed_factor, fan_speed, travel_to_z); + }); + + if (polygon.size() > 2) { - addExtrusionMove(polygon[start_idx], config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - - if (wall_0_wipe_dist > 0) - { // apply outer wall wipe - p0 = polygon[start_idx]; - int distance_traversed = 0; - for (size_t point_idx = 1;; point_idx++) - { - Point2LL p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()]; - int p0p1_dist = vSize(p1 - p0); - if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) - { - Point2LL vector = p1 - p0; - Point2LL half_way = p0 + normal(vector, wall_0_wipe_dist - distance_traversed); - addTravel_simple(half_way); - break; - } - else - { - addTravel_simple(p1); - distance_traversed += p0p1_dist; - } - p0 = p1; - } - forceNewPathStart(); - } + addWipeTravel(path_adapter, wall_0_wipe_dist, backwards, get<0>(add_wall_result), get<1>(add_wall_result)); } else { @@ -608,13 +656,16 @@ void LayerPlan::addPolygon( void LayerPlan::addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, const ZSeamConfig& z_seam_config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio flow_ratio, bool always_retract, bool reverse_order, - const std::optional start_near_location) + const std::optional start_near_location, + bool scarf_seam, + bool smooth_speed) { if (polygons.empty()) { @@ -627,28 +678,41 @@ void LayerPlan::addPolygonsByOptimizer( } orderOptimizer.optimize(); - if (! reverse_order) + const auto add_polygons + = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &always_retract, &scarf_seam, &smooth_speed](const auto& iterator_begin, const auto& iterator_end) { - for (const PathOrdering& path : orderOptimizer.paths_) + for (auto iterator = iterator_begin; iterator != iterator_end; ++iterator) { - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); + addPolygon( + *iterator->vertices_, + iterator->start_vertex_, + iterator->backwards_, + settings, + config, + wall_0_wipe_dist, + spiralize, + flow_ratio, + always_retract, + scarf_seam, + smooth_speed); } + }; + + if (! reverse_order) + { + add_polygons(orderOptimizer.paths_.begin(), orderOptimizer.paths_.end()); } else { - for (int index = orderOptimizer.paths_.size() - 1; index >= 0; --index) - { - const PathOrdering& path = orderOptimizer.paths_[index]; - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); - } + add_polygons(orderOptimizer.paths_.rbegin(), orderOptimizer.paths_.rend()); } } static constexpr double max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate void LayerPlan::addWallLine( - const Point2LL& p0, - const Point2LL& p1, + const Point3LL& p0, + const Point3LL& p1, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -657,7 +721,8 @@ void LayerPlan::addWallLine( const Ratio width_factor, double& non_bridge_line_volume, Ratio speed_factor, - double distance_to_bridge_start) + double distance_to_bridge_start, + const bool travel_to_z) { const coord_t min_line_len = 5; // we ignore lines less than 5um long const double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length @@ -668,7 +733,7 @@ void LayerPlan::addWallLine( const Ratio bridge_wall_coast = settings.get("bridge_wall_coast"); const Ratio overhang_speed_factor = settings.get("wall_overhang_speed_factor"); - Point2LL cur_point = p0; + Point3LL cur_point = p0; // helper function to add a single non-bridge line @@ -676,14 +741,14 @@ void LayerPlan::addWallLine( // alternatively, if the line follows a bridge line, it may be segmented and the print speed gradually increased to reduce under-extrusion - auto addNonBridgeLine = [&](const Point2LL& line_end) + auto addNonBridgeLine = [&](const Point3LL& line_end) { - coord_t distance_to_line_end = vSize(cur_point - line_end); + coord_t distance_to_line_end = (cur_point - line_end).vSize(); while (distance_to_line_end > min_line_len) { // if we are accelerating after a bridge line, the segment length is less than the whole line length - Point2LL segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) + Point3LL segment_end = (speed_factor == 1 || distance_to_line_end < acceleration_segment_len) ? line_end : cur_point + (line_end - cur_point) * acceleration_segment_len / distance_to_line_end; @@ -708,7 +773,7 @@ void LayerPlan::addWallLine( segment_end = line_end; } - const coord_t len = vSize(cur_point - segment_end); + const coord_t len = (cur_point - segment_end).vSize(); if (coast_dist > 0 && ((distance_to_bridge_start - len) <= coast_dist)) { if ((len - coast_dist) > min_line_len) @@ -721,7 +786,9 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - speed_factor); + speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } // then coast to start of bridge segment constexpr Ratio no_flow = 0.0_r; // Coasting has no flow rate. @@ -737,7 +804,9 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } distance_to_bridge_start -= len; @@ -752,16 +821,18 @@ void LayerPlan::addWallLine( segment_flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? speed_factor : overhang_speed_factor); + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } - non_bridge_line_volume += vSize(cur_point - segment_end) * segment_flow * width_factor * speed_factor * default_config.getSpeed(); + non_bridge_line_volume += (cur_point - segment_end).vSize() * segment_flow * width_factor * speed_factor * default_config.getSpeed(); cur_point = segment_end; speed_factor = 1 - (1 - speed_factor) * acceleration_factor; if (speed_factor >= 0.9) { speed_factor = 1.0; } - distance_to_line_end = vSize(cur_point - line_end); + distance_to_line_end = (cur_point - line_end).vSize(); } }; @@ -773,7 +844,7 @@ void LayerPlan::addWallLine( // what part of the line segment will be printed with what config. return false; } - return PolygonUtils::polygonCollidesWithLineSegment(roofing_mask_, p0, p1) || roofing_mask_.inside(p1, true); + return PolygonUtils::polygonCollidesWithLineSegment(roofing_mask_, p0.toPoint2LL(), p1.toPoint2LL()) || roofing_mask_.inside(p1.toPoint2LL(), true); }(); if (use_roofing_config) @@ -785,7 +856,7 @@ void LayerPlan::addWallLine( // to the first and last point of the intersected line segments alternating between // roofing and default_config's. OpenLinesSet line_polys; - line_polys.addSegment(p0, p1); + line_polys.addSegment(p0.toPoint2LL(), p1.toPoint2LL()); constexpr bool restitch = false; // only a single line doesn't need stitching auto roofing_line_segments = roofing_mask_.intersection(line_polys, restitch); @@ -794,7 +865,7 @@ void LayerPlan::addWallLine( // roofing_line_segments should never be empty since we already checked that the line segment // intersects with the roofing area. But if it is empty then just print the line segment // using the default_config. - addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } else { @@ -823,16 +894,25 @@ void LayerPlan::addWallLine( // if the start of the line segment is not at minimum distance from p0 if (vSize2(line_poly.front() - p0) > min_line_len * min_line_len) { - addExtrusionMove(line_poly.front(), default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove( + line_poly.front(), + default_config, + SpaceFillType::Polygons, + flow, + width_factor, + spiralize, + 1.0_r, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } - addExtrusionMove(line_poly.back(), roofing_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(line_poly.back(), roofing_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } // if the last point is not yet at a minimum distance from p1 then add a move to p1 if (vSize2(roofing_line_segments.back().back() - p1) > min_line_len * min_line_len) { - addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r); + addExtrusionMove(p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } } } @@ -846,19 +926,21 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - (overhang_mask_.empty() || (! overhang_mask_.inside(p0, true) && ! overhang_mask_.inside(p1, true))) ? 1.0_r : overhang_speed_factor); + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); } else { // bridges may be required - if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0, p1)) + if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.toPoint2LL(), p1.toPoint2LL())) { // the line crosses the boundary between supported and non-supported regions so one or more bridges are required // determine which segments of the line are bridges OpenLinesSet line_polys; - line_polys.addSegment(p0, p1); + line_polys.addSegment(p0.toPoint2LL(), p1.toPoint2LL()); constexpr bool restitch = false; // only a single line doesn't need stitching line_polys = bridge_wall_mask_.intersection(line_polys, restitch); @@ -868,10 +950,10 @@ void LayerPlan::addWallLine( { // find the bridge line segment that's nearest to the current point size_t nearest = 0; - double smallest_dist2 = vSize2f(cur_point - line_polys[0][0]); + double smallest_dist2 = (cur_point - line_polys[0][0]).vSize2f(); for (size_t i = 1; i < line_polys.size(); ++i) { - double dist2 = vSize2f(cur_point - line_polys[i][0]); + double dist2 = (cur_point - line_polys[i][0]).vSize2f(); if (dist2 < smallest_dist2) { nearest = i; @@ -881,10 +963,10 @@ void LayerPlan::addWallLine( const OpenPolyline& bridge = line_polys[nearest]; // set b0 to the nearest vertex and b1 the furthest - Point2LL b0 = bridge[0]; - Point2LL b1 = bridge[1]; + Point3LL b0 = bridge[0]; + Point3LL b1 = bridge[1]; - if (vSize2f(cur_point - b1) < vSize2f(cur_point - b0)) + if ((cur_point - b1).vSize2f() < (cur_point - b0).vSize2f()) { // swap vertex order b0 = bridge[1]; @@ -895,7 +977,7 @@ void LayerPlan::addWallLine( addNonBridgeLine(b0); - const double bridge_line_len = vSize(b1 - cur_point); + const double bridge_line_len = (b1 - cur_point).vSize(); if (bridge_line_len >= min_bridge_line_len) { @@ -903,7 +985,7 @@ void LayerPlan::addWallLine( if (bridge_line_len > min_line_len) { - addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor); + addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); non_bridge_line_volume = 0; cur_point = b1; // after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag @@ -924,7 +1006,7 @@ void LayerPlan::addWallLine( // if we haven't yet reached p1, fill the gap with default_config line addNonBridgeLine(p1); } - else if (bridge_wall_mask_.inside(p0, true) && vSize(p0 - p1) >= min_bridge_line_len) + else if (bridge_wall_mask_.inside(p0.toPoint2LL(), true) && (p0 - p1).vSize() >= min_bridge_line_len) { // both p0 and p1 must be above air (the result will be ugly!) addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); @@ -940,7 +1022,7 @@ void LayerPlan::addWallLine( void LayerPlan::addWall( const Polygon& wall, - int start_idx, + size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, const GCodePathConfig& roofing_config, @@ -975,146 +1057,77 @@ void LayerPlan::addWall( addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } -void LayerPlan::addWall( - const ExtrusionLine& wall, - int start_idx, - const Settings& settings, +template +std::tuple LayerPlan::addSplitWall( + const PathAdapter& wall, + const coord_t wall_length, + const size_t start_idx, + const size_t max_index, + const int direction, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - coord_t wall_0_wipe_dist, - double flow_ratio, - bool always_retract, - const bool is_closed, - const bool is_reversed, - const bool is_linked_path) + const bool always_retract, + const bool is_small_feature, + Ratio small_feature_speed_factor, + const coord_t max_area_deviation, + const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, + const auto scarf_seam_length, + const auto scarf_seam_start_ratio, + const auto scarf_split_distance, + const coord_t scarf_max_z_offset, + const coord_t speed_split_distance, + const Ratio start_speed_ratio, + const coord_t accelerate_length, + const Ratio end_speed_ratio, + const coord_t decelerate_length, + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const AddExtrusionSegmentFunction& func_add_segment) { - if (wall.empty()) - { - return; - } - if (is_closed) - { - // make sure wall start point is not above air! - start_idx = locateFirstSupportedVertex(wall, start_idx); - } - - double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output - double speed_factor = 1.0; // start first line at normal speed coord_t distance_to_bridge_start = 0; // will be updated before each line is processed - - const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); - - const Ratio nominal_line_width_multiplier{ - 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } - }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this - - // helper function to calculate the distance from the start of the current wall line to the first bridge segment - - auto computeDistanceToBridgeStart = [&](unsigned current_index) - { - distance_to_bridge_start = 0; - - if (! bridge_wall_mask_.empty()) - { - // there is air below the part so iterate through the lines that have not yet been output accumulating the total distance to the first bridge segment - for (unsigned point_idx = current_index; point_idx < wall.size(); ++point_idx) + Point2LL p0 = wall.pointAt(start_idx); + coord_t w0 = wall.lineWidthAt(start_idx); + bool first_line = ! is_scarf_closure; + bool first_split = ! is_scarf_closure; + Point3LL split_origin = p0; + if (! is_scarf_closure && scarf_seam_length > 0) + { + split_origin.z_ = scarf_max_z_offset; + } + + coord_t wall_processed_distance = 0; // This will grow while we travel along the wall, to the total wall length + double scarf_factor_origin = 0.0; // Interpolation factor at the current point for the scarf + double accelerate_factor_origin = 0.0; // Interpolation factor at the current point for the acceleration + double decelerate_factor_origin = 0.0; // Interpolation factor at the current point for the deceleration + const coord_t start_decelerate_position = wall_length - decelerate_length; + Point3LL split_destination = p0; + size_t previous_point_index = start_idx; + bool keep_processing = true; + + for (size_t point_idx = 1; point_idx < max_index && keep_processing; point_idx++) + { + const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); + previous_point_index = (wall.size() + start_idx + (point_idx - 1) * direction) % wall.size(); + const Point2LL& p1 = wall.pointAt(actual_point_index); + const coord_t w1 = wall.lineWidthAt(actual_point_index); + coord_t segment_processed_distance = 0; + + if constexpr (std::is_same_v) + { + // The bridging functionality has not been designed to work with anything else than ExtrusionLine objects, + // and there is no need to do it otherwise yet. So the compute_distance_to_bridge_start argument will + // just be ignored if using an other PathType (e.g. Polygon) + if (compute_distance_to_bridge_start && ! bridge_wall_mask_.empty()) { - const ExtrusionJunction& p0 = wall[point_idx]; - const ExtrusionJunction& p1 = wall[(point_idx + 1) % wall.size()]; - - if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.p_, p1.p_)) - { - // the line crosses the boundary between supported and non-supported regions so it will contain one or more bridge segments - - // determine which segments of the line are bridges - - OpenLinesSet line_polys; - line_polys.addSegment(p0.p_, p1.p_); - constexpr bool restitch = false; // only a single line doesn't need stitching - line_polys = bridge_wall_mask_.intersection(line_polys, restitch); - - while (line_polys.size() > 0) - { - // find the bridge line segment that's nearest to p0 - size_t nearest = 0; - double smallest_dist2 = vSize2f(p0.p_ - line_polys[0][0]); - for (unsigned i = 1; i < line_polys.size(); ++i) - { - double dist2 = vSize2f(p0.p_ - line_polys[i][0]); - if (dist2 < smallest_dist2) - { - nearest = i; - smallest_dist2 = dist2; - } - } - const OpenPolyline& bridge = line_polys[nearest]; - - // set b0 to the nearest vertex and b1 the furthest - Point2LL b0 = bridge[0]; - Point2LL b1 = bridge[1]; - - if (vSize2f(p0.p_ - b1) < vSize2f(p0.p_ - b0)) - { - // swap vertex order - b0 = bridge[1]; - b1 = bridge[0]; - } - - distance_to_bridge_start += vSize(b0 - p0.p_); - - const double bridge_line_len = vSize(b1 - b0); - - if (bridge_line_len >= min_bridge_line_len) - { - // job done, we have found the first bridge line - return; - } - - distance_to_bridge_start += bridge_line_len; - - // finished with this segment - line_polys.removeAt(nearest); - } - } - else if (! bridge_wall_mask_.inside(p0.p_, true)) - { - // none of the line is over air - distance_to_bridge_start += vSize(p1.p_ - p0.p_); - } + distance_to_bridge_start = computeDistanceToBridgeStart(wall.getPath(), (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); } - - // we have got all the way to the end of the wall without finding a bridge segment so disable coasting by setting distance_to_bridge_start back to 0 - - distance_to_bridge_start = 0; - } - }; - - bool first_line = true; - const coord_t small_feature_max_length = settings.get("small_feature_max_length"); - const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall.shorterThan(small_feature_max_length); - Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); - const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; - small_feature_speed_factor = std::max((double)small_feature_speed_factor, (double)(min_speed / default_config.getSpeed())); - const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! - const coord_t max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); - - ExtrusionJunction p0 = wall[start_idx]; - - const int direction = is_reversed ? -1 : 1; - const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); - for (size_t point_idx = 1; point_idx < max_index; point_idx++) - { - const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; - - if (! bridge_wall_mask_.empty()) - { - computeDistanceToBridgeStart((wall.size() + start_idx + point_idx * direction - 1) % wall.size()); } if (first_line) { - addTravel(p0.p_, always_retract); + addTravel(p0, always_retract); first_line = false; } @@ -1130,8 +1143,8 @@ void LayerPlan::addWall( pieces we'd want to get low enough deviation, then check if each piece is not too short at the end. */ - const coord_t delta_line_width = p1.w_ - p0.w_; - const Point2LL line_vector = p1.p_ - p0.p_; + const coord_t delta_line_width = w1 - w0; + const Point2LL line_vector = p1 - p0; const coord_t line_length = vSize(line_vector); /* Calculate how much the line would deviate from the trapezoidal shape if printed at average width. @@ -1147,80 +1160,516 @@ void LayerPlan::addWall( const size_t pieces = std::max(size_t(1), std::min(pieces_limit_deviation, pieces_limit_resolution)); // Resolution overrides deviation, if resolution is a constraint. const coord_t piece_length = round_divide(line_length, pieces); - for (size_t piece = 0; piece < pieces; ++piece) + for (size_t piece = 0; piece < pieces && keep_processing; ++piece) { const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) - const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); - const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); - if (is_small_feature) + const coord_t line_width = std::lrint(static_cast(w0) + average_progress * static_cast(delta_line_width)); + const Ratio line_width_ratio = static_cast(line_width) / nominal_line_width; + const Point2LL destination = p0 + normal(line_vector, piece_length * (piece + 1)); + if (is_small_feature && ! is_scarf_closure) { constexpr bool spiralize = false; - addExtrusionMove( - destination, - default_config, - SpaceFillType::Polygons, - flow_ratio, - line_width * nominal_line_width_multiplier, - spiralize, - small_feature_speed_factor); + addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width_ratio, spiralize, small_feature_speed_factor); } else { - const Point2LL origin = p0.p_ + normal(line_vector, piece_length * piece); - addWallLine( - origin, - destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio, - line_width * nominal_line_width_multiplier, - non_bridge_line_volume, - speed_factor, - distance_to_bridge_start); + coord_t piece_remaining_distance = piece_length; + + // Cut piece into smaller parts for scarf seam and acceleration/deceleration + while (piece_remaining_distance > 0 && keep_processing) + { + // Make a list of all the possible incoming positions where we would eventually want to stop next + // The positions are expressed in distance from wall start along the wall segments + std::vector split_positions{ wall_processed_distance + piece_remaining_distance }; + + const bool process_scarf = wall_processed_distance < scarf_seam_length; + if (process_scarf) + { + split_positions.push_back(scarf_seam_length); + split_positions.push_back(wall_processed_distance + scarf_split_distance); + } + + const bool process_acceleration = ! is_scarf_closure && wall_processed_distance < accelerate_length; + if (process_acceleration) + { + split_positions.push_back(accelerate_length); + split_positions.push_back(wall_processed_distance + speed_split_distance); + } + + bool deceleration_started = false; + if (! is_scarf_closure && decelerate_length > 0) + { + deceleration_started = wall_processed_distance >= start_decelerate_position; + if (deceleration_started) + { + split_positions.push_back(wall_processed_distance + speed_split_distance); + } + else + { + split_positions.push_back(start_decelerate_position); + } + } + + // Now take the closest position candidate and make a sub-segment to it + const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); + const coord_t length_to_process = destination_position - wall_processed_distance; + const double destination_factor = static_cast(segment_processed_distance + length_to_process) / line_length; + split_destination = cura::lerp(p0, p1, destination_factor); + + double scarf_segment_flow_ratio = 1.0; + double scarf_factor_destination = 1.0; // Out of range, scarf is done => 1.0 + if (process_scarf) + { + // Calculate scarf interpolation factor on the destination point + scarf_factor_destination = static_cast(destination_position) / static_cast(scarf_seam_length); + + // Interpolate Z offset according to interpolation factor + if (! is_scarf_closure) + { + split_destination.z_ = std::llrint(std::lerp(scarf_max_z_offset, 0.0, scarf_factor_destination)); + } + + // Interpolate flow according to interpolation factor average, because it can't be different + // at start and end positions + const double scarf_factor_average = (scarf_factor_origin + scarf_factor_destination) / 2.0; + if (is_scarf_closure) + { + scarf_segment_flow_ratio = std::lerp(1.0, scarf_seam_start_ratio, scarf_factor_average); + } + else + { + scarf_segment_flow_ratio = std::lerp(scarf_seam_start_ratio, 1.0, scarf_factor_average); + } + + if (first_split) + { + // Manually add a Z-only travel move to set the nozzle at the height of the first point + addTravel(p0, always_retract, split_origin.z_); + first_split = false; + } + } + + Ratio accelerate_speed_factor = 1.0_r; + double accelerate_factor_destination = 1.0; // Out of range, acceleration is done => 1.0 + if (process_acceleration) + { + // Interpolate speed according to interpolation factor average, because it can't be different + // at start and end positions + accelerate_factor_destination = static_cast(destination_position) / static_cast(accelerate_length); + const double accelerate_factor_average = (accelerate_factor_origin + accelerate_factor_destination) / 2.0; + accelerate_speed_factor = std::lerp(start_speed_ratio, 1.0, accelerate_factor_average); + } + + Ratio decelerate_speed_factor = is_scarf_closure ? end_speed_ratio : 1.0_r; + double decelerate_factor_destination = 0.0; // Out of range, deceleration is not started => 0.0 + if (deceleration_started) + { + // Interpolate speed according to interpolation factor average, because it can't be different + // at start and end positions + decelerate_factor_destination = 1.0 - (static_cast(wall_length - destination_position) / static_cast(decelerate_length)); + const double decelerate_factor_average = (decelerate_factor_origin + decelerate_factor_destination) / 2.0; + decelerate_speed_factor = std::lerp(1.0, end_speed_ratio, decelerate_factor_average); + } + + // now add the (sub-)segment + func_add_segment( + split_origin, + split_destination, + accelerate_speed_factor * decelerate_speed_factor, + flow_ratio * scarf_segment_flow_ratio, + line_width_ratio, + distance_to_bridge_start); + + wall_processed_distance = destination_position; + segment_processed_distance += length_to_process; + piece_remaining_distance -= length_to_process; + split_origin = split_destination; + scarf_factor_origin = scarf_factor_destination; + accelerate_factor_origin = accelerate_factor_destination; + decelerate_factor_origin = decelerate_factor_destination; + + if (is_scarf_closure) + { + keep_processing = wall_processed_distance < scarf_seam_length; + } + } } } p0 = p1; + w0 = w1; } - if (wall.size() >= 2) + return { previous_point_index, split_destination.toPoint2LL() }; +} + +std::vector + LayerPlan::calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const +{ + std::vector path_coastings; + path_coastings.resize(paths.size()); + + if (extruder_settings.get("coasting_enable")) { - if (! bridge_wall_mask_.empty()) + // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). + // Note that this doesn't perform the coasting itself, it just calculates the coasting values which will be applied by the 'writePathWithCoasting' func. + // All of this is necessary since we split up paths because of scarf and acceleration-adjustments (start/end), so we need to have adjacency info. + + const double coasting_volume = extruder_settings.get("coasting_volume"); + const double coasting_min_volume = extruder_settings.get("coasting_min_volume"); + + for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse + | ranges::views::chunk_by( + [](const auto&path_a, const auto&path_b) + { + return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); + })) { - computeDistanceToBridgeStart((start_idx + wall.size() - 1) % wall.size()); - } + if (reversed_chunk.empty()) + { + continue; + } + + const PrintFeatureType type = reversed_chunk.front().second.config.getPrintFeatureType(); + if (type != PrintFeatureType::OuterWall && type != PrintFeatureType::InnerWall) + { + continue; + } + + double accumulated_volume = 0.0; + bool chunk_coasting_point_reached = false; + bool chunk_min_volume_reached = false; - if (wall_0_wipe_dist > 0 && ! is_linked_path) - { // apply outer wall wipe - p0 = wall[start_idx]; - int distance_traversed = 0; - for (unsigned int point_idx = 1;; point_idx++) + for (const auto& [path_idx, path] : reversed_chunk) { - if (point_idx > wall.size() && distance_traversed == 0) // Wall has a total circumference of 0. This loop would never end. + if (path.isTravelPath()) { - break; // No wipe if the wall has no circumference. + continue; } - ExtrusionJunction p1 = wall[(start_idx + point_idx) % wall.size()]; - int p0p1_dist = vSize(p1 - p0); - if (distance_traversed + p0p1_dist >= wall_0_wipe_dist) + + PathCoasting& path_coasting = path_coastings[path_idx]; + const double path_extrusion_per_length = path.config.extrusion_mm3_per_mm * path.flow; + + for (const auto& [point_idx, point] : path.points | ranges::views::enumerate | ranges::views::reverse) + { + Point3LL previous_point; + if (point_idx > 0) + { + previous_point = path.points[point_idx - 1]; + } + else if (path_idx > 0) + { + previous_point = paths[path_idx - 1].points.back(); + } + else + { + previous_point = current_position; + } + + const double segment_length = INT2MM((point - previous_point).vSize()); + const double segment_volume = segment_length * path_extrusion_per_length; + + accumulated_volume += segment_volume; + + if (! chunk_coasting_point_reached && path_coasting.apply_coasting == ApplyCoasting::NoCoasting) + { + if (accumulated_volume >= coasting_volume) + { + const double start_pos_factor = (accumulated_volume - coasting_volume) / segment_volume; + Point3LL coasting_start_pos = cura::lerp(previous_point, point, start_pos_factor); + path_coasting = { ApplyCoasting::PartialCoasting, point_idx, coasting_start_pos }; + chunk_coasting_point_reached = true; + } + else if (point_idx == 0) // End of path reached (reverse iteration), coasting not fulfilled + { + path_coasting.apply_coasting = ApplyCoasting::CoastEntirePath; + } + } + + if (accumulated_volume >= coasting_min_volume) + { + chunk_min_volume_reached = true; + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) + { + break; + } + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) { - Point2LL vector = p1.p_ - p0.p_; - Point2LL half_way = p0.p_ + normal(vector, wall_0_wipe_dist - distance_traversed); - addTravel_simple(half_way); break; } - else + } + + if (! chunk_min_volume_reached) + { + // It turns out this chunk doesn't fit the minimum requirements for coasting, so skip it + for (const auto& [path_idx, path] : reversed_chunk) { - addTravel_simple(p1.p_); - distance_traversed += p0p1_dist; + path_coastings[path_idx].apply_coasting = ApplyCoasting::NoCoasting; } - p0 = p1; } - forceNewPathStart(); + } + } + + return path_coastings; +} + +coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const +{ + coord_t distance_to_bridge_start = 0; + + if (! bridge_wall_mask_.empty()) + { + // there is air below the part so iterate through the lines that have not yet been output accumulating the total distance to the first bridge segment + for (size_t point_idx = current_index; point_idx < wall.size(); ++point_idx) + { + const ExtrusionJunction& p0 = wall[point_idx]; + const ExtrusionJunction& p1 = wall[(point_idx + 1) % wall.size()]; + + if (PolygonUtils::polygonCollidesWithLineSegment(bridge_wall_mask_, p0.p_, p1.p_)) + { + // the line crosses the boundary between supported and non-supported regions so it will contain one or more bridge segments + + // determine which segments of the line are bridges + + OpenLinesSet line_polys; + line_polys.addSegment(p0.p_, p1.p_); + constexpr bool restitch = false; // only a single line doesn't need stitching + line_polys = bridge_wall_mask_.intersection(line_polys, restitch); + + while (line_polys.size() > 0) + { + // find the bridge line segment that's nearest to p0 + size_t nearest = 0; + double smallest_dist2 = vSize2f(p0.p_ - line_polys[0][0]); + for (unsigned i = 1; i < line_polys.size(); ++i) + { + double dist2 = vSize2f(p0.p_ - line_polys[i][0]); + if (dist2 < smallest_dist2) + { + nearest = i; + smallest_dist2 = dist2; + } + } + const OpenPolyline& bridge = line_polys[nearest]; + + // set b0 to the nearest vertex and b1 the furthest + Point2LL b0 = bridge[0]; + Point2LL b1 = bridge[1]; + + if (vSize2f(p0.p_ - b1) < vSize2f(p0.p_ - b0)) + { + // swap vertex order + b0 = bridge[1]; + b1 = bridge[0]; + } + + distance_to_bridge_start += vSize(b0 - p0.p_); + + const double bridge_line_len = vSize(b1 - b0); + + if (bridge_line_len >= min_bridge_line_len) + { + // job done, we have found the first bridge line + return distance_to_bridge_start; + } + + distance_to_bridge_start += bridge_line_len; + + // finished with this segment + line_polys.removeAt(nearest); + } + } + else if (! bridge_wall_mask_.inside(p0.p_, true)) + { + // none of the line is over air + distance_to_bridge_start += vSize(p1.p_ - p0.p_); + } + } + + // we have got all the way to the end of the wall without finding a bridge segment so disable coasting by setting distance_to_bridge_start back to 0 + + distance_to_bridge_start = 0; + } + + return distance_to_bridge_start; +} + +template +std::tuple LayerPlan::addWallWithScarfSeam( + const PathAdapter& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_candidate_small_feature, + const bool scarf_seam, + const bool smooth_speed, + const AddExtrusionSegmentFunction& func_add_segment) +{ + if (wall.empty()) + { + return { start_idx, Point2LL() }; + } + + const bool actual_scarf_seam = scarf_seam && is_closed && layer_nr_ > 0; + + const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + + const coord_t nominal_line_width = default_config.getLineWidth(); + + const coord_t wall_length = wall.length(); + const coord_t small_feature_max_length = settings.get("small_feature_max_length"); + const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || is_candidate_small_feature) && wall_length < small_feature_max_length; + const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; + Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); + small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); + const coord_t max_area_deviation = std::max(settings.get("meshfix_maximum_extrusion_area_deviation"), 1); // Square micrometres! + const auto max_resolution = std::max(settings.get("meshfix_maximum_resolution"), coord_t(1)); + const int direction = is_reversed ? -1 : 1; + const size_t max_index = is_closed ? wall.size() + 1 : wall.size(); + + const coord_t scarf_seam_length = std::min(wall_length, actual_scarf_seam ? settings.get("scarf_joint_seam_length") : 0); + const auto scarf_seam_start_ratio = actual_scarf_seam ? settings.get("scarf_joint_seam_start_height_ratio") : 1.0_r; + const auto scarf_split_distance = settings.get("scarf_split_distance"); + const coord_t scarf_max_z_offset = static_cast(-(1.0 - scarf_seam_start_ratio) * static_cast(layer_thickness_)); + + const Velocity top_speed = default_config.getSpeed(); + const coord_t speed_split_distance = settings.get("wall_0_speed_split_distance"); // mm + const Ratio start_speed_ratio = smooth_speed ? settings.get("wall_0_start_speed_ratio") : 1.0_r; + const int acceleration = settings.get("wall_0_acceleration"); // mm/s² + const Velocity start_speed = top_speed * start_speed_ratio; // mm/s + const coord_t accelerate_length = (smooth_speed && start_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(start_speed)) / (2.0 * acceleration)) : 0; // µm + + const Ratio end_speed_ratio = smooth_speed ? settings.get("wall_0_end_speed_ratio") : 1.0_r; + const int deceleration = settings.get("wall_0_deceleration"); // mm/s² + const Velocity end_speed = top_speed * end_speed_ratio; // mm/s + const coord_t decelerate_length = (smooth_speed && end_speed_ratio < 1.0) ? MM2INT((square(top_speed) - square(end_speed)) / (2.0 * deceleration)) : 0; // µm + + auto addSplitWallPass = [&](bool is_scarf_closure) -> std::tuple + { + constexpr bool compute_distance_to_bridge_start = true; + + return addSplitWall( + PathAdapter(wall), + wall_length, + start_idx, + max_index, + direction, + default_config, + always_retract, + is_small_feature, + small_feature_speed_factor, + max_area_deviation, + max_resolution, + flow_ratio, + nominal_line_width, + min_bridge_line_len, + scarf_seam_length, + scarf_seam_start_ratio, + scarf_split_distance, + scarf_max_z_offset, + speed_split_distance, + start_speed_ratio, + accelerate_length, + end_speed_ratio, + decelerate_length, + is_scarf_closure, + compute_distance_to_bridge_start, + func_add_segment); + }; + + // First pass to add the wall with the scarf beginning and acceleration + std::tuple result = addSplitWallPass(false); + + if (scarf_seam_length > 0) + { + // Second pass to add the scarf closure + result = addSplitWallPass(true); + } + + return result; +} + +void LayerPlan::addWall( + const ExtrusionLine& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const GCodePathConfig& roofing_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path, + const bool scarf_seam, + const bool smooth_speed) +{ + if (wall.empty()) + { + return; + } + + double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output + const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + const PathAdapter path_adapter(wall); + + const std::tuple add_wall_result = addWallWithScarfSeam( + path_adapter, + start_idx, + settings, + default_config, + flow_ratio, + always_retract, + is_closed, + is_reversed, + wall.inset_idx_ == 0, + scarf_seam, + smooth_speed, + [&](const Point3LL& start, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t distance_to_bridge_start) + { + constexpr bool travel_to_z = false; + + addWallLine( + start, + end, + settings, + default_config, + roofing_config, + bridge_config, + actual_flow_ratio, + line_width_ratio, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start, + travel_to_z); + }); + + if (wall.size() >= 2) + { + if (! bridge_wall_mask_.empty()) + { + computeDistanceToBridgeStart(wall, (start_idx + wall.size() - 1) % wall.size(), min_bridge_line_len); + } + + if (! is_linked_path) + { + addWipeTravel(path_adapter, wall_0_wipe_dist, is_reversed, get<0>(add_wall_result), get<1>(add_wall_result)); } } else @@ -1497,6 +1946,40 @@ void LayerPlan::addLinesInGivenOrder( } } +bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const +{ + const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() }; + const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); + return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; +} + +void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) +{ + Application::getInstance().communication_->sendLineTo( + path.config.type, + position + Point3LL(0, 0, z_ + path.z_offset), + path.getLineWidthForLayerView(), + path.config.getLayerThickness() + path.z_offset + position.z_, + extrude_speed); +} + +void LayerPlan::writeTravelRelativeZ(GCodeExport& gcode, const Point3LL& position, const Velocity& speed, const coord_t path_z_offset) +{ + gcode.writeTravel(position + Point3LL(0, 0, z_ + path_z_offset), speed); +} + +void LayerPlan::writeExtrusionRelativeZ( + GCodeExport& gcode, + const Point3LL& position, + const Velocity& speed, + const coord_t path_z_offset, + double extrusion_mm3_per_mm, + PrintFeatureType feature, + bool update_extrusion_offset) +{ + gcode.writeExtrusion(position + Point3LL(0, 0, z_ + path_z_offset), speed, extrusion_mm3_per_mm, feature, update_extrusion_offset); +} + void LayerPlan::addLinesMonotonic( const Shape& area, const OpenLinesSet& lines, @@ -1799,15 +2282,15 @@ double ExtruderPlan::getRetractTime(const GCodePath& path) return retraction_config_.distance / (path.retract ? retraction_config_.speed : retraction_config_.primeSpeed); } -std::pair ExtruderPlan::getPointToPointTime(const Point2LL& p0, const Point2LL& p1, const GCodePath& path) +std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const { - const double length = vSizeMM(p0 - p1); + const double length = (p0 - p1).vSizeMM(); return { length, length / (path.config.getSpeed() * path.speed_factor) }; } TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point2LL starting_position) { - Point2LL p0 = starting_position; + Point3LL p0 = starting_position; const double min_path_speed = fan_speed_layer_time_settings_.cool_min_speed; slowest_path_speed_ = std::accumulate( @@ -1859,9 +2342,9 @@ TimeMaterialEstimates ExtruderPlan::computeNaiveTimeEstimates(Point2LL starting_ path.estimates.unretracted_travel_time += 0.5 * retract_unretract_time; } } - for (Point2LL& p1 : path.points) + for (Point3LL& p1 : path.points) { - double length = vSizeMM(p0 - p1); + double length = (p0 - p1).vSizeMM(); if (is_extrusion_path) { if (length > 0) @@ -1968,7 +2451,7 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) if (! extruder_plan.paths_.empty() && ! extruder_plan.paths_.back().points.empty()) { - starting_position = extruder_plan.paths_.back().points.back(); + starting_position = extruder_plan.paths_.back().points.back().toPoint2LL(); } } } @@ -1979,12 +2462,11 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) last_extruder_plan.processFanSpeedForMinimalLayerTime(maximum_cool_min_layer_time, other_extr_plan_time); } - void LayerPlan::writeGCode(GCodeExport& gcode) { auto communication = Application::getInstance().communication_; communication->setLayerForSend(layer_nr_); - communication->sendCurrentPosition(gcode.getPositionXY()); + communication->sendCurrentPosition(gcode.getPosition()); gcode.setLayerNr(layer_nr_); gcode.writeLayerComment(layer_nr_); @@ -2040,6 +2522,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_nr = extruder_plan.extruder_nr_; gcode.ResetLastEValueAfterWipe(prev_extruder); + gcode.writePrepareFansForNozzleSwitch(); const RetractionAndWipeConfig& prev_retraction_config = storage_.retraction_wipe_config_per_extruder[prev_extruder]; if (prev_retraction_config.retraction_hop_after_extruder_switch) @@ -2052,8 +2535,6 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.switchExtruder(extruder_nr, prev_retraction_config.extruder_switch_retraction_config); } - gcode.writePrepareFansForNozzleSwitch(); - { // require printing temperature to be met constexpr bool wait = true; gcode.writeTemperatureCommand(extruder_nr, extruder_plan.required_start_temperature_, wait); @@ -2096,7 +2577,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } } } - gcode.writePrepareFansForExtrusion(extruder_plan.getFanSpeed()); + // Fan speed may already be set by a plugin. Prevents two fan speed commands without move in between. + if (! extruder_plan.paths_.empty() && extruder_plan.paths_.front().fan_speed == -1) + { + gcode.writePrepareFansForExtrusion(extruder_plan.getFanSpeed()); + } std::vector& paths = extruder_plan.paths_; extruder_plan.inserts_.sort(); @@ -2112,13 +2597,24 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); }; - for (int64_t path_idx = 0; path_idx < paths.size(); path_idx++) + const std::vector coasting_per_path = calculatePathsCoasting(extruder.settings_, paths, gcode.getPosition()); + + for (size_t path_idx = 0; path_idx < paths.size(); path_idx++) { extruder_plan.handleInserts(path_idx, gcode); cumulative_path_time = 0.; // reset to 0 for current path. GCodePath& path = paths[path_idx]; + assert(! path.points.empty()); + + // If travel paths have a non default fan speed for some reason set it as fan speed as such modification could be made by a plugin. + if (! path.isTravelPath() || path.fan_speed >= 0) + { + const double path_fan_speed = path.getFanSpeed(); + gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); + } + if (path.perform_prime) { gcode.writePrimeTrain(extruder.settings_.get("speed_travel")); @@ -2218,6 +2714,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) else { gcode.writeZhopEnd(); + if (z_ > 0 && path.z_offset != 0) { gcode.setZ(z_ + path.z_offset); @@ -2254,10 +2751,11 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeComment(ss.str()); } - if (! path.spiralize && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset != gcode.getPositionZ()) && (path_idx > 0 || layer_nr_ > 0)) + if (! path.spiralize && path.travel_to_z && (! path.retract || ! path.perform_z_hop) && (z_ + path.z_offset + path.points.front().z_ != gcode.getPositionZ()) + && (path_idx > 0 || layer_nr_ > 0)) { // First move to desired height to then make a plain horizontal move - gcode.writeTravel(Point3LL(gcode.getPosition().x_, gcode.getPosition().y_, z_ + path.z_offset), speed); + gcode.writeTravel(Point3LL(gcode.getPosition().x_, gcode.getPosition().y_, z_ + path.z_offset + path.points.front().z_), speed); } if (path.config.isTravelPath()) @@ -2270,11 +2768,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeTravel(current_position, extruder.settings_.get("speed_z_hop")); // Prevent the final travel(s) from resetting to the 'previous' layer height. + path.z_offset = final_travel_z_ - z_; gcode.setZ(final_travel_z_); } for (size_t point_idx = 0; point_idx + 1 < path.points.size(); point_idx++) { - gcode.writeTravel(path.points[point_idx], speed); + writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); } if (path.unretract_before_last_travel_move && final_travel_z_ == z_) { @@ -2283,7 +2782,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) } if (! path.points.empty()) { - gcode.writeTravel(path.points.back(), speed); + writeTravelRelativeZ(gcode, path.points.back(), speed, path.z_offset); } continue; } @@ -2291,26 +2790,29 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool spiralize = path.spiralize; if (! spiralize) // normal (extrusion) move (with coasting) { - // if path provides a valid (in range 0-100) fan speed, use it - const double path_fan_speed = path.getFanSpeed(); - gcode.writeFanCommand(path_fan_speed != GCodePathConfig::FAN_SPEED_DEFAULT ? path_fan_speed : extruder_plan.getFanSpeed()); - bool coasting = extruder.settings_.get("coasting_enable"); if (coasting) { - coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness_, insertTempOnTime); + coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, insertTempOnTime, coasting_per_path[path_idx]); } if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm - Point2LL prev_point = gcode.getPositionXY(); + Point3LL prev_point = gcode.getPosition(); for (unsigned int point_idx = 0; point_idx < path.points.size(); point_idx++) { const auto [_, time] = extruder_plan.getPointToPointTime(prev_point, path.points[point_idx], path); 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); + writeExtrusionRelativeZ( + gcode, + path.points[point_idx], + extrude_speed, + path.z_offset, + path.getExtrusionMM3perMM(), + path.config.type, + update_extrusion_offset); + sendLineTo(path, path.points[point_idx], extrude_speed); prev_point = path.points[point_idx]; } @@ -2326,7 +2828,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) GCodePath& _path = paths[_path_idx]; for (unsigned int point_idx = 0; point_idx < _path.points.size(); point_idx++) { - Point2LL p1 = _path.points[point_idx]; + Point2LL p1 = _path.points[point_idx].toPoint2LL(); totalLength += vSizeMM(p0 - p1); p0 = p1; } @@ -2340,19 +2842,21 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (unsigned int point_idx = 0; point_idx < spiral_path.points.size(); point_idx++) { - const Point2LL p1 = spiral_path.points[point_idx]; + const Point2LL p1 = spiral_path.points[point_idx].toPoint2LL(); length += vSizeMM(p0 - p1); p0 = p1; - gcode.setZ(std::round(z_ + layer_thickness_ * length / totalLength)); + const coord_t z_offset = std::round(layer_thickness_ * length / totalLength); const double extrude_speed = speed * spiral_path.speed_back_pressure_factor; - communication->sendLineTo( - spiral_path.config.type, + writeExtrusionRelativeZ( + gcode, spiral_path.points[point_idx], - spiral_path.getLineWidthForLayerView(), - spiral_path.config.getLayerThickness(), - extrude_speed); - gcode.writeExtrusion(spiral_path.points[point_idx], extrude_speed, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, update_extrusion_offset); + extrude_speed, + path.z_offset + z_offset, + spiral_path.getExtrusionMM3perMM(), + spiral_path.config.type, + update_extrusion_offset); + sendLineTo(spiral_path, spiral_path.points[point_idx], extrude_speed); } // 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 @@ -2363,8 +2867,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(spiral_path.config.type, spiral_path.points[0], spiral_path.getLineWidthForLayerView(), spiral_path.config.getLayerThickness(), speed); + sendLineTo(spiral_path, spiral_path.points[0], speed); } path_idx--; // the last path_idx didnt spiralize, so it's not part of the current spiralize path } @@ -2447,135 +2950,65 @@ bool LayerPlan::writePathWithCoasting( GCodeExport& gcode, const size_t extruder_plan_idx, const size_t path_idx, - const coord_t layer_thickness, - const std::function insertTempOnTime) + const std::function insertTempOnTime, + const PathCoasting& path_coasting) { - ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; - const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - const double coasting_volume = extruder.settings_.get("coasting_volume"); - if (coasting_volume <= 0) + if (path_coasting.apply_coasting == ApplyCoasting::NoCoasting) { return false; } + + size_t coasting_start_index; + Point3LL previous_position = gcode.getPosition(); + const ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; 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) - { - 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 coord_t coasting_dist - = MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); - const coord_t coasting_min_dist - = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - // /\ 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...) - accumulated_dist_per_point.push_back(0); - - coord_t accumulated_dist = 0; - - bool length_is_less_than_min_dist = true; - - std::optional acc_dist_idx_gt_coast_dist; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point) - // == the point printed BEFORE the start point for coasting - - const Point2LL* last = &path.points[path.points.size() - 1]; - for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++) - { - const Point2LL& point = path.points[path.points.size() - 1 - backward_point_idx]; - const coord_t distance = vSize(point - *last); - accumulated_dist += distance; - accumulated_dist_per_point.push_back(accumulated_dist); - - if (! acc_dist_idx_gt_coast_dist.has_value() && accumulated_dist >= coasting_dist) - { - acc_dist_idx_gt_coast_dist = backward_point_idx; // the newly added point - } - - if (accumulated_dist >= coasting_min_dist) - { - length_is_less_than_min_dist = false; - break; - } - - last = &point; - } + const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - if (accumulated_dist < coasting_min_dist_considered) + if (path_coasting.apply_coasting == ApplyCoasting::CoastEntirePath) { - return false; + coasting_start_index = 0; } - coord_t actual_coasting_dist = coasting_dist; - if (length_is_less_than_min_dist) + else { - // in this case accumulated_dist is the length of the whole path - actual_coasting_dist = accumulated_dist * coasting_dist / coasting_min_dist; - if (actual_coasting_dist == 0) // Downscaling due to Minimum Coasting Distance reduces coasting to less than 1 micron. - { - return false; // Skip coasting at all then. - } - for (acc_dist_idx_gt_coast_dist = 1; acc_dist_idx_gt_coast_dist.value() < accumulated_dist_per_point.size(); acc_dist_idx_gt_coast_dist.value()++) - { // search for the correct coast_dist_idx - if (accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value()] >= actual_coasting_dist) - { - break; - } - } - } - - assert( - acc_dist_idx_gt_coast_dist.has_value() && acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size()); // something has gone wrong; coasting_min_dist < coasting_dist ? + const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; + coasting_start_index = path_coasting.coasting_start_index; - const size_t point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist.value(); - - Point2LL start; - { // computation of begin point of coasting - const coord_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value() - 1]; - const Point2LL& a = path.points[point_idx_before_start]; - const Point2LL& b = path.points[point_idx_before_start + 1]; - start = b + normal(a - b, residual_dist); - } - - Point2LL prev_pt = gcode.getPositionXY(); - { // write normal extrude path: - auto communication = Application::getInstance().communication_; - for (size_t point_idx = 0; point_idx <= point_idx_before_start; point_idx++) + // write normal extrude path, followed by split extrusion to coasting starting point + for (size_t point_idx = 0; point_idx < coasting_start_index; point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, 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); + writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, path.points[point_idx], extrude_speed); - prev_pt = path.points[point_idx]; + previous_position = 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); + + writeExtrusionRelativeZ(gcode, path_coasting.coasting_start_pos, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, path_coasting.coasting_start_pos, extrude_speed); } // write coasting path - for (size_t point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++) + for (size_t point_idx = coasting_start_index; point_idx < path.points.size(); point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); const Ratio coasting_speed_modifier = extruder.settings_.get("coasting_speed"); const Velocity speed = Velocity(coasting_speed_modifier * path.config.getSpeed()); - gcode.writeTravel(path.points[point_idx], speed); + writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); - prev_pt = path.points[point_idx]; + previous_position = path.points[point_idx]; } + return true; } void LayerPlan::applyModifyPlugin() { + bool handled_initial_travel = false; for (auto& extruder_plan : extruder_plans_) { scripta::log( @@ -2598,6 +3031,40 @@ void LayerPlan::applyModifyPlugin() extruder_plan.paths_ = slots::instance().modify(extruder_plan.paths_, extruder_plan.extruder_nr_, layer_nr_); + // Check if the plugin changed first_travel_destination and update it accordingly if it has + if (! handled_initial_travel) + { + for (auto& path : extruder_plan.paths_) + { + if (path.isTravelPath() && path.points.size() > 0) + { + if (path.points.front() != first_travel_destination_) + { + first_travel_destination_ = path.points.front().toPoint2LL(); + } + handled_initial_travel = true; + break; + } + } + } + + size_t removed_count = std::erase_if( + extruder_plan.paths_, + [](GCodePath& path) + { + return path.points.empty(); + }); + if (removed_count > 0) + { + spdlog::warn("Removed {} empty paths after plugin slot GCODE_PATHS_MODIFY was executed", removed_count); + } + // Ensure that the output is at least valid enough to not cause crashes. + if (extruder_plan.paths_.size() == 0) + { + GCodePath* reinstated_path = getLatestPathWithConfig(configs_storage_.travel_config_per_extruder[getExtruder()], SpaceFillType::None); + addTravel_simple(first_travel_destination_.value_or(getLastPlannedPositionOrStartingPosition()), reinstated_path); + } + scripta::log( "extruder_plan_1", extruder_plan.paths_, @@ -2639,6 +3106,19 @@ void LayerPlan::applyGradualFlow() } } +std::shared_ptr LayerPlan::findFirstPrintedMesh() const +{ + for (const ExtruderPlan& extruder_plan : extruder_plans_) + { + if (std::shared_ptr mesh = extruder_plan.findFirstPrintedMesh()) + { + return mesh; + } + } + + return nullptr; +} + LayerIndex LayerPlan::getLayerNr() const { return layer_nr_; @@ -2684,6 +3164,11 @@ void LayerPlan::setSeamOverhangMask(const Shape& polys) seam_overhang_mask_ = polys; } +const Shape& LayerPlan::getSeamOverhangMask() const +{ + return seam_overhang_mask_; +} + void LayerPlan::setRoofingMask(const Shape& polys) { roofing_mask_ = polys; diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index db64a130a5..4001f0837c 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -91,7 +91,7 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer Point2LL first_location_new_layer = new_layer_destination_state->first; assert(newest_layer->extruder_plans_.front().paths_[0].points.size() == 1); - assert(newest_layer->extruder_plans_.front().paths_[0].points[0] == first_location_new_layer); + assert(newest_layer->extruder_plans_.front().paths_[0].points[0].toPoint2LL() == first_location_new_layer); // if the last planned position in the previous layer isn't the same as the first location of the new layer, travel to the new location if (! prev_layer->last_planned_position_ || *prev_layer->last_planned_position_ != first_location_new_layer) @@ -99,10 +99,21 @@ void LayerPlanBuffer::addConnectingTravelMove(LayerPlan* prev_layer, const Layer const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; const Settings& extruder_settings = Application::getInstance().current_slice_->scene.extruders[prev_layer->extruder_plans_.back().extruder_nr_].settings_; 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. + + const bool travel_retract_before_outer_wall = mesh_group_settings.get("travel_retract_before_outer_wall"); + const bool retract_at_layer_change = extruder_settings.get("retract_at_layer_change"); + bool next_mesh_retract_before_outer_wall = false; + std::shared_ptr first_printed_mesh = newest_layer->findFirstPrintedMesh(); + if (! retract_at_layer_change && first_printed_mesh && travel_retract_before_outer_wall) + { + // Check whether we are moving toving towards an outer wall and it should be retracted + const Settings& mesh_settings = first_printed_mesh->settings; + const InsetDirection inset_direction = mesh_settings.get("inset_direction"); + const size_t wall_line_count = mesh_settings.get("wall_line_count"); + + next_mesh_retract_before_outer_wall = inset_direction == InsetDirection::OUTSIDE_IN || wall_line_count == 1; + } + const bool force_retract = retract_at_layer_change || next_mesh_retract_before_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) diff --git a/src/PathAdapter.cpp b/src/PathAdapter.cpp new file mode 100644 index 0000000000..09a5437493 --- /dev/null +++ b/src/PathAdapter.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PathAdapter.h" + +#include "utils/ExtrusionLine.h" + +namespace cura +{ + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.junctions_.at(index).p_; +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t index) const +{ + return path_.junctions_.at(index).w_; +} + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.at(index); +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t /*index*/) const +{ + return fixed_line_width_; +} + +} // namespace cura diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index bc4e2a07a7..3b7933235f 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -604,7 +604,7 @@ const Shape& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const const Shape& PrimeTower::getGroundPoly() const { - return getOuterPoly(-Raft::getTotalExtraLayers()); + return getOuterPoly(-LayerIndex(Raft::getTotalExtraLayers())); } void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const diff --git a/src/PrimeTower/PrimeTower.cpp b/src/PrimeTower/PrimeTower.cpp index 074d1f501a..30b8ab7eb7 100644 --- a/src/PrimeTower/PrimeTower.cpp +++ b/src/PrimeTower/PrimeTower.cpp @@ -351,7 +351,7 @@ bool PrimeTower::extruderRequiresPrime(const std::vector& extruder_is_used void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const size_t extruder_nr) const { LayerIndex layer_nr = gcode_layer.getLayerNr(); - if (layer_nr != -Raft::getTotalExtraLayers()) + if (layer_nr != -LayerIndex(Raft::getTotalExtraLayers())) { coord_t wipe_radius; auto iterator = base_occupied_outline_.iterator_at(gcode_layer.getLayerNr()); diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 15a6b03cf8..beb6154d3e 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -124,7 +124,7 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage { constexpr bool force_comb_retract = false; layer.addTravel(ironing_polygons[0][0], force_comb_retract); - layer.addPolygonsByOptimizer(ironing_polygons, line_config, ZSeamConfig()); + layer.addPolygonsByOptimizer(ironing_polygons, line_config, mesh.settings, ZSeamConfig()); added = true; } if (! ironing_lines.empty()) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..77e7acdd19 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2037,21 +2037,28 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); - if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) + if (hole.size() > 1) { + // The hole contains other branches! It can not be fully removed. + // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring + // larger changes. holes_resting_outside[layer_idx].emplace(idx); } - else if (hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).area() > hole.length() * EPSILON) + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) { - holes_resting_outside[layer_idx].emplace( - idx); // technically not resting outside, also not valid, but the alternative is potentially having lines go though the model + holes_resting_outside[layer_idx].emplace(idx); + } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).offset(-config.xy_min_distance / 2).empty()) + { + // technically not resting outside, also not valid, but the alternative is potentially having lines go through the model + holes_resting_outside[layer_idx].emplace(idx); } else { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { - if (hole_aabb.hit(AABB(hole2)) - && ! hole.intersection(hole2).empty()) // TODO should technically be outline: Check if this is fine either way as it would save an offset + // TODO should technically be outline: Check if this is fine either way as it would save an offset + if (hole_aabb.hit(AABB(hole2)) && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) { hole_rest_map[layer_idx][idx].emplace_back(idx2); } diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 0a2af0e233..79ac54a51f 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -64,7 +64,7 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceMeshStorage& mesh, T const double support_overhang_angle = mesh.settings.get("support_angle"); const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * config_.layer_height) : std::numeric_limits::max(); - if (max_overhang_speed == 0) + if (max_overhang_speed < 2) { max_overhang_insert_lag_ = std::numeric_limits::max(); } diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index c34aaf8d69..1773f221c4 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -436,6 +436,19 @@ const Shape& WallToolPaths::getInnerContour() bool WallToolPaths::removeEmptyToolPaths(std::vector& toolpaths) { + for (VariableWidthLines& toolpath : toolpaths) + { + toolpath.erase( + std::remove_if( + toolpath.begin(), + toolpath.end(), + [](const ExtrusionLine& line) + { + return line.junctions_.empty(); + }), + toolpath.end()); + } + toolpaths.erase( std::remove_if( toolpaths.begin(), diff --git a/src/WallsComputation.cpp b/src/WallsComputation.cpp index 752d1cf0c9..f75c89defa 100644 --- a/src/WallsComputation.cpp +++ b/src/WallsComputation.cpp @@ -104,21 +104,15 @@ void WallsComputation::generateWalls(SliceLayer* layer, SectionType section) // Remove the parts which did not generate a wall. As these parts are too small to print, // and later code can now assume that there is always minimal 1 wall line. - if (settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps")) - { - for (size_t part_idx = 0; part_idx < layer->parts.size(); part_idx++) + bool check_wall_and_spiral = settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps"); + auto iterator_remove = std::remove_if( + layer->parts.begin(), + layer->parts.end(), + [&check_wall_and_spiral](const SliceLayerPart& part) { - if (layer->parts[part_idx].wall_toolpaths.empty() && layer->parts[part_idx].spiral_wall.empty()) - { - if (part_idx != layer->parts.size() - 1) - { // move existing part into part to be deleted - layer->parts[part_idx] = std::move(layer->parts.back()); - } - layer->parts.pop_back(); // always remove last element from array (is more efficient) - part_idx -= 1; // check the part we just moved here - } - } - } + return (check_wall_and_spiral && part.wall_toolpaths.empty() && part.spiral_wall.empty()) || part.outline.empty() || part.print_outline.empty(); + }); + layer->parts.erase(iterator_remove, layer->parts.end()); } void WallsComputation::generateSpiralInsets(SliceLayerPart* part, coord_t line_width_0, coord_t wall_0_inset, bool recompute_outline_based_on_outer_wall) diff --git a/src/communication/ArcusCommunication.cpp b/src/communication/ArcusCommunication.cpp index 27e0e19b51..14867fa962 100644 --- a/src/communication/ArcusCommunication.cpp +++ b/src/communication/ArcusCommunication.cpp @@ -67,7 +67,7 @@ class ArcusCommunication::PathCompiler std::vector points; //!< The points used to define the line segments, the size of this vector is D*(N+1) as each line segment is defined from one point to the next. D is //!< the dimensionality of the point. - Point2LL last_point; + Point3LL last_point; PathCompiler(const PathCompiler&) = delete; PathCompiler& operator=(const PathCompiler&) = delete; @@ -80,13 +80,12 @@ class ArcusCommunication::PathCompiler : _cs_private_data(cs_private_data) , _layer_nr(0) , extruder(0) - , data_point_type(cura::proto::PathSegment::Point2D) + , data_point_type(cura::proto::PathSegment::Point3D) , line_types() , line_widths() , line_thicknesses() , line_velocities() , points() - , last_point{ 0, 0 } { } @@ -143,11 +142,11 @@ class ArcusCommunication::PathCompiler * of the path this jump is marked as `PrintFeatureType::NoneType`. * \param from The initial point of a polygon. */ - void handleInitialPoint(const Point2LL& initial_point) + void handleInitialPoint(const Point3LL& initial_point) { if (points.size() == 0) { - addPoint2D(initial_point); + addPoint3D(initial_point); } else if (initial_point != last_point) { @@ -201,7 +200,7 @@ class ArcusCommunication::PathCompiler /*! * \brief Move the current point of this path to \p position. */ - void setCurrentPosition(const Point2LL& position) + void setCurrentPosition(const Point3LL& position) { handleInitialPoint(position); } @@ -217,7 +216,7 @@ class ArcusCommunication::PathCompiler * \param line_thickness The thickness (in the Z direction) of the line. * \param velocity The velocity of printing this polygon. */ - void sendLineTo(const PrintFeatureType& print_feature_type, const Point2LL& to, const coord_t& width, const coord_t& thickness, const Velocity& feedrate) + void sendLineTo(const PrintFeatureType& print_feature_type, const Point3LL& to, const coord_t& width, const coord_t& thickness, const Velocity& feedrate) { assert(! points.empty() && "A point must already be in the buffer for sendLineTo(.) to function properly."); @@ -227,53 +226,18 @@ class ArcusCommunication::PathCompiler } } - /*! - * \brief Adds closed polygon to the current path. - * \param print_feature_type The type of feature that the polygon is part of - * (infill, wall, etc). - * \param polygon The shape of the polygon. - * \param width The width of the lines of the polygon. - * \param thickness The layer thickness of the polygon. - * \param velocity How fast the polygon is printed. - */ - void sendPolygon(const PrintFeatureType& print_feature_type, const Polygon& polygon, const coord_t& width, const coord_t& thickness, const Velocity& velocity) - { - if (polygon.size() < 2) // Don't send single points or empty polygons. - { - return; - } - - ClipperLib::Path::const_iterator point = polygon.begin(); - handleInitialPoint(*point); - - // Send all coordinates one by one. - while (++point != polygon.end()) - { - if (*point == last_point) - { - continue; // Ignore zero-length segments. - } - addLineSegment(print_feature_type, *point, width, thickness, velocity); - } - - // Make sure the polygon is closed. - if (*polygon.begin() != polygon.back()) - { - addLineSegment(print_feature_type, *polygon.begin(), width, thickness, velocity); - } - } - private: /*! * \brief Convert and add a point to the points buffer. * - * Each point is represented as two consecutive floats. All members adding a - * 2D point to the data should use this function. + * Each point is represented as three consecutive floats. All members adding a + * 3D point to the data should use this function. */ - void addPoint2D(const Point2LL& point) + void addPoint3D(const Point3LL& point) { - points.push_back(INT2MM(point.X)); - points.push_back(INT2MM(point.Y)); + points.push_back(INT2MM(point.x_)); + points.push_back(INT2MM(point.y_)); + points.push_back(INT2MM(point.z_)); last_point = point; } @@ -289,9 +253,9 @@ class ArcusCommunication::PathCompiler * \param thickness The layer thickness of the polygon. * \param velocity How fast the polygon is printed. */ - void addLineSegment(const PrintFeatureType& print_feature_type, const Point2LL& point, const coord_t& width, const coord_t& thickness, const Velocity& velocity) + void addLineSegment(const PrintFeatureType& print_feature_type, const Point3LL& point, const coord_t& width, const coord_t& thickness, const Velocity& velocity) { - addPoint2D(point); + addPoint3D(point); line_types.push_back(print_feature_type); line_widths.push_back(INT2MM(width)); line_thicknesses.push_back(INT2MM(thickness)); @@ -381,7 +345,7 @@ bool ArcusCommunication::hasSlice() const && private_data->slice_count < 1; // Only slice once per run of CuraEngine. See documentation of slice_count. } -void ArcusCommunication::sendCurrentPosition(const Point2LL& position) +void ArcusCommunication::sendCurrentPosition(const Point3LL& position) { path_compiler->setCurrentPosition(position); } @@ -415,7 +379,7 @@ void ArcusCommunication::sendLayerComplete(const LayerIndex::value_type& layer_n layer->set_thickness(thickness); } -void ArcusCommunication::sendLineTo(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) +void ArcusCommunication::sendLineTo(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) { path_compiler->sendLineTo(type, to, line_width, line_thickness, velocity); } @@ -444,19 +408,6 @@ void ArcusCommunication::sendOptimizedLayerData() data.slice_data.clear(); } -void ArcusCommunication::sendPolygon(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) -{ - path_compiler->sendPolygon(type, polygon, line_width, line_thickness, velocity); -} - -void ArcusCommunication::sendPolygons(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity) -{ - for (const Polygon& polygon : polygons) - { - path_compiler->sendPolygon(type, polygon, line_width, line_thickness, velocity); - } -} - void ArcusCommunication::sendPrintTimeMaterialEstimates() const { spdlog::debug("Sending print time and material estimates."); diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index 80db95519e..da4bef97e5 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -52,7 +52,7 @@ void CommandLine::beginGCode() void CommandLine::flushGCode() { } -void CommandLine::sendCurrentPosition(const Point2LL&) +void CommandLine::sendCurrentPosition(const Point3LL&) { } void CommandLine::sendFinishedSlicing() const @@ -61,18 +61,12 @@ void CommandLine::sendFinishedSlicing() const void CommandLine::sendLayerComplete(const LayerIndex::value_type&, const coord_t&, const coord_t&) { } -void CommandLine::sendLineTo(const PrintFeatureType&, const Point2LL&, const coord_t&, const coord_t&, const Velocity&) +void CommandLine::sendLineTo(const PrintFeatureType&, const Point3LL&, const coord_t&, const coord_t&, const Velocity&) { } void CommandLine::sendOptimizedLayerData() { } -void CommandLine::sendPolygon(const PrintFeatureType&, const Polygon&, const coord_t&, const coord_t&, const Velocity&) -{ -} -void CommandLine::sendPolygons(const PrintFeatureType&, const Shape&, const coord_t&, const coord_t&, const Velocity&) -{ -} void CommandLine::setExtruderForSend(const ExtruderTrain&) { } @@ -192,7 +186,9 @@ void CommandLine::sliceNext() force_read_parent = false; force_read_nondefault = false; } - else if (argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb")) + else if ( + argument.starts_with("--progress_cb") || argument.starts_with("--slice_info_cb") || argument.starts_with("--gcode_header_cb") + || argument.starts_with("--engine_info_cb")) { // Unused in command line slicing, but used in EmscriptenCommunication. argument_index++; diff --git a/src/communication/EmscriptenCommunication.cpp b/src/communication/EmscriptenCommunication.cpp index 8155290ee9..e62e68cf32 100644 --- a/src/communication/EmscriptenCommunication.cpp +++ b/src/communication/EmscriptenCommunication.cpp @@ -38,6 +38,10 @@ EmscriptenCommunication::EmscriptenCommunication(const std::vector& { gcode_header_handler_ = *ranges::next(gcode_header_flag); } + if (auto engine_info_flag = ranges::find(arguments_, "--engine_info_cb"); engine_info_flag != arguments_.end()) + { + engine_info_handler_ = *ranges::next(engine_info_flag); + } } void EmscriptenCommunication::sendGCodePrefix(const std::string& prefix) const @@ -109,6 +113,35 @@ std::string EmscriptenCommunication::createSliceInfoMessage() return buffer.GetString(); } +std::string EmscriptenCommunication::createEngineInfoMessage() +{ + // Construct a string with rapidjson containing the engine information + rapidjson::Document doc; + auto& allocator = doc.GetAllocator(); + doc.SetObject(); + + // Set the slicer version + rapidjson::Value version("version", allocator); + rapidjson::Value version_value(CURA_ENGINE_VERSION, allocator); + doc.AddMember(version, version_value, allocator); + + // Set the hash + rapidjson::Value hash("hash", allocator); + rapidjson::Value hash_value(CURA_ENGINE_HASH, allocator); + doc.AddMember(hash, hash_value, allocator); + + // Serialize the JSON document to a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +void EmscriptenCommunication::beginGCode() +{ + auto engine_info = createEngineInfoMessage(); + emscripten_run_script(fmt::format("globalThis[\"{}\"]({})", engine_info_handler_, engine_info).c_str()); +} void EmscriptenCommunication::sliceNext() { CommandLine::sliceNext(); diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index 1de0935cb7..7dafd74087 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -417,7 +417,7 @@ const Point3LL& GCodeExport::getPosition() const } Point2LL GCodeExport::getPositionXY() const { - return Point2LL(current_position_.x_, current_position_.y_); + return current_position_.toPoint2LL(); } int GCodeExport::getPositionZ() const @@ -682,6 +682,11 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons writeComment("Generated with Cura_SteamEngine " CURA_ENGINE_VERSION); + if (mesh_group_settings.get("machine_start_gcode_first")) + { + writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); + } + if (getFlavor() == EGCodeFlavor::GRIFFIN) { std::ostringstream tmp; @@ -693,8 +698,11 @@ bool GCodeExport::initializeExtruderTrains(const SliceDataStorage& storage, cons processInitialLayerTemperature(storage, start_extruder_nr); } + if (! mesh_group_settings.get("machine_start_gcode_first")) + { + writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); + } writeExtrusionMode(false); // ensure absolute extrusion mode is set before the start gcode - writeCode(mesh_group_settings.get("machine_start_gcode").c_str()); // in case of shared nozzle assume that the machine-start gcode reset the extruders as per machine description if (Application::getInstance().current_slice_->scene.settings.get("machine_extruders_share_nozzle")) @@ -819,8 +827,9 @@ void GCodeExport::processInitialLayerExtrudersTemperatures(const SliceDataStorag void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage, const size_t start_extruder_nr) { Scene& scene = Application::getInstance().current_slice_->scene; - const size_t num_extruders = scene.extruders.size(); bool wait_start_extruder = false; + std::vector extruders_used = storage.getExtrudersUsed(); + size_t used_extruders = std::count(extruders_used.begin(), extruders_used.end(), true); switch (getFlavor()) { @@ -830,7 +839,7 @@ void GCodeExport::processInitialLayerTemperature(const SliceDataStorage& storage wait_start_extruder = true; break; default: - if (num_extruders > 1 || getFlavor() == EGCodeFlavor::REPRAP) + if (used_extruders > 1 || getFlavor() == EGCodeFlavor::REPRAP) { std::ostringstream tmp; tmp << "T" << start_extruder_nr; @@ -975,7 +984,7 @@ void GCodeExport::writeTravel(const coord_t x, const coord_t y, const coord_t z, const PrintFeatureType travel_move_type = extruder_attr_[current_extruder_].retraction_e_amount_current_ ? PrintFeatureType::MoveRetraction : PrintFeatureType::MoveCombing; const int display_width = extruder_attr_[current_extruder_].retraction_e_amount_current_ ? MM2INT(0.2) : MM2INT(0.1); const double layer_height = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("layer_height"); - Application::getInstance().communication_->sendLineTo(travel_move_type, Point2LL(x, y), display_width, layer_height, speed); + Application::getInstance().communication_->sendLineTo(travel_move_type, Point3LL(x, y, z), display_width, layer_height, speed); *output_stream_ << "G0"; writeFXYZE(speed, x, y, z, current_e_value_, travel_move_type); @@ -1249,9 +1258,11 @@ void GCodeExport::writeZhopStart(const coord_t hop_height, Velocity speed /*= 0* speed = extruder.settings_.get("speed_z_hop"); } is_z_hopped_ = hop_height; + const coord_t target_z = current_layer_z_ + is_z_hopped_; current_speed_ = speed; - *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ current_layer_z_ + is_z_hopped_ } << new_line_; - total_bounding_box_.includeZ(current_layer_z_ + is_z_hopped_); + *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ target_z } << new_line_; + Application::getInstance().communication_->sendLineTo(PrintFeatureType::MoveRetraction, Point3LL(current_position_.x_, current_position_.y_, target_z), 0, 0, speed); + total_bounding_box_.includeZ(target_z); assert(speed > 0.0 && "Z hop speed should be positive."); } } @@ -1269,12 +1280,37 @@ void GCodeExport::writeZhopEnd(Velocity speed /*= 0*/) current_position_.z_ = current_layer_z_; current_speed_ = speed; *output_stream_ << "G1 F" << PrecisionedDouble{ 1, speed * 60 } << " Z" << MMtoStream{ current_layer_z_ } << new_line_; + Application::getInstance() + .communication_->sendLineTo(PrintFeatureType::MoveRetraction, Point3LL(current_position_.x_, current_position_.y_, current_layer_z_), 0, 0, speed); assert(speed > 0.0 && "Z hop speed should be positive."); } } void GCodeExport::startExtruder(const size_t new_extruder) { + const auto extruder_settings = Application::getInstance().current_slice_->scene.extruders[new_extruder].settings_; + const auto prestart_code = extruder_settings.get("machine_extruder_prestart_code"); + const auto start_code = extruder_settings.get("machine_extruder_start_code"); + const auto start_code_duration = extruder_settings.get("machine_extruder_start_code_duration"); + const auto extruder_change_duration = extruder_settings.get("machine_extruder_change_duration"); + + // Be nice to be able to calculate the extruder change time verses time + // to heat and run this so it's run before the change call. **Future note** + if (! prestart_code.empty()) + { + if (relative_extrusion_) + { + writeExtrusionMode(false); // ensure absolute extrusion mode is set before the prestart gcode + } + + writeCode(prestart_code.c_str()); + + if (relative_extrusion_) + { + writeExtrusionMode(true); // restore relative extrusion mode + } + } + extruder_attr_[new_extruder].is_used_ = true; if (new_extruder != current_extruder_) // wouldn't be the case on the very first extruder start if it's extruder 0 { @@ -1286,15 +1322,16 @@ void GCodeExport::startExtruder(const size_t new_extruder) { *output_stream_ << "T" << new_extruder << new_line_; } + // Only add time is we are actually changing extruders + estimate_calculator_.addTime(extruder_change_duration); } + estimate_calculator_.addTime(start_code_duration); current_extruder_ = new_extruder; assert(getCurrentExtrudedVolume() == 0.0 && "Just after an extruder switch we haven't extruded anything yet!"); resetExtrusionValue(); // zero the E value on the new extruder, just to be sure - const auto extruder_settings = Application::getInstance().current_slice_->scene.extruders[new_extruder].settings_; - const auto start_code = extruder_settings.get("machine_extruder_start_code"); if (! start_code.empty()) { if (relative_extrusion_) @@ -1310,9 +1347,6 @@ void GCodeExport::startExtruder(const size_t new_extruder) } } - const auto start_code_duration = extruder_settings.get("machine_extruder_start_code_duration"); - estimate_calculator_.addTime(start_code_duration); - Application::getInstance().communication_->setExtruderForSend(Application::getInstance().current_slice_->scene.extruders[new_extruder]); Application::getInstance().communication_->sendCurrentPosition(getPositionXY()); @@ -1722,6 +1756,11 @@ void GCodeExport::finalize(const char* endCode) for (int n = 1; n < MAX_EXTRUDERS; n++) if (getTotalFilamentUsed(n) > 0) spdlog::info("Filament {}: {}", n + 1, int(getTotalFilamentUsed(n))); + flushOutputStream(); +} + +void GCodeExport::flushOutputStream() +{ output_stream_->flush(); } diff --git a/src/geometry/Point2LL.cpp b/src/geometry/Point2LL.cpp new file mode 100644 index 0000000000..d3071b82af --- /dev/null +++ b/src/geometry/Point2LL.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "geometry/Point2LL.h" //The headers we're implementing. + +#include "geometry/Point3LL.h" + +namespace cura +{ + +Point2LL operator+(const Point2LL& p2, const Point3LL& p3) +{ + return { p3.x_ + p2.X, p3.y_ + p2.Y }; +} + +Point3LL operator+(const Point3LL& p3, const Point2LL& p2) +{ + return { p3.x_ + p2.X, p3.y_ + p2.Y, p3.z_ }; +} + +Point3LL& operator+=(Point3LL& p3, const Point2LL& p2) +{ + p3.x_ += p2.X; + p3.y_ += p2.Y; + return p3; +} + +Point3LL operator-(const Point3LL& p3, const Point2LL& p2) +{ + return { p3.x_ - p2.X, p3.y_ - p2.Y, p3.z_ }; +} + +Point3LL& operator-=(Point3LL& p3, const Point2LL& p2) +{ + p3.x_ -= p2.X; + p3.y_ -= p2.Y; + return p3; +} + +Point2LL operator-(const Point2LL& p2, const Point3LL& p3) +{ + return { p2.X - p3.x_, p2.Y - p3.y_ }; +} + +} // namespace cura diff --git a/src/utils/Point3LL.cpp b/src/geometry/Point3LL.cpp similarity index 76% rename from src/utils/Point3LL.cpp rename to src/geometry/Point3LL.cpp index bb8e7c6930..3dd6c15ec4 100644 --- a/src/utils/Point3LL.cpp +++ b/src/geometry/Point3LL.cpp @@ -6,6 +6,12 @@ namespace cura { +Point3LL::Point3LL(const Point2LL& point) + : x_(point.X) + , y_(point.Y) +{ +} + Point3LL Point3LL::operator+(const Point3LL& p) const { return Point3LL(x_ + p.x_, y_ + p.y_, z_ + p.z_); @@ -63,4 +69,19 @@ Point3LL& Point3LL::operator/=(const Point3LL& p) return *this; } +Point2LL Point3LL::toPoint2LL() const +{ + return Point2LL(x_, y_); +} + +Point3LL Point3LL::resized(coord_t length) const +{ + const coord_t actual_length = vSize(); + if (actual_length < 1) + { + return { length, 0, 0 }; + } + return ((*this) * length) / actual_length; +} + } // namespace cura diff --git a/src/infill.cpp b/src/infill.cpp index 6067fa625b..695e7a134c 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -322,7 +322,8 @@ void Infill::_generate( 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); + mesh ? mesh->settings : settings, + z_); toolpaths.insert(toolpaths.end(), toolpaths_.begin(), toolpaths_.end()); result_polygons.push_back(generated_result_polygons_); result_lines.push_back(generated_result_lines_); @@ -930,12 +931,15 @@ void Infill::connectLines(OpenLinesSet& result_lines) } else { + constexpr coord_t epsilon_sqd = 25; + // Resolve any intersections of the fill lines close to the boundary, by inserting extra points so the lines don't create a tiny 'loop'. Point2LL 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) + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 && vSize2(previous_point - intersect) > epsilon_sqd + && vSize2(next_point - intersect) > epsilon_sqd) { resolveIntersection(infill_line_width_, intersect, previous_point, next_point, previous_segment, crossing); } diff --git a/src/plugins/converters.cpp b/src/plugins/converters.cpp index 964a50007d..c5a33d5edd 100644 --- a/src/plugins/converters.cpp +++ b/src/plugins/converters.cpp @@ -51,12 +51,28 @@ broadcast_settings_request::value_type broadcast_settings_request::operator()(co } auto* object_settings = message.mutable_object_settings(); - for (const auto& object : slice_message.object_lists()) + for (const auto& mesh_group : slice_message.object_lists()) { - auto* settings = object_settings->Add()->mutable_settings(); - for (const auto& setting : object.settings()) + std::unordered_map mesh_group_settings; + for (const auto& setting : mesh_group.settings()) { - settings->emplace(setting.name(), setting.value()); + mesh_group_settings[setting.name()] = setting.value(); + } + for (const auto& object : mesh_group.objects()) + { + std::unordered_map per_object_settings = mesh_group_settings; + for (const auto& setting : object.settings()) + { + per_object_settings[setting.name()] = setting.value(); + } + + per_object_settings["mesh_name"] = object.name(); + + auto* settings = object_settings->Add()->mutable_settings(); + for (const auto& key_value_pair : per_object_settings) + { + settings->emplace(key_value_pair.first, key_value_pair.second); + } } } @@ -168,7 +184,8 @@ postprocess_response::native_value_type } 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 + infill_generate_request::operator()(const infill_generate_request::native_value_type& inner_contour, const std::string& pattern, const Settings& settings, const coord_t z) + const { value_type message{}; message.set_pattern(pattern); @@ -178,6 +195,11 @@ infill_generate_request::value_type msg_settings->insert({ key, value }); } + // ------------------------------------------------------------ + // Add current z height to settings message + // ------------------------------------------------------------ + msg_settings->insert({ "z", std::to_string(z) }); + if (inner_contour.empty()) { return message; @@ -337,8 +359,9 @@ gcode_paths_modify_request::value_type for (const auto& point : path.points) { auto* points = gcode_path->mutable_path()->add_path(); - points->set_x(point.X); - points->set_y(point.Y); + points->set_x(point.x_); + points->set_y(point.y_); + points->set_z(point.z_); } gcode_path->set_space_fill_type(getSpaceFillType(path.space_fill_type)); gcode_path->set_flow(path.flow); @@ -478,7 +501,7 @@ gcode_paths_modify_response::native_value_type | ranges::views::transform( [](const auto& point_msg) { - return Point2LL{ point_msg.x(), point_msg.y() }; + return Point3LL{ point_msg.x(), point_msg.y(), point_msg.z() }; }) | ranges::to_vector; diff --git a/src/raft.cpp b/src/raft.cpp index edaff83509..f4d40eb8f0 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -199,26 +199,23 @@ Raft::LayerType Raft::getLayerType(LayerIndex layer_index) const auto interface_layers = Raft::getInterfaceLayers(); const auto surface_layers = Raft::getSurfaceLayers(); - if (layer_index < -airgap - surface_layers - interface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers + interface_layers)) { return LayerType::RaftBase; } - else if (layer_index < -airgap - surface_layers) + if (layer_index < -LayerIndex(airgap + surface_layers)) { return LayerType::RaftInterface; } - else if (layer_index < -airgap) + if (layer_index < -LayerIndex(airgap)) { return LayerType::RaftSurface; } - else if (layer_index < 0) + if (layer_index < LayerIndex(0)) { return LayerType::Airgap; } - else - { - return LayerType::Model; - } + return LayerType::Model; } size_t Raft::getLayersAmount(const std::string& extruder_nr_setting_name, const std::string& target_raft_section) diff --git a/src/skin.cpp b/src/skin.cpp index 351290be93..987e39a8b9 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -93,7 +93,7 @@ void SkinInfillAreaComputation::generateSkinsAndInfill() { generateRoofingFillAndSkinFill(part); - generateTopAndBottomMostSkinFill(part); + generateTopAndBottomMostSurfaces(part); } } @@ -343,6 +343,22 @@ void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& p } } +void SkinInfillAreaComputation::generateTopAndBottomMostSurfaces(SliceLayerPart& part) +{ + const Shape outline_above = getOutlineOnLayer(part, layer_nr_ + 1); + part.top_most_surface = part.outline.difference(outline_above); + + if (layer_nr_ > 0) + { + const Shape outline_below = getOutlineOnLayer(part, layer_nr_ + 1); + part.bottom_most_surface = part.outline.difference(outline_below); + } + else + { + part.bottom_most_surface = part.outline; + } +} + /* * This function is executed in a parallel region based on layer_nr. * When modifying make sure any changes does not introduce data races. @@ -529,8 +545,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); const Shape more_dense_infill = infill_area.difference(less_dense_infill); - infill_area_per_combine_current_density.push_back( - simplifier.polygon(more_dense_infill.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(more_dense_infill.difference(sum_more_dense))); if (is_connected) { sum_more_dense = sum_more_dense.unionPolygons(more_dense_infill); @@ -538,7 +553,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) } part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); - infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense))); part.infill_area_own = std::nullopt; // clear infill_area_own, it's not needed any more. assert(! part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is now initialized"); } @@ -634,24 +649,4 @@ void SkinInfillAreaComputation::combineInfillLayers(SliceMeshStorage& mesh) } } -/* - * This function is executed in a parallel region based on layer_nr. - * When modifying make sure any changes does not introduce data races. - * - * this function may only read/write the skin and infill from the *current* layer. - */ - -void SkinInfillAreaComputation::generateTopAndBottomMostSkinFill(SliceLayerPart& part) -{ - for (SkinPart& skin_part : part.skin_parts) - { - Shape filled_area_above = generateFilledAreaAbove(part, 1); - skin_part.top_most_surface_fill = skin_part.outline.difference(filled_area_above); - - Shape filled_area_below = generateFilledAreaBelow(part, 1); - skin_part.bottom_most_surface_fill = skin_part.skin_fill.difference(filled_area_below); - } -} - - } // namespace cura diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 697721ccd0..729b9845b9 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -398,6 +398,12 @@ std::vector SliceDataStorage::getExtrudersUsed() const std::vector ret; ret.resize(Application::getInstance().current_slice_->scene.extruders.size(), false); + // set all the false to start, we set them to true if used + for (size_t extruder_nr = 0; extruder_nr < Application::getInstance().current_slice_->scene.extruders.size(); extruder_nr++) + { + ret[extruder_nr] = false; + } + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; const EPlatformAdhesion adhesion_type = mesh_group_settings.get("adhesion_type"); if (adhesion_type == EPlatformAdhesion::SKIRT || adhesion_type == EPlatformAdhesion::BRIM) diff --git a/src/support.cpp b/src/support.cpp index 17a55d855a..898b8b2835 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -1712,7 +1712,8 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh { return; } - const coord_t z_distance_top = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); // Number of layers between support roof and model. + const coord_t support_top_distance = mesh.settings.get("support_top_distance"); + const coord_t z_distance_top = round_up_divide(support_top_distance, layer_height); // Number of layers between support roof and model. const coord_t roof_line_width = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_line_width"); const coord_t roof_outline_offset = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_offset"); @@ -1732,7 +1733,7 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh Shape roofs; generateSupportInterfaceLayer(global_support_areas_per_layer[layer_idx], mesh_outlines, roof_line_width, roof_outline_offset, minimum_roof_area, roofs); support_layers[layer_idx].support_roof.push_back(roofs); - if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + if (layer_idx > 0 && layer_idx < support_layers.size() - 1 && support_top_distance % layer_height != 0) { support_layers[layer_idx].support_fractional_roof.push_back(roofs.difference(support_layers[layer_idx + 1].support_roof)); } diff --git a/src/utils/AABB.cpp b/src/utils/AABB.cpp index 163c6a5f26..36cfa32e98 100644 --- a/src/utils/AABB.cpp +++ b/src/utils/AABB.cpp @@ -33,7 +33,7 @@ AABB::AABB(const Shape& shape) calculate(shape); } -AABB::AABB(const Polygon& poly) +AABB::AABB(const PointsSet& poly) : min_(POINT_MAX, POINT_MAX) , max_(POINT_MIN, POINT_MIN) { @@ -83,7 +83,7 @@ void AABB::calculate(const Shape& shape) } } -void AABB::calculate(const Polygon& poly) +void AABB::calculate(const PointsSet& poly) { min_ = Point2LL(POINT_MAX, POINT_MAX); max_ = Point2LL(POINT_MIN, POINT_MIN); @@ -141,7 +141,7 @@ void AABB::include(const Point2LL& point) max_.Y = std::max(max_.Y, point.Y); } -void AABB::include(const Polygon& polygon) +void AABB::include(const PointsSet& polygon) { for (const Point2LL& point : polygon) { diff --git a/src/utils/SVG.cpp b/src/utils/SVG.cpp index 69a115d9d4..83f223e9b1 100644 --- a/src/utils/SVG.cpp +++ b/src/utils/SVG.cpp @@ -59,6 +59,14 @@ std::string SVG::toString(const ColorObject& color) const } } +void SVG::handleFlush(const bool flush) const +{ + if (flush) + { + fflush(out_); + } +} + SVG::SVG(std::string filename, AABB aabb, Point2LL canvas_size, ColorObject background) : SVG( @@ -249,7 +257,7 @@ void SVG::writeLines(const std::vector& polyline, const ColorObject co fprintf(out_, "\" />\n"); // Write the end of the tag. } -void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width) const +void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const bool flush) const { Point3D fa = transformF(a); Point3D fb = transformF(b); @@ -262,6 +270,8 @@ void SVG::writeLine(const Point2LL& a, const Point2LL& b, const ColorObject colo static_cast(fb.y_), toString(color).c_str(), static_cast(stroke_width)); + + handleFlush(flush); } void SVG::writeArrow(const Point2LL& a, const Point2LL& b, const ColorObject color, const double stroke_width, const double head_size) const @@ -342,10 +352,7 @@ void SVG::writePolygons(const Shape& polys, const ColorObject color, const doubl writePolygon(poly, color, stroke_width, false); } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } void SVG::writePolygon(const Polygon poly, const ColorObject color, const double stroke_width, const bool flush) const @@ -381,19 +388,18 @@ void SVG::writePolygon(const Polygon poly, const ColorObject color, const double i++; } - if (flush) - { - fflush(out_); - } + handleFlush(flush); } -void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width) const +void SVG::writePolylines(const Shape& polys, const ColorObject color, const double stroke_width, const bool flush) const { for (const Polygon& poly : polys) { - writePolyline(poly, color, stroke_width); + writePolyline(poly, color, stroke_width, false); } + + handleFlush(flush); } void SVG::writePolyline(const Polygon& poly, const ColorObject color, const double stroke_width) const @@ -427,6 +433,16 @@ void SVG::writePolyline(const Polygon& poly, const ColorObject color, const doub } } +void SVG::writePolyline(const Polyline& poly, const ColorObject color, const double stroke_width, const bool flush) const +{ + for (auto iterator = poly.beginSegments(); iterator != poly.endSegments(); ++iterator) + { + writeLine((*iterator).start, (*iterator).end, color, stroke_width, false); + } + + handleFlush(flush); +} + void SVG::writePaths(const std::vector& paths, const ColorObject color, const double width_factor) const { for (const VariableWidthLines& lines : paths) @@ -443,7 +459,7 @@ void SVG::writeLines(const VariableWidthLines& lines, const ColorObject color, c } } -void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor) const +void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const double width_factor, const bool flush) const { constexpr double minimum_line_width = 10; // Always have some width, otherwise some lines become completely invisible. if (line.junctions_.empty()) // Only draw lines that have at least 2 junctions, otherwise they are degenerate. @@ -481,6 +497,11 @@ void SVG::writeLine(const ExtrusionLine& line, const ColorObject color, const do start_vertex = end_vertex; // For the next line segment. } + + if (flush) + { + fflush(out_); + } } void SVG::writeCoordinateGrid(const coord_t grid_size, const Color color, const double stroke_width, const double font_size) const diff --git a/src/utils/ToolpathVisualizer.cpp b/src/utils/ToolpathVisualizer.cpp index 88161c78c7..84675f992f 100644 --- a/src/utils/ToolpathVisualizer.cpp +++ b/src/utils/ToolpathVisualizer.cpp @@ -4,6 +4,8 @@ #include +#include "geometry/Point3LL.h" + namespace cura { diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 4b961d2bec..870fda7e0b 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1420,7 +1420,7 @@ void PolygonUtils::fixSelfIntersections(const coord_t epsilon, Shape& polygon) { const Point2LL& other = polygon[poly_idx][(point_idx + 1) % pathlen]; const Point2LL vec = LinearAlg2D::pointIsLeftOfLine(other, a, b) > 0 ? b - a : a - b; - const coord_t len = vSize(vec); + const coord_t len = std::max(vSize(vec), 1LL); pt.X += (-vec.Y * move_dist) / len; pt.Y += (vec.X * move_dist) / len; } diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp new file mode 100644 index 0000000000..ecd63e89cb --- /dev/null +++ b/src/utils/scoring/BestElementFinder.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/BestElementFinder.h" + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +void BestElementFinder::appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold) +{ + WeighedCriterion weighed_criterion; + weighed_criterion.criterion = criterion; + + CriteriaPass criteria_pass; + criteria_pass.outsider_delta_threshold = outsider_delta_threshold; + criteria_pass.criteria.push_back(weighed_criterion); + appendCriteriaPass(criteria_pass); +} + +std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) +{ + // Start by initializing the candidates list in natural order + std::vector best_candidates(candidates_count); + for (size_t i = 0; i < candidates_count; ++i) + { + best_candidates[i].candidate_index = i; + } + + const auto begin = best_candidates.begin(); + auto end = best_candidates.end(); + + // Now run the criteria passes until we have a single outsider or no more cirteria + for (const CriteriaPass& criteria_pass : criteria_) + { + // For each element, reset score, process each criterion and apply weights to get the global score + double best_score = 0.0; + for (auto iterator = begin; iterator != end; ++iterator) + { + iterator->score = 0.0; + + for (const auto& weighed_criterion : criteria_pass.criteria) + { + iterator->score += weighed_criterion.criterion->computeScore(iterator->candidate_index) * weighed_criterion.weight; + } + + best_score = std::max(best_score, iterator->score); + } + + // Skip candidates that have a score too far from the actual best one + const double delta_threshold = criteria_pass.outsider_delta_threshold + std::numeric_limits::epsilon(); + end = std::remove_if( + begin, + end, + [&best_score, &delta_threshold](const Candidate& candidate) + { + return best_score - candidate.score > delta_threshold; + }); + + if (std::distance(begin, end) == 1) + { + // We have a single outsider, don't go further + return begin->candidate_index; + } + } + + return begin != end ? std::make_optional(begin->candidate_index) : std::nullopt; +} + +} // namespace cura diff --git a/src/utils/scoring/CornerScoringCriterion.cpp b/src/utils/scoring/CornerScoringCriterion.cpp new file mode 100644 index 0000000000..05451e6e6d --- /dev/null +++ b/src/utils/scoring/CornerScoringCriterion.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/CornerScoringCriterion.h" + +#include "geometry/PointsSet.h" +#include "utils/linearAlg2D.h" +#include "utils/math.h" + + +namespace cura +{ + +CornerScoringCriterion::CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference) + : points_(points) + , corner_preference_(corner_preference) + , segments_sizes_(points.size()) +{ + // Pre-calculate the segments lengths because we are going to need them multiple times + for (size_t i = 0; i < points.size(); ++i) + { + const Point2LL& here = points_.at(i); + const Point2LL& next = points_.at((i + 1) % points_.size()); + const coord_t segment_size = vSize(next - here); + segments_sizes_[i] = segment_size; + total_length_ += segment_size; + } +} + +double CornerScoringCriterion::computeScore(const size_t candidate_index) const +{ + double corner_angle = cornerAngle(candidate_index); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) + + double score = 0.0; + + switch (corner_preference_) + { + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score = -corner_angle; + } + else + { + score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; + } + + return score; +} + +double CornerScoringCriterion::cornerAngle(size_t vertex_index, const coord_t angle_query_distance) const +{ + const coord_t bounded_distance = std::min(angle_query_distance, total_length_ / 2); + const Point2LL& here = points_.at(vertex_index); + const Point2LL next = findNeighbourPoint(vertex_index, bounded_distance); + const Point2LL previous = findNeighbourPoint(vertex_index, -bounded_distance); + + double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; + + return angle / std::numbers::pi; +} + +Point2LL CornerScoringCriterion::findNeighbourPoint(size_t vertex_index, coord_t distance) const +{ + const int direction = distance > 0 ? 1 : -1; + const int size_delta = distance > 0 ? -1 : 0; + distance = std::abs(distance); + + // Travel on the path until we reach the distance + int actual_delta = 0; + coord_t travelled_distance = 0; + coord_t segment_size = 0; + while (travelled_distance < distance) + { + actual_delta += direction; + segment_size = segments_sizes_[(vertex_index + actual_delta + size_delta + points_.size()) % points_.size()]; + travelled_distance += segment_size; + } + + const Point2LL& next_pos = points_.at((vertex_index + actual_delta + points_.size()) % points_.size()); + + if (travelled_distance > distance) [[likely]] + { + // We have overtaken the required distance, go backward on the last segment + int prev = (vertex_index + actual_delta - direction + points_.size()) % points_.size(); + const Point2LL& prev_pos = points_.at(prev); + + const Point2LL vector = next_pos - prev_pos; + const Point2LL unit_vector = (vector * 1000) / segment_size; + const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); + return prev_pos + vector_delta / 1000; + } + else + { + // Luckily, the required distance stops exactly on an existing point + return next_pos; + } +} + +} // namespace cura diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp new file mode 100644 index 0000000000..d087162365 --- /dev/null +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/DistanceScoringCriterion.h" + +#include "geometry/PointsSet.h" + + +namespace cura +{ + +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type, const double distance_divider) + : points_(points) + , target_pos_(target_pos) + , distance_type_(distance_type) + , distance_divider_(distance_divider) +{ +} + +double DistanceScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + + double distance = 0.0; + switch (distance_type_) + { + case DistanceType::Euclidian: + // Use actual (non-squared) distance to ensure a proper scoring distribution + distance = vSizeMM(candidate_position - target_pos_); + break; + case DistanceType::XOnly: + distance = INT2MM(std::abs(candidate_position.X - target_pos_.X)); + break; + case DistanceType::YOnly: + distance = INT2MM(std::abs(candidate_position.Y - target_pos_.Y)); + break; + } + + // Use reciprocal function to normalize distance score decreasingly + return 1.0 / (1.0 + (distance / distance_divider_)); +} + +} // namespace cura diff --git a/src/utils/scoring/ExclusionAreaScoringCriterion.cpp b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp new file mode 100644 index 0000000000..c4bdd097ed --- /dev/null +++ b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/ExclusionAreaScoringCriterion.h" + +#include "geometry/Shape.h" + + +namespace cura +{ + +ExclusionAreaScoringCriterion::ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area) + : points_(points) + , exclusion_area_(exclusion_area) +{ +} + +double ExclusionAreaScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + return exclusion_area_.inside(candidate_position, true) ? 0.0 : 1.0; +} + +} // namespace cura diff --git a/src/utils/scoring/RandomScoringCriterion.cpp b/src/utils/scoring/RandomScoringCriterion.cpp new file mode 100644 index 0000000000..de6c81280c --- /dev/null +++ b/src/utils/scoring/RandomScoringCriterion.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/RandomScoringCriterion.h" + +#include "utils/math.h" + +namespace cura +{ + +RandomScoringCriterion::RandomScoringCriterion() +{ +} + +double RandomScoringCriterion::computeScore(const size_t /*candidate_index*/) const +{ + return cura::randf(); +} + +} // namespace cura diff --git a/tests/FffGcodeWriterTest.cpp b/tests/FffGcodeWriterTest.cpp index a3c52d6509..452dd4a749 100644 --- a/tests/FffGcodeWriterTest.cpp +++ b/tests/FffGcodeWriterTest.cpp @@ -155,12 +155,12 @@ TEST_F(FffGcodeWriterTest, SurfaceGetsExtraInfillLinesUnderIt) Point2LL last; for (const auto& path:gcode_layer.extruder_plans_[0].paths_) { for (const auto& point: path.points) { - Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, point, last); + Point2LL closest_here = LinearAlg2D::getClosestOnLineSegment(p, point.toPoint2LL(), last); int64_t dist = vSize2(p - closest_here); if (dist optimizer; - /*! * A simple isosceles triangle. Base length and height 50. */ Polygon triangle; PathOrderOptimizerTest() - : optimizer(Point2LL(0, 0)) { } void SetUp() override { - optimizer = PathOrderOptimizer(Point2LL(0, 0)); - triangle.clear(); triangle.push_back(Point2LL(0, 0)); triangle.push_back(Point2LL(50, 0)); @@ -44,6 +36,7 @@ class PathOrderOptimizerTest : public testing::Test */ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); optimizer.optimize(); // Don't crash. EXPECT_EQ(optimizer.paths_.size(), 0) << "Still empty!"; } @@ -54,6 +47,8 @@ TEST_F(PathOrderOptimizerTest, OptimizeWhileEmpty) */ TEST_F(PathOrderOptimizerTest, ThreeTrianglesShortestOrder) { + PathOrderOptimizer optimizer(Point2LL(0, 0)); + Polygon near = triangle; // Copy, then translate. near.translate(Point2LL(100, 100)); Polygon middle = triangle; diff --git a/tests/arcus/MockCommunication.h b/tests/arcus/MockCommunication.h index 0f95cee769..2a86661566 100644 --- a/tests/arcus/MockCommunication.h +++ b/tests/arcus/MockCommunication.h @@ -25,10 +25,8 @@ class MockCommunication : public Communication MOCK_CONST_METHOD0(isSequential, bool()); MOCK_CONST_METHOD1(sendProgress, void(double progress)); MOCK_METHOD3(sendLayerComplete, void(const LayerIndex::value_type& layer_nr, const coord_t& z, const coord_t& thickness)); - MOCK_METHOD5(sendPolygons, void(const PrintFeatureType& type, const Shape& polygons, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity)); - MOCK_METHOD5(sendPolygon, void(const PrintFeatureType& type, const Polygon& polygon, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity)); - MOCK_METHOD5(sendLineTo, void(const PrintFeatureType& type, const Point2LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity)); - MOCK_METHOD1(sendCurrentPosition, void(const Point2LL& position)); + MOCK_METHOD5(sendLineTo, void(const PrintFeatureType& type, const Point3LL& to, const coord_t& line_width, const coord_t& line_thickness, const Velocity& velocity)); + MOCK_METHOD1(sendCurrentPosition, void(const Point3LL& position)); MOCK_METHOD1(setExtruderForSend, void(const ExtruderTrain& extruder)); MOCK_METHOD1(setLayerForSend, void(const LayerIndex::value_type& layer_nr)); MOCK_METHOD0(sendOptimizedLayerData, void()); diff --git a/tests/arcus/MockSocket.cpp b/tests/arcus/MockSocket.cpp index df41690a41..a6860202c4 100644 --- a/tests/arcus/MockSocket.cpp +++ b/tests/arcus/MockSocket.cpp @@ -21,9 +21,10 @@ void MockSocket::reset() { /* Do nothing. */ } -void MockSocket::sendMessage(Arcus::MessagePtr message) +bool MockSocket::sendMessage(Arcus::MessagePtr message) { sent_messages.push_back(message); + return true; } Arcus::MessagePtr MockSocket::takeNextMessage() @@ -45,4 +46,4 @@ Arcus::MessagePtr MockSocket::popMessageFromSendQueue() return result; } -} // namespace cura \ No newline at end of file +} // namespace cura diff --git a/tests/arcus/MockSocket.h b/tests/arcus/MockSocket.h index 272720f216..9bed7467e0 100644 --- a/tests/arcus/MockSocket.h +++ b/tests/arcus/MockSocket.h @@ -4,9 +4,10 @@ #ifndef MOCKSOCKET_H #define MOCKSOCKET_H -#include //Inheriting from this to be able to swap this socket in the tested class. #include //History of sent and received messages. +#include //Inheriting from this to be able to swap this socket in the tested class. + namespace cura { @@ -27,7 +28,7 @@ class MockSocket : public Arcus::Socket void reset() override; // Catch these functions so that we can see whether they are called. - void sendMessage(Arcus::MessagePtr message) override; + bool sendMessage(Arcus::MessagePtr message) override; Arcus::MessagePtr takeNextMessage() override; // Helpers to store send and received messages. @@ -39,4 +40,4 @@ class MockSocket : public Arcus::Socket // NOLINTEND(misc-non-private-member-variables-in-classes) } // namespace cura -#endif // MOCKSOCKET_H \ No newline at end of file +#endif // MOCKSOCKET_H