diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 63f74a069e..6019c2bc8b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: 🐞 Bug Report -description: File a bug report +description: Something behaves incorrectly while Orca Slicer keeps running labels: ["bug"] body: - type: markdown @@ -10,6 +10,8 @@ body: Please note that this is not the place to make feature requests or ask for help. For this, please use the [Feature request](https://github.com/OrcaSlicer/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=feature_request.yml) issue type or you can discuss your idea on our [Discord server](https://discord.gg/P4VE9UY9gJ) with others. + If Orca Slicer closes on its own, freezes or stops responding, please use the [Crash report](https://github.com/OrcaSlicer/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=crash_report.yml) form instead. It asks for the logs a crash needs. + Before filing, please check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment. - type: checkboxes attributes: @@ -47,7 +49,7 @@ body: id: os_type attributes: label: "Operating System (OS)" - description: "What OSes are you are experiencing issues on?" + description: "What OSes are you experiencing issues on?" multiple: true options: - Linux @@ -86,7 +88,7 @@ body: id: reproduce_steps attributes: label: How to reproduce - description: Please described the detailed steps to reproduce this issue + description: Please describe the detailed steps to reproduce this issue placeholder: | 1. Go to '...' 2. Click on '...' @@ -108,28 +110,23 @@ body: description: What should happen after the above steps? validations: required: true - - type: markdown - id: file_required - attributes: - value: | - Please be sure to add the following files: - * Please upload a ZIP archive containing the **project file** used when the problem arise. Please export it just before or after the problem occurs. Even if you did nothing and/or there is no object, export it! (We need the configurations in project file). - You can export the project file from the application menu in `File`->`Save project as...`, then zip it - * A **log file** for crashes and similar issues. - You can find your log file here: - Windows: `%APPDATA%\OrcaSlicer\log` or usually `C:\Users\\AppData\Roaming\OrcaSlicer\log` - MacOS: `$HOME/Library/Application Support/OrcaSlicer/log` - Linux: `$HOME/.config/OrcaSlicer/log` - If Orca Slicer still starts, you can also reach this directory from the application menu in `Help` -> `Show Configuration Folder` - You can zip the log directory, or just select the newest logs when this issue happens, and zip them - type: textarea id: file_uploads attributes: label: Project file & Debug log uploads - description: Drop the project file and debug log here + description: | + Attach the files with the **Paste, drop, or click to add files** control directly underneath this box. Zip anything that is not a `.log`, `.txt` or image, since GitHub rejects other file types, and keep each file under 25 MB. + + * The **project file** used when the problem happened, zipped. Export it just before or after the problem occurs. Even if you did nothing and there is no object on the plate, export it, since we need the configuration it carries. `File` -> `Save project as...` + * The **log folder**, zipped. `Help` -> `Show Configuration Folder` opens it, or find it at: + * Windows: `%APPDATA%\OrcaSlicer\log`, usually `C:\Users\\AppData\Roaming\OrcaSlicer\log` + * macOS: `$HOME/Library/Application Support/OrcaSlicer/log` + * Linux: `$HOME/.config/OrcaSlicer/log` + * Flatpak: `$HOME/.var/app/com.orcaslicer.OrcaSlicer/config/OrcaSlicer/log` + * If the zip comes out over 25 MB, attach the newest logs from that folder on their own instead. placeholder: | - Project File: `File` -> `Save project as...` then zip it & drop it here - Log File: `Help` -> `Show Configuration Folder`, then zip the log directory, or just select the newest logs in `log` when this issue happens and zip them, then drop the zip file here + Zipped project file + Zipped log folder validations: required: true - type: checkboxes @@ -144,7 +141,5 @@ body: label: Anything else? description: | Screenshots? References? Anything that will give us more context about the issue you are encountering! - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. validations: required: false diff --git a/.github/ISSUE_TEMPLATE/crash_report.yml b/.github/ISSUE_TEMPLATE/crash_report.yml new file mode 100644 index 0000000000..bcbee11d36 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash_report.yml @@ -0,0 +1,183 @@ +name: 💥 Crash Report +description: Orca Slicer closes on its own, freezes or stops responding +labels: ["crash"] +body: + - type: markdown + attributes: + value: | + **Thank you for taking the time to report a crash.** + + Use this form when Orca Slicer closes on its own, freezes, or stops responding. + If the application stays open and only produces a wrong result, please use the [Bug report](https://github.com/OrcaSlicer/OrcaSlicer/issues/new?assignees=&labels=&projects=&template=bug_report.yml) form instead. + A printer whose toolhead collides with the print is also a bug report rather than a crash, since the application itself did not stop. + + Before filing, please check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment. + - type: checkboxes + attributes: + label: Is this crash reproducible in the latest nightly build? + description: > + Please verify this crash still happens in the latest nightly build first. It may already be fixed there: + [Nightly builds](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds). + options: + - label: I have checked the latest nightly build and the crash is still reproducible + required: true + - type: checkboxes + attributes: + label: Is there an existing issue for this crash? + description: Please search to see if an issue already exists for the crash you encountered. + options: + - label: I have searched the existing issues + required: true + - type: input + id: version + attributes: + label: OrcaSlicer Version + description: Which version of Orca Slicer are you running? You can see the full version in `Help` -> `About Orca Slicer`. + placeholder: e.g. 2.5.0 + validations: + required: true + - type: input + id: working_version + attributes: + label: Regression compared to a previous version + description: Did it work in a previous version? + placeholder: e.g. 2.3.2 + validations: + required: false + - type: dropdown + id: os_type + attributes: + label: "Operating System (OS)" + description: "What OSes are you seeing the crash on?" + multiple: true + options: + - Linux + - macOS + - Windows + validations: + required: true + - type: input + id: os_version + attributes: + label: "OS Version" + description: "What OS version does this relate to?" + placeholder: "i.e. OS: Windows 7/8/10/11 ..., Ubuntu 22.04/Fedora 36 ..., macOS 10.15/11.1/12.3 ..." + validations: + required: true + - type: input + id: printer + attributes: + label: Printer + description: Which printer was selected + placeholder: Voron 2.4/VzBot/Prusa MK4/Bambu Lab X1 series/Bambu Lab P1P/... + validations: + required: true + - type: dropdown + id: crash_moment + attributes: + label: When does the crash happen? + description: Pick the point where Orca Slicer stops working. + options: + - Not sure + - On startup, before the main window appears + - When opening or importing a project or model + - While changing printer, filament or process settings + - While slicing + - In the 3D view, Preview or Assembly view + - When exporting G-code or sending a print to the printer + - On the Device tab, or connecting to a printer (camera, sync, login) + - While using a specific tool, dialog or calibration + - After resuming from sleep or changing monitors + - When closing the application + - No clear pattern + validations: + required: true + - type: dropdown + id: crash_frequency + attributes: + label: How often does it happen? + options: + - Not sure + - Every time + - Often, but not every time + - Rarely + - It only happened once + validations: + required: true + - type: dropdown + id: fresh_config + attributes: + label: Does it still crash with a fresh configuration? + description: > + Close Orca Slicer and rename your configuration folder (`%APPDATA%\OrcaSlicer` on Windows, + `$HOME/Library/Application Support/OrcaSlicer` on macOS, `$HOME/.config/OrcaSlicer` on Linux), + then start it again. Renaming keeps your settings, so you can put the folder back afterwards. + options: + - I have not tried this + - Yes, it still crashes + - No, the crash goes away + validations: + required: true + - type: textarea + id: reproduce_steps + attributes: + label: How to reproduce + description: Please describe the detailed steps that lead to the crash. + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. Orca Slicer closes + validations: + required: true + - type: textarea + id: system_info + attributes: + label: Additional system information + description: > + Display card and driver version are worth adding for crashes on startup or in the 3D view. + CPU and memory are worth adding for crashes while slicing. + placeholder: | + CPU: 11th gen Intel r core tm i7-1185g7/AMD Ryzen 7 6800h/... + Memory: 32/16 GB... + Display Card: NVIDIA Quadro P400/... + validations: + required: false + - type: textarea + id: file_uploads + attributes: + label: Project file, logs and crash report uploads + description: | + A crash report without logs usually cannot be acted on. Attach the files with the **Paste, drop, or click to add files** control directly underneath this box. Zip anything that is not a `.log`, `.txt` or image, since GitHub rejects other file types, and keep each file under 25 MB. + + * The **project file** used when the crash happened, zipped. Export it just before or after the crash, even if the plate is empty, since we need the configuration it carries. `File` -> `Save project as...` + * The whole **log folder**, zipped rather than single files picked out of it. `Help` -> `Show Configuration Folder` opens it, or find it at: + * Windows: `%APPDATA%\OrcaSlicer\log`, usually `C:\Users\\AppData\Roaming\OrcaSlicer\log` + * macOS: `$HOME/Library/Application Support/OrcaSlicer/log` + * Linux: `$HOME/.config/OrcaSlicer/log` + * Flatpak: `$HOME/.var/app/com.orcaslicer.OrcaSlicer/config/OrcaSlicer/log` + * On Windows the crash itself is written to a separate `crash_*.log` in there, and that is the file we need most. If the zip comes out over 25 MB GitHub will refuse it, so attach the newest log and any `crash_*.log` on their own instead. + * The **operating system crash report**, on macOS and Linux, where Orca Slicer cannot write its own crash log. It is often the only record of where it died: + * macOS: Console.app -> Crash Reports, or `$HOME/Library/Logs/DiagnosticReports/`. The file starts with `OrcaSlicer` and ends in `.ips`. Zip it before attaching, GitHub does not accept `.ips` files. + * Linux: run `orca-slicer` from a terminal (Flatpak: `flatpak run com.orcaslicer.OrcaSlicer`) and paste everything it prints when it dies. On systemd systems `coredumpctl info orca-slicer` gives a backtrace. + placeholder: | + Zipped project file + Zipped log folder + Zipped macOS .ips crash report, or the terminal output on Linux + validations: + required: true + - type: checkboxes + id: file_checklist + attributes: + label: Checklist of files to include + options: + - label: Log folder + - label: Project file + - label: Operating system crash report (macOS and Linux) + - type: textarea + attributes: + label: Anything else? + description: | + Screenshots? References? Anything that will give us more context about the crash you are encountering! + validations: + required: false diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 0ab9cfe41d..10edec5aaa 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -100,7 +100,7 @@ jobs: matrix: include: ${{ fromJSON(vars.SELF_HOSTED && '[{"arch":"x64","os":"orca-win-server","compiler":"clang"}]' - || '[{"arch":"x64","os":"windows-latest","compiler":"clang"},{"arch":"arm64","os":"windows-11-arm","compiler":"clang"}]') }} + || '[{"arch":"x64","os":"windows-latest","compiler":"clang"},{"arch":"arm64","os":"windows-11-vs2026-arm","compiler":"clang"}]') }} needs: check_build_script # Don't run scheduled builds on forks: if: ${{ !cancelled() && needs.check_build_script.result == 'success' && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} @@ -169,7 +169,7 @@ jobs: if: ${{ !cancelled() && success() && !vars.SELF_HOSTED }} uses: ./.github/workflows/unit_tests.yml with: - os: windows-11-arm + os: windows-11-vs2026-arm artifact: ${{ github.sha }}-tests-windows-arm64 test-dir: build-arm64/tests unit_tests_macos_arm64: @@ -283,21 +283,42 @@ jobs: id: fp_cache_key run: echo "key=flatpak-builder-${{ matrix.variant.arch }}-${{ hashFiles('deps/**', 'scripts/flatpak/com.orcaslicer.OrcaSlicer.yml', 'scripts/flatpak/make_deps_tar.sh') }}" >> "$GITHUB_OUTPUT" shell: bash - # Manage flatpak-builder cache externally so PRs restore but never upload + # Manage flatpak-builder cache externally so PRs restore but never upload. + # The compiler cache under it is keyed per run below, so it is left out. - name: Restore flatpak-builder cache if: github.event_name == 'pull_request' uses: actions/cache/restore@v6 with: - path: .flatpak-builder + path: | + .flatpak-builder/* + !.flatpak-builder/ccache key: ${{ steps.fp_cache_key.outputs.key }} restore-keys: flatpak-builder-${{ matrix.variant.arch }}- - name: Save/restore flatpak-builder cache if: github.event_name != 'pull_request' uses: actions/cache@v6 with: - path: .flatpak-builder + path: | + .flatpak-builder/* + !.flatpak-builder/ccache key: ${{ steps.fp_cache_key.outputs.key }} restore-keys: flatpak-builder-${{ matrix.variant.arch }}- + # Compiler cache for the OrcaSlicer module, as in build_orca.yml. Pull + # requests only restore it; every other run (main, release branches, the + # nightly, a dispatch) saves it. orca_deps stays on the state cache above. + - name: Name the compiler cache leg + run: | + leg="Flatpak-${{ matrix.variant.arch }}" + echo "CCACHE_LEG=$leg" >> "$GITHUB_ENV" + echo "CCACHE_ENTRY=ccache-$leg-${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_ENV" + shell: bash + - name: Restore compiler cache + id: ccache_restore + uses: actions/cache/restore@v6 + with: + path: .flatpak-builder/ccache + key: ${{ env.CCACHE_ENTRY }} + restore-keys: ccache-${{ env.CCACHE_LEG }}- - name: Disable debug info for faster CI builds run: | sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' \ @@ -308,6 +329,33 @@ jobs: sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \ scripts/flatpak/com.orcaslicer.OrcaSlicer.yml shell: bash + # flatpak-builder's --ccache only wraps cc and gcc, and the manifest builds + # with clang, so CMake's launcher runs ccache instead; --ccache is still what + # mounts the cache directory into the sandbox. The settings go into that + # directory's own config file, which the sandbox reads too. + - name: Enable compiler cache + run: | + printf ' %s\n' \ + 'CMAKE_C_COMPILER_LAUNCHER: ccache' \ + 'CMAKE_CXX_COMPILER_LAUNCHER: ccache' > "$RUNNER_TEMP/ccache-env.yml" + sed -i "/^ git_commit_hash: /r $RUNNER_TEMP/ccache-env.yml" \ + scripts/flatpak/com.orcaslicer.OrcaSlicer.yml + grep -q '^ CMAKE_CXX_COMPILER_LAUNCHER: ccache$' scripts/flatpak/com.orcaslicer.OrcaSlicer.yml + mkdir -p .flatpak-builder/ccache + export CCACHE_DIR=$PWD/.flatpak-builder/ccache + ccache --set-config=max_size=3G + # The compiler is reinstalled every run, so its mtime means nothing. + ccache --set-config=compiler_check=content + # Headers a fresh checkout has just written, the few files that use + # __DATE__ or __TIME__, and the precompiled header, whose macros ccache + # cannot see. + ccache --set-config=sloppiness=pch_defines,time_macros,include_file_mtime,include_file_ctime + # Hash the includes the compiler reports instead of preprocessing every + # miss before compiling it. + ccache --set-config=depend_mode=true + # The restored directory carries the previous run's counters. + ccache -z + shell: bash - name: Check the manifest keeps orca_deps cacheable run: ./scripts/flatpak/check_manifest_cacheable.sh shell: bash @@ -318,9 +366,50 @@ jobs: with: bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak manifest-path: scripts/flatpak/com.orcaslicer.OrcaSlicer.yml - cache: false + # cache only turns on flatpak-builder --ccache; the caching itself is above. + cache: true + restore-cache: false + save-cache: false arch: ${{ matrix.variant.arch }} upload-artifact: false + # The build has just touched everything it can use, so an object untouched + # for a week is dead, usually orphaned by a flag change. + - name: Compiler cache statistics + if: always() + run: | + export CCACHE_DIR=$PWD/.flatpak-builder/ccache + ccache --evict-older-than 7d + ccache -s -v || ccache -s + shell: bash + # Save the new entry first, then drop the older ones for this leg on this + # ref, so a failed save leaves the previous entry in place. A cancelled or + # failed build saves too, since what it compiled is still valid; a restore + # that did not finish does not, since the directory may be a truncated copy. + - name: Save compiler cache + id: ccache_save + if: ${{ always() && steps.ccache_restore.outcome == 'success' && github.event_name != 'pull_request' }} + uses: actions/cache/save@v6 + with: + path: .flatpak-builder/ccache + key: ${{ env.CCACHE_ENTRY }} + - name: Drop older compiler cache entries + if: ${{ always() && steps.ccache_save.outcome == 'success' }} + # The container has no gh, so this is the list and delete over the REST API. + # Older means a lower run id, so two runs finishing close together keep + # the newer entry whichever of them cleans up last. + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + run: | + api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches" + curl -sSf -H "Authorization: Bearer $GH_TOKEN" \ + "$api?ref=$GITHUB_REF&key=ccache-$CCACHE_LEG-&per_page=100" \ + | jq -r --arg prefix "ccache-$CCACHE_LEG-" --argjson run "$GITHUB_RUN_ID" \ + '.actions_caches[] | select((.key | ltrimstr($prefix) | split("-")[0] | tonumber?) < $run) | .id' \ + | while read -r id; do + curl -sSf -X DELETE -H "Authorization: Bearer $GH_TOKEN" "$api/$id" + done + shell: bash - name: Upload artifacts Flatpak uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 753546ba91..60428036d4 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -76,6 +76,60 @@ jobs: if (-not (Test-Path "$cmakeBin\cmake.exe")) { throw "cmake.exe not found at $cmakeBin" } Add-Content -Path $env:GITHUB_PATH -Value $cmakeBin + # Compiler cache. Pushes save it, so main keeps it warm; pull requests + # restore it and discard what they compiled. Objects are keyed on the + # preprocessed source, the compiler and the flags, so a leg only ever + # hits its own entries. A failed install costs the caching, not the build. + - name: Name the compiler cache leg + if: ${{ !inputs.macos-combine-only }} + shell: bash + run: | + leg="${{ runner.os }}-${{ inputs.arch || 'amd64' }}${{ runner.os == 'Windows' && format('-{0}', inputs.compiler) || '' }}" + echo "CCACHE_LEG=$leg" >> "$GITHUB_ENV" + echo "CCACHE_ENTRY=ccache-$leg-${{ github.run_id }}-${{ github.run_attempt }}" >> "$GITHUB_ENV" + + # The action only installs and configures ccache. Restore and save go + # through actions/cache with one path string, since the cache service + # only matches entries saved under the identical path and the action + # spells it differently on Windows. + - name: Compiler cache + id: ccache + if: ${{ !inputs.macos-combine-only }} + continue-on-error: true + uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: ${{ env.CCACHE_LEG }} + max-size: 3G + restore: false + save: false + # ccache -s runs as its own step; no summary table per job. + job-summary: '' + + - name: Restore compiler cache + id: ccache_restore + if: ${{ steps.ccache.outcome == 'success' }} + uses: actions/cache/restore@v6 + with: + path: ${{ github.workspace }}/.ccache + key: ${{ env.CCACHE_ENTRY }} + restore-keys: ccache-${{ env.CCACHE_LEG }}- + + - name: Enable compiler cache + if: ${{ steps.ccache.outcome == 'success' }} + shell: bash + run: | + echo "CMAKE_C_COMPILER_LAUNCHER=ccache" >> "$GITHUB_ENV" + echo "CMAKE_CXX_COMPILER_LAUNCHER=ccache" >> "$GITHUB_ENV" + # Headers a fresh checkout has just written, the few files that + # use __DATE__ or __TIME__, and the precompiled header, whose + # macros ccache cannot see. + echo "CCACHE_SLOPPINESS=pch_defines,time_macros,include_file_mtime,include_file_ctime" >> "$GITHUB_ENV" + # Hash the includes the compiler reports instead of preprocessing + # every miss before compiling it. + echo "CCACHE_DEPEND=1" >> "$GITHUB_ENV" + # The restored directory carries the previous run's counters. + ccache -z + - name: Get the version and date on Ubuntu and macOS if: runner.os != 'Windows' run: | @@ -693,3 +747,41 @@ jobs: asset_name: orca_custom_preset_tests.zip asset_content_type: application/octet-stream max_releases: 1 + + # The build has just touched everything it can use, so an object + # untouched for a week is dead, usually orphaned by a flag change. + - name: Compiler cache statistics + if: ${{ always() && steps.ccache.outcome == 'success' }} + shell: bash + run: | + ccache --evict-older-than 7d + ccache -s -v || ccache -s + + # Entries are immutable, so the new one is saved first and the older + # ones for this leg on this ref are dropped afterwards: a failed save + # leaves the previous entry in place. A cancelled or failed build saves + # too, since what it compiled is still valid; a restore that did not + # finish does not, since the directory may be a truncated copy. + - name: Save compiler cache + id: ccache_save + if: ${{ always() && steps.ccache_restore.outcome == 'success' && github.event_name != 'pull_request' }} + uses: actions/cache/save@v6 + with: + path: ${{ github.workspace }}/.ccache + key: ${{ env.CCACHE_ENTRY }} + + - name: Drop older compiler cache entries + if: ${{ always() && steps.ccache_save.outcome == 'success' }} + # A read-only token (fork PRs) cannot delete; that only costs storage. + # Older means a lower run id, so two runs finishing close together keep + # the newer entry whichever of them cleans up last. + continue-on-error: true + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + gh cache list --ref "$GITHUB_REF" --key "ccache-$CCACHE_LEG-" --limit 100 --json id,key \ + | jq -r --arg prefix "ccache-$CCACHE_LEG-" --argjson run "$GITHUB_RUN_ID" \ + '.[] | select((.key | ltrimstr($prefix) | split("-")[0] | tonumber?) < $run) | .id' \ + | tr -d '\r' \ + | while read -r id; do gh cache delete "$id"; done diff --git a/.github/workflows/doxygen-docs.yml b/.github/workflows/doxygen-docs.yml index 6af7255fa3..d7d2f982e7 100644 --- a/.github/workflows/doxygen-docs.yml +++ b/.github/workflows/doxygen-docs.yml @@ -19,11 +19,18 @@ jobs: permissions: contents: read steps: - - uses: thejerrybao/setup-swap-space@v1 - with: - swap-space-path: /swapfile - swap-size-gb: 8 - remove-existing-swap-files: true + # Doxygen with call graphs over all of src/ outgrows the runner's RAM; + # replace the runner's swapfile with an 8 GB one. + - name: Grow swap space + run: | + set -euo pipefail + sudo swapoff -a + sudo rm -f /swapfile + sudo fallocate -l 8G /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + free -h - name: Checkout repository uses: actions/checkout@v7 diff --git a/.github/workflows/parity_nightly.yml b/.github/workflows/parity_nightly.yml new file mode 100644 index 0000000000..79f5c9b514 --- /dev/null +++ b/.github/workflows/parity_nightly.yml @@ -0,0 +1,219 @@ +# Nightly parity checks from OrcaSlicer/orca-test-repo, kept out of the +# per-build "Run external slicer regression tests" step because they take far +# longer than that step's budget: +# effect - the CLI override sweep's full effect stage: every landed option +# re-sliced on its own to see whether it changes the G-code +# harness - the GUI-vs-CLI parity harness (metrics only, never fails) +# Both test the latest successful build_all.yml Linux AppImage from main, with +# sources checked out at the commit that build was made from. Nothing here +# gates a build or a PR. +name: Parity Nightly + +on: + schedule: + # build_all.yml starts at 17:00 UTC and has finished by ~20:00 + - cron: "0 21 * * *" + workflow_dispatch: + inputs: + test_repo_ref: + description: "orca-test-repo ref to run" + required: false + default: "main" + build_branch: + description: "branch whose latest successful build_all artifact to test" + required: false + default: "main" + fixtures: + description: "harness fixture ids, space-separated (empty = all)" + required: false + default: "" + cli_presets: + description: "harness lane C presets: flat = flatten inherits first, raw = leaf profile as-is" + required: false + default: "flat" + +permissions: + contents: read + actions: read + +jobs: + build: + name: Find the build to test + # Don't run scheduled checks on forks + if: github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer' + runs-on: ubuntu-24.04 + outputs: + run_id: ${{ steps.find.outputs.run_id }} + head_sha: ${{ steps.find.outputs.head_sha }} + steps: + - id: find + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + gh run list --workflow build_all.yml \ + --branch "${{ inputs.build_branch || 'main' }}" \ + --status success --limit 1 --json databaseId,headSha \ + --jq '"run_id=\(.[0].databaseId)\nhead_sha=\(.[0].headSha)"' \ + >> "$GITHUB_OUTPUT" + cat "$GITHUB_OUTPUT" + + effect: + name: Override sweep effect stage (shard ${{ matrix.shard }}) + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + # orca-test-repo's parity/effect_routing.json holds a 2-way split, + # ~12.5 min a shard on this runner + shard: [0, 1] + steps: + - &checkout-suite + name: Check out the test suite + uses: actions/checkout@v7 + with: + repository: OrcaSlicer/orca-test-repo + ref: ${{ inputs.test_repo_ref || 'main' }} + path: orca-test-repo + + # The AppImage ships only packed preset caches, so profiles and the CLI + # option surface come from the sources the build was made from + - &checkout-slicer + name: Check out OrcaSlicer at the build's commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.build.outputs.head_sha }} + path: slicer + lfs: 'false' + + - &extract-appimage + name: Download and extract the Linux AppImage + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + gh run download "${{ needs.build.outputs.run_id }}" --dir appimage \ + --pattern "OrcaSlicer_Linux_ubuntu_2404*" + appimage=$(find appimage -name "*.AppImage" ! -name "*aarch64*" | head -1) + [ -n "$appimage" ] || { echo "no x86_64 AppImage in run ${{ needs.build.outputs.run_id }}"; exit 1; } + chmod +x "$appimage" + "$appimage" --appimage-extract > /dev/null + # The bare binary cannot find the AppImage's bundled libraries; AppRun + # sets them up and execs it, so exit codes and signals pass through + [ -x squashfs-root/AppRun ] || { echo "no AppRun in the AppImage"; exit 1; } + echo "ORCA_BIN=$PWD/squashfs-root/AppRun" >> "$GITHUB_ENV" + echo "ORCA_SOURCE=$PWD/slicer" >> "$GITHUB_ENV" + + - name: Install the AppImage's host runtime dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install suite dependencies + run: pip install -r orca-test-repo/requirements.txt + + - name: Run the override sweep with the full effect stage + id: run + continue-on-error: true + working-directory: orca-test-repo + run: | + set -o pipefail + # -rA keeps the per-stage summaries, which pytest otherwise swallows + # for passing tests + python -m pytest test_cli_overrides.py -c pytest.ini -v -rA \ + --effect-full --effect-shard ${{ matrix.shard }}/2 \ + --orca-bin "$ORCA_BIN" --orca-source "$ORCA_SOURCE" \ + 2>&1 | tee ../sweep.log + + - name: Publish job summary + if: always() + run: | + { + echo "## Override sweep effect stage, shard ${{ matrix.shard }}/2" + echo "Build ${{ needs.build.outputs.head_sha }} (run ${{ needs.build.outputs.run_id }})" + echo '```' + grep -E "\[override sweep" sweep.log || echo "no stage summaries, see the log" + grep -E "^=+ .*(passed|failed)" sweep.log | tail -1 || true + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload the override report + if: always() + uses: actions/upload-artifact@v7 + with: + name: override-report-shard${{ matrix.shard }} + path: | + orca-test-repo/.pytest_cache/override_report.json + sweep.log + if-no-files-found: warn + retention-days: 30 + + # The sweep step continues on error so the summary and report still get + # published; this puts the failure back on the job + - name: Fail the job if the sweep failed + if: steps.run.outcome == 'failure' + run: | + echo "the override sweep failed, see the job summary and the uploaded report" >&2 + exit 1 + + harness: + name: GUI-vs-CLI parity harness + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 180 + steps: + - *checkout-suite + - *checkout-slicer + - *extract-appimage + + - name: Install display tooling and the AppImage's host runtime + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + xvfb xdotool imagemagick openbox mesa-utils \ + libopengl0 libglu1-mesa libgl1 libegl1 libwebkit2gtk-4.1-0 + + - name: Run the parity harness + run: | + set -euo pipefail + fixtures=() + for f in ${{ inputs.fixtures || '' }}; do + fixtures+=(--fixture "$f") + done + # 2 GUI displays: ~1.5 cores peak / ~1.9 GB on this 4-vCPU runner, + # and each fixture is fully isolated, so results match a serial run + python3 orca-test-repo/parity/run_parity.py \ + --slicer-root "$ORCA_SOURCE" --bin "$ORCA_BIN" \ + --cli-presets "${{ inputs.cli_presets || 'flat' }}" \ + --gui-workers 2 --out "$PWD/parity-out" "${fixtures[@]}" + + - name: Publish job summary + if: always() + run: | + if [ -f parity-out/report.md ]; then + cat parity-out/report.md >> "$GITHUB_STEP_SUMMARY" + else + echo "the harness produced no report, see the log" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Drop per-lane datadirs before upload + if: always() + run: rm -rf parity-out/*/seed parity-out/*/datadir-* || true + + - name: Upload the scorecard and evidence + if: always() + uses: actions/upload-artifact@v7 + with: + name: parity-scorecard + path: parity-out/ + if-no-files-found: warn + retention-days: 30 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5620875d4f..6e713d8c88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,10 @@ endif() cmake_minimum_required(VERSION 3.13) +if(POLICY CMP0177) + cmake_policy(SET CMP0177 NEW) +endif() + # The following line used to be in tests/CMakeLists.txt # Having it there causes rebuilds of all targets on any CMakeLists.txt change under tests/ @@ -553,59 +557,92 @@ if ((NOT MSVC OR IS_CLANG_CL) AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR elseif (NOT MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) endif () - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) - # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. - add_compile_options(-Werror=return-type) + # Every warning is an error unless it appears in one of the two lists below. + # disabled - never wanted. Off everywhere, so it never warns or errors. + # demoted - wanted, not cleared yet. Still warns, does not error. - # Since some portions of code are just commented out or put under conditional compilation, there are - # a bunch of warning related to unused functions and variables. Suppress those warnings to not pollute - # compilers diagnostics output with warnings we not going to look at - add_compile_options(-Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-label -Wno-unused-local-typedefs) + # Disabled. + set(warnings_disabled + reorder # members initialised in an order we chose + sign-compare # signed/unsigned comparisons throughout + misleading-indentation # false positives on mixed tabs and spaces + switch # unhandled enum value in a switch + unused-function # commented-out or conditionally compiled code + unused-variable # commented-out or conditionally compiled code + unused-but-set-variable # commented-out or conditionally compiled code + unused-label # commented-out or conditionally compiled code + unused-local-typedefs # commented-out or conditionally compiled code + ) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND warnings_disabled deprecated-declarations) # legacy OpenGL calls + endif () + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0) + list(APPEND warnings_disabled ignored-attributes) # from Eigen headers marked SYSTEM + endif () + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND warnings_disabled unknown-pragmas) # igl pragmas, GCC bug 66943 + endif () + foreach (w IN LISTS warnings_disabled) + add_compile_options(-Wno-${w}) + endforeach () - # Ignore signed/unsigned comparison warnings - add_compile_options(-Wno-sign-compare) + # GCC is not built in CI, so don't throw errors CI won't catch. + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + add_compile_options(-Werror=return-type) + else () + # Turn everything else into an error. Dependency headers are exempt because the + # SYSTEM include flag (-imsvc on clang-cl, -isystem elsewhere) keeps their + # diagnostics out. + add_compile_options(-Werror) + endif () - # The mismatch of tabs and spaces throughout the project can sometimes - # cause this warning to appear even though the indentation is fine. - # Some includes also cause the warning - add_compile_options(-Wno-misleading-indentation) + # Demoted. Remove a name once its category is cleared on every compiler. + set(warnings_demoted) + if (APPLE) + list(APPEND warnings_demoted + # MacDarkMode.mm makes two calls to AppKit's private titlebarViewController + # and one to a wxWidgets category on NSTableColumn whose header is not + # imported. Clearing it means declaring the private selectors ourselves, which + # needs a macOS build to verify. + objc-method-access + ) + endif () + if (WIN32 AND CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") + list(APPEND warnings_demoted + # About two dozen GetProcAddress casts, most in the vendored dark_mode.hpp, + # retype FARPROC to a real signature. The __stdcall typedefs are identical to + # FARPROC on x64, so only arm64 reports them. Clearing them is a separate + # sweep. + cast-function-type-mismatch + ) + endif () + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND warnings_demoted + # enum-constexpr-conversion is a Clang warning that defaults to an error, + # present through clang 20 and gone in clang 21. + enum-constexpr-conversion + ) + endif () - # Disable warning if enum value does not have a corresponding case in switch statement - add_compile_options(-Wno-switch) - - # removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) - # https://eigen.tuxfamily.org/bz/show_bug.cgi?id=1221 - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0) - add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM - endif() - - # Clang reports legacy OpenGL calls as deprecated. Turn off the warning for now - # to reduce the clutter, we know about this one. It should be reenabled after - # we finally get rid of the deprecated code. - if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - add_compile_options(-Wno-deprecated-declarations) - endif() - - if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15) - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-Wno-error=enum-constexpr-conversion HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV) - if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV) - add_compile_options(-Wno-error=enum-constexpr-conversion) - endif() - endif() - - #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 - # We will turn the warning of for GCC for now: - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 - # We will turn the warning of for GCC for now: - add_compile_options(-Wno-unknown-pragmas) - endif() + # The list mixes names not every compiler has, so add each exception only where the + # compiler knows the warning. Probe with the positive -W, which an unknown + # warning fails on both compilers (GCC errors, Clang reports unknown-warning-option). + # An option that takes a =N argument rejects the bare -W, so fall back to + # -W=1 and demote with the trailing =. + include(CheckCXXCompilerFlag) + foreach (category IN LISTS warnings_demoted) + string(MAKE_C_IDENTIFIER "ORCA_HAS_W_${category}" _orca_has_w) + check_cxx_compiler_flag("-W${category}" ${_orca_has_w}) + if (${_orca_has_w}) + add_compile_options(-Wno-error=${category}) + else () + check_cxx_compiler_flag("-W${category}=1" ${_orca_has_w}_arg) + if (${${_orca_has_w}_arg}) + add_compile_options(-Wno-error=${category}=) + endif () + endif () + endforeach () # Compress the debug info with zstd to save space in Flatpak CI builds if(FLATPAK) @@ -615,10 +652,6 @@ if ((NOT MSVC OR IS_CLANG_CL) AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR endif() endif() - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=template-id-cdtor" ) - endif() - endif() if (SLIC3R_ASAN) @@ -1208,8 +1241,9 @@ endif () if (NOT SLIC3R_WARNINGS) add_compile_options(-w) elseif (MSVC AND NOT IS_CLANG_CL) - # /we4715 is C4715, no return from a non-void function, matching the - # -Werror=return-type the GNU/Clang builds apply. + # /we4715 is C4715, no return from a non-void function, an error on the GNU/Clang + # builds under -Werror. MSVC is not in that model, so this stays a single promoted + # warning. add_compile_options(/W3 /we4715) endif () diff --git a/cmake/modules/PrecompiledHeader.cmake b/cmake/modules/PrecompiledHeader.cmake index 7ef80aacff..7d8b3a5603 100644 --- a/cmake/modules/PrecompiledHeader.cmake +++ b/cmake/modules/PrecompiledHeader.cmake @@ -256,6 +256,13 @@ function(add_precompiled_header _target _input) message(STATUS "Adding precompiled header ${_input} to target ${_target}.") target_precompile_headers(${_target} PRIVATE ${_input}) + # Clang records the modification time of every input in the precompiled + # header, which makes it differ between two checkouts of the same source + # and defeats a compiler cache. The build system already rebuilds the + # header when an input changes. + target_compile_options(${_target} PRIVATE + "$<$:SHELL:-Xclang -fno-pch-timestamp>") + get_target_property(_sources ${_target} SOURCES) list(FILTER _sources INCLUDE REGEX ".*\\.mm?") diff --git a/deps/wxWidgets/0001-macos-use-srgb-colour-components.patch b/deps/wxWidgets/0001-macos-use-srgb-colour-components.patch new file mode 100644 index 0000000000..decbee0ad9 --- /dev/null +++ b/deps/wxWidgets/0001-macos-use-srgb-colour-components.patch @@ -0,0 +1,29 @@ +diff --git a/src/osx/cocoa/colour.mm b/src/osx/cocoa/colour.mm +index 31515d146f..86b33e94a2 100644 +--- a/src/osx/cocoa/colour.mm ++++ b/src/osx/cocoa/colour.mm +@@ -125,3 +125,3 @@ + wxOSXEffectiveAppearanceSetter helper; +- if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpaceName:NSCalibratedRGBColorSpace] ) ++ if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpace:[NSColorSpace sRGBColorSpace]] ) + return [colRGBA redComponent]; +@@ -134,3 +134,3 @@ + wxOSXEffectiveAppearanceSetter helper; +- if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpaceName:NSCalibratedRGBColorSpace] ) ++ if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpace:[NSColorSpace sRGBColorSpace]] ) + return [colRGBA greenComponent]; +@@ -143,3 +143,3 @@ + wxOSXEffectiveAppearanceSetter helper; +- if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpaceName:NSCalibratedRGBColorSpace] ) ++ if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpace:[NSColorSpace sRGBColorSpace]] ) + return [colRGBA blueComponent]; +@@ -152,3 +152,3 @@ + wxOSXEffectiveAppearanceSetter helper; +- if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpaceName:NSCalibratedRGBColorSpace] ) ++ if ( NSColor* colRGBA = [m_nsColour colorUsingColorSpace:[NSColorSpace sRGBColorSpace]] ) + return [colRGBA alphaComponent]; +@@ -160,3 +160,3 @@ + { +- return [m_nsColour colorUsingColorSpaceName:NSCalibratedRGBColorSpace] != nil; ++ return [m_nsColour colorUsingColorSpace:[NSColorSpace sRGBColorSpace]] != nil; + } diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 1e2cc85f78..e57e82f3e9 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -21,12 +21,22 @@ else () set(_wx_edge "-DwxUSE_WEBVIEW_EDGE=OFF") endif () +set(_wx_patch_command "") +if (APPLE) + set(_wx_patch_command + ${GIT_EXECUTABLE} checkout -f -- src/osx/cocoa/colour.mm + COMMAND ${GIT_EXECUTABLE} apply --verbose + ${CMAKE_CURRENT_LIST_DIR}/0001-macos-use-srgb-colour-components.patch + ) +endif () + orcaslicer_add_cmake_project( wxWidgets GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets" GIT_TAG v3.3.2 GIT_SHALLOW ON GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp + PATCH_COMMAND ${_wx_patch_command} DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG} CMAKE_ARGS -DwxBUILD_PRECOMP=ON diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 92b25f26c0..a063ab0484 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -2293,8 +2293,6 @@ msgstr "" msgid "%s has been removed." msgstr "" -msgid "Switching application language" -msgstr "" msgid "Select the language" msgstr "" @@ -4997,9 +4995,7 @@ msgstr "" #, possible-c-format, possible-boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" #, possible-boost-format @@ -8294,8 +8290,6 @@ msgstr "" msgid "Language selection" msgstr "" -msgid "Switching application language while some presets are modified." -msgstr "" msgid "Asia-Pacific" msgstr "" diff --git a/localization/i18n/ca/OrcaSlicer_ca.po b/localization/i18n/ca/OrcaSlicer_ca.po index 91ba0e2f2a..4fee47786f 100644 --- a/localization/i18n/ca/OrcaSlicer_ca.po +++ b/localization/i18n/ca/OrcaSlicer_ca.po @@ -2522,8 +2522,6 @@ msgstr "Hi ha una actualització disponible. Obriu el quadre de diàleg del paqu msgid "%s has been removed." msgstr "%s s'ha eliminat." -msgid "Switching application language" -msgstr "Canvi d'idioma de l'aplicació" msgid "Select the language" msgstr "Seleccioneu l'idioma" @@ -5431,13 +5429,9 @@ msgstr "El valor %s està fora de rang. El rang vàlid és de %d a %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"És %s%% or %s %s?\n" -"SÍ per %s%%.\n" -"NO per %s %s." +"És %s%% or %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8924,8 +8918,6 @@ msgstr "Voleu continuar?" msgid "Language selection" msgstr "Selecció d'idiomes" -msgid "Switching application language while some presets are modified." -msgstr "Canviant l'idioma de l'aplicació mentre es modifiquen alguns perfils." msgid "Asia-Pacific" msgstr "Àsia-Pacífic" diff --git a/localization/i18n/cs/OrcaSlicer_cs.po b/localization/i18n/cs/OrcaSlicer_cs.po index 9884888723..f79c90bcd1 100644 --- a/localization/i18n/cs/OrcaSlicer_cs.po +++ b/localization/i18n/cs/OrcaSlicer_cs.po @@ -2482,8 +2482,6 @@ msgstr "Je k dispozici aktualizace. Otevřete dialog balíčku předvoleb a prov msgid "%s has been removed." msgstr "%s bylo odstraněno." -msgid "Switching application language" -msgstr "Přepnutí jazyka aplikace" msgid "Select the language" msgstr "Zvolte jazyk" @@ -5388,13 +5386,9 @@ msgstr "Hodnota %s je mimo rozsah. Platný rozsah je od %d do %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Je to %s%% nebo %s %s?\n" -"ANO pro %s%%,\n" -"NE pro %s %s." +"Je to %s%% nebo %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8882,8 +8876,6 @@ msgstr "Chcete pokračovat?" msgid "Language selection" msgstr "Výběr jazyka" -msgid "Switching application language while some presets are modified." -msgstr "Přepnutí jazyka aplikace, když jsou některé předvolby upraveny." msgid "Asia-Pacific" msgstr "Asie-Pacifik" diff --git a/localization/i18n/de/OrcaSlicer_de.po b/localization/i18n/de/OrcaSlicer_de.po index bb096c0a23..2a3e4e19d6 100644 --- a/localization/i18n/de/OrcaSlicer_de.po +++ b/localization/i18n/de/OrcaSlicer_de.po @@ -2430,8 +2430,6 @@ msgstr "Es ist ein Update verfügbar. Öffnen Sie den Profilbündel-Dialog, um e msgid "%s has been removed." msgstr "%s wurde entfernt." -msgid "Switching application language" -msgstr "Wechsel der Sprache" msgid "Select the language" msgstr "Sprache wählen" @@ -5293,13 +5291,9 @@ msgstr "Wert %s ist außerhalb des Bereichs. Der gültige Bereich liegt zwischen #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Heißt es %s%% oder %s %s?\n" -"Ja für %s%%, \n" -"Nein für %s %s." +"Heißt es %s%% oder %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8754,8 +8748,6 @@ msgstr "Möchten Sie fortfahren?" msgid "Language selection" msgstr "Sprachauswahl" -msgid "Switching application language while some presets are modified." -msgstr "Umschalten der Anwendungssprache, während einige Profile geändert werden." msgid "Asia-Pacific" msgstr "Asien-Pazifik" diff --git a/localization/i18n/en/OrcaSlicer_en.po b/localization/i18n/en/OrcaSlicer_en.po index b949aea730..37485ba662 100644 --- a/localization/i18n/en/OrcaSlicer_en.po +++ b/localization/i18n/en/OrcaSlicer_en.po @@ -2289,8 +2289,6 @@ msgstr "" msgid "%s has been removed." msgstr "" -msgid "Switching application language" -msgstr "" msgid "Select the language" msgstr "" @@ -4993,9 +4991,7 @@ msgstr "" #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" #, boost-format @@ -8290,8 +8286,6 @@ msgstr "" msgid "Language selection" msgstr "" -msgid "Switching application language while some presets are modified." -msgstr "" msgid "Asia-Pacific" msgstr "" diff --git a/localization/i18n/es/OrcaSlicer_es.po b/localization/i18n/es/OrcaSlicer_es.po index e1d86c2fe8..9dfcde53e5 100644 --- a/localization/i18n/es/OrcaSlicer_es.po +++ b/localization/i18n/es/OrcaSlicer_es.po @@ -2356,8 +2356,6 @@ msgstr "Hay una actualización disponible. Abra el cuadro de diálogo del paquet msgid "%s has been removed." msgstr "Se ha eliminado %s." -msgid "Switching application language" -msgstr "Cambiando el idioma de la aplicación" msgid "Select the language" msgstr "Seleccionar el idioma" @@ -5157,13 +5155,9 @@ msgstr "El valor %s está fuera de rango. El rango válido es de %d a %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"¿Es %s%% o %s %s?\n" -"SÍ para %s%%, \n" -"NO para %s %s." +"¿Es %s%% o %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8529,8 +8523,6 @@ msgstr "¿Quieres continuar?" msgid "Language selection" msgstr "Selección de idiomas" -msgid "Switching application language while some presets are modified." -msgstr "Cambiando idioma de la aplicación mientras se modifican algunos perfiles." msgid "Asia-Pacific" msgstr "Asia-Pacífico" diff --git a/localization/i18n/eu/OrcaSlicer_eu.po b/localization/i18n/eu/OrcaSlicer_eu.po index a475d76ab2..b966a124cb 100644 --- a/localization/i18n/eu/OrcaSlicer_eu.po +++ b/localization/i18n/eu/OrcaSlicer_eu.po @@ -2390,8 +2390,6 @@ msgstr "Eguneratze bat dago erabilgarri. Ireki aurrezarpen-paketeen elkarrizketa msgid "%s has been removed." msgstr "%s kendu da." -msgid "Switching application language" -msgstr "Aplikazioaren hizkuntza aldatzen" msgid "Select the language" msgstr "Hautatu hizkuntza" @@ -5205,13 +5203,9 @@ msgstr "%s balioa tartetik kanpo dago. Baliozko tartea %d eta %d artekoa da." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%% edo %s %s da?\n" -"BAI %s%%-(r)entzat,\n" -"EZ %s %s-(r)entzat." +"%s%% edo %s %s da?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8610,8 +8604,6 @@ msgstr "Jarraitu nahi duzu?" msgid "Language selection" msgstr "Hizkuntza-hautaketa" -msgid "Switching application language while some presets are modified." -msgstr "Aplikazioaren hizkuntza aldatzen ari da aurrezarpen batzuk aldatuta dauden bitartean." msgid "Asia-Pacific" msgstr "Asia-Pazifikoa" diff --git a/localization/i18n/fr/OrcaSlicer_fr.po b/localization/i18n/fr/OrcaSlicer_fr.po index de356edd7d..9d8739babe 100644 --- a/localization/i18n/fr/OrcaSlicer_fr.po +++ b/localization/i18n/fr/OrcaSlicer_fr.po @@ -2414,8 +2414,6 @@ msgstr "Une mise à jour est disponible. Ouvrez la boîte de dialogue du paquet msgid "%s has been removed." msgstr "%s a été supprimé." -msgid "Switching application language" -msgstr "Changer la langue de l'application" msgid "Select the language" msgstr "Sélectionner la langue" @@ -5243,13 +5241,9 @@ msgstr "La valeur %s est hors plage. La plage valide est comprise entre %d et %d #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Est-ce %s%% ou %s %s ?\n" -"OUI pour %s%%, \n" -"NON pour %s %s." +"Est-ce %s%% ou %s %s ?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8678,8 +8672,6 @@ msgstr "Voulez-vous continuer ?" msgid "Language selection" msgstr "Sélection de la langue" -msgid "Switching application language while some presets are modified." -msgstr "Changement de langue de l’application alors que certains préréglages sont modifiés." msgid "Asia-Pacific" msgstr "Asie-Pacifique" diff --git a/localization/i18n/hu/OrcaSlicer_hu.po b/localization/i18n/hu/OrcaSlicer_hu.po index 2c00949e15..198d3decac 100644 --- a/localization/i18n/hu/OrcaSlicer_hu.po +++ b/localization/i18n/hu/OrcaSlicer_hu.po @@ -2460,8 +2460,6 @@ msgstr "Frissítés érhető el. Nyisd meg a beállításcsomag párbeszédablak msgid "%s has been removed." msgstr "%s eltávolítva." -msgid "Switching application language" -msgstr "Alkalmazás nyelvének váltása" msgid "Select the language" msgstr "Válaszd ki a nyelvet" @@ -5340,13 +5338,9 @@ msgstr "%s érték tartományon kívül van. Az érvényes tartomány: %d - %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%% vagy %s %s?\n" -"IGEN %s%%, \n" -"NEM %s %s." +"%s%% vagy %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8806,8 +8800,6 @@ msgstr "Szeretnéd folytatni?" msgid "Language selection" msgstr "Nyelv kiválasztása" -msgid "Switching application language while some presets are modified." -msgstr "Alkalmazás nyelvének átváltása, miközben egyes beállítások módosultak." msgid "Asia-Pacific" msgstr "Ázsia-Csendes-óceáni térség" diff --git a/localization/i18n/it/OrcaSlicer_it.po b/localization/i18n/it/OrcaSlicer_it.po index 1aa2f15701..bc9a0980f2 100644 --- a/localization/i18n/it/OrcaSlicer_it.po +++ b/localization/i18n/it/OrcaSlicer_it.po @@ -2466,8 +2466,6 @@ msgstr "È disponibile un aggiornamento. Apri la finestra di dialogo del bundle msgid "%s has been removed." msgstr "%s è stato rimosso." -msgid "Switching application language" -msgstr "Cambio lingua applicazione" msgid "Select the language" msgstr "Seleziona la lingua" @@ -5341,13 +5339,9 @@ msgstr "Il valore %s è fuori intervallo. L'intervallo valido è da %d a %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"È %s%% o %s %s?\n" -"Sì per %s%%, \n" -"NO per %s %s." +"È %s%% o %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8807,8 +8801,6 @@ msgstr "Vuoi continuare?" msgid "Language selection" msgstr "Selezione lingua" -msgid "Switching application language while some presets are modified." -msgstr "Cambio lingua applicazione durante la modifica di alcuni profili." msgid "Asia-Pacific" msgstr "Asia-Pacifico" diff --git a/localization/i18n/ja/OrcaSlicer_ja.po b/localization/i18n/ja/OrcaSlicer_ja.po index 9067928361..ed9960bb8a 100644 --- a/localization/i18n/ja/OrcaSlicer_ja.po +++ b/localization/i18n/ja/OrcaSlicer_ja.po @@ -2473,8 +2473,6 @@ msgstr "アップデートが利用可能です。プリセットバンドルの msgid "%s has been removed." msgstr "%sを削除しました。" -msgid "Switching application language" -msgstr "アプリケーション言語の切り替え" msgid "Select the language" msgstr "言語を選択" @@ -5355,12 +5353,9 @@ msgstr "値%sは範囲外です。有効な範囲は%dから%dです。" #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%% か、それとも %s %sですか?\n" -"%s%% の場合ははい、 %s %s はいいえ。" +"%s%% か、それとも %s %sですか?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8825,8 +8820,6 @@ msgstr "続行しますか?" msgid "Language selection" msgstr "言語選択" -msgid "Switching application language while some presets are modified." -msgstr "アプリケーション言語を切り替える時に、プリセットの変更があります" msgid "Asia-Pacific" msgstr "アジア太平洋地域" diff --git a/localization/i18n/ko/OrcaSlicer_ko.po b/localization/i18n/ko/OrcaSlicer_ko.po index fef0a09398..16dc582437 100644 --- a/localization/i18n/ko/OrcaSlicer_ko.po +++ b/localization/i18n/ko/OrcaSlicer_ko.po @@ -2481,8 +2481,6 @@ msgstr "사용 가능한 업데이트가 있습니다. 사전 설정 번들 대 msgid "%s has been removed." msgstr "%s이(가) 제거되었습니다." -msgid "Switching application language" -msgstr "응용 프로그램 언어 전환" msgid "Select the language" msgstr "언어 선택" @@ -5366,13 +5364,9 @@ msgstr "값 %s이 범위를 벗어났습니다. 유효한 범위는 %d에서 %d #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%% 또는 %s %s입니까?\n" -"%s%%에 대해 예,\n" -"%s %s에 대해 아니요." +"%s%% 또는 %s %s입니까?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8860,8 +8854,6 @@ msgstr "계속하시겠습니까?" msgid "Language selection" msgstr "언어 선택" -msgid "Switching application language while some presets are modified." -msgstr "일부 사전 설정이 수정되는 동안 응용 프로그램 언어를 전환합니다." msgid "Asia-Pacific" msgstr "아시아 태평양" diff --git a/localization/i18n/lt/OrcaSlicer_lt.po b/localization/i18n/lt/OrcaSlicer_lt.po index 80273427f0..0141917d77 100644 --- a/localization/i18n/lt/OrcaSlicer_lt.po +++ b/localization/i18n/lt/OrcaSlicer_lt.po @@ -2449,8 +2449,6 @@ msgstr "Yra prieinamas atnaujinimas. Atidarykite profilių paketo dialogo langą msgid "%s has been removed." msgstr "%s buvo pašalintas." -msgid "Switching application language" -msgstr "Perjungiama programos kalba" msgid "Select the language" msgstr "Pasirinkite kalbą" @@ -5327,13 +5325,9 @@ msgstr "Reikšmė %s yra už ribų. Galimas diapazonas yra nuo %d iki %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Ar tai %s%% ar %s %s?\n" -"TAIP %s%%, \n" -"NE %s %s." +"Ar tai %s%% ar %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8797,8 +8791,6 @@ msgstr "Ar norite tęsti?" msgid "Language selection" msgstr "Kalbos pasirinkimas" -msgid "Switching application language while some presets are modified." -msgstr "Keičiama programos kalba, kai yra pakeistų profilių." msgid "Asia-Pacific" msgstr "Azija-Ramusis vandenynas" diff --git a/localization/i18n/nl/OrcaSlicer_nl.po b/localization/i18n/nl/OrcaSlicer_nl.po index 720f4ba6f5..a767093bb8 100644 --- a/localization/i18n/nl/OrcaSlicer_nl.po +++ b/localization/i18n/nl/OrcaSlicer_nl.po @@ -2679,8 +2679,6 @@ msgstr "Er is een update beschikbaar. Open het dialoogvenster voor de voorinstel msgid "%s has been removed." msgstr "%s is verwijderd." -msgid "Switching application language" -msgstr "De taal van de applicatie wordt aangepast" msgid "Select the language" msgstr "Kies de taal" @@ -5833,13 +5831,9 @@ msgstr "Waarde %s valt buiten het bereik. Het geldige bereik loopt van %d tot %d #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Is het %s%% or %s %s?\n" -"JA voor %s%%, \n" -"NEE voor %s %s." +"Is het %s%% or %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -9602,8 +9596,6 @@ msgstr "Wilt u doorgaan?" msgid "Language selection" msgstr "Taal selectie" -msgid "Switching application language while some presets are modified." -msgstr "De taal van de toepassing aanpaasen terwijl sommige voorinstellingen zijn aangepast." msgid "Asia-Pacific" msgstr "Azië-Pacific" diff --git a/localization/i18n/pl/OrcaSlicer_pl.po b/localization/i18n/pl/OrcaSlicer_pl.po index 3d7fb083ef..39b0c71d89 100644 --- a/localization/i18n/pl/OrcaSlicer_pl.po +++ b/localization/i18n/pl/OrcaSlicer_pl.po @@ -2512,8 +2512,6 @@ msgstr "Dostępna jest aktualizacja. Otwórz okno pakietu profili, aby ją zains msgid "%s has been removed." msgstr "%s został usunięty." -msgid "Switching application language" -msgstr "Zmiana języka aplikacji" msgid "Select the language" msgstr "Wybierz język" @@ -5454,13 +5452,9 @@ msgstr "Wartość %s jest spoza zakresu. Poprawny zakres wynosi od %d do %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Czy to %s%% czy %s %s?\n" -"TAK dla %s%%,\n" -"NIE dla %s %s." +"Czy to %s%% czy %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -9016,8 +9010,6 @@ msgstr "Czy kontynuować?" msgid "Language selection" msgstr "Wybór języka" -msgid "Switching application language while some presets are modified." -msgstr "Zmiana języka aplikacji przy jednoczesnym istniejących zmodyfikowanych ustawieniach." msgid "Asia-Pacific" msgstr "Azja i Pacyfik" @@ -25440,15 +25432,6 @@ msgstr "" #~ msgid "Low-temperature filament (PLA/PETG/TPU) is loaded in the extruder. In order to avoid extruder clogging, it is not allowed to set the chamber temperature above 45℃." #~ msgstr "W ekstruzorze jest załadowany filament o niskiej temperaturze (PLA/PETG/TPU). Aby uniknąć zatkania ekstruzora, nie wolno ustawiać temperatury komory powyżej 45℃." -#~ msgid "" -#~ "Is it %s%% or %s %s?\n" -#~ "YES for %s%%,\n" -#~ "NO for %s %s." -#~ msgstr "" -#~ "Czy to %s%% czy %s %s?\n" -#~ "TAK dla %s%%,\n" -#~ "NIE dla %s %s." - #~ msgid "Allow multiple materials on the same plate" #~ msgstr "Pozwól na kilka filamentów na tej samej płycie" diff --git a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po index 066b8b310b..ca92c4c09e 100644 --- a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po +++ b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po @@ -2128,7 +2128,7 @@ msgid "" "OrcaSlicer has attempted to recreate the configuration file.\n" "Please note, application settings will be lost, but printer profiles will not be affected." msgstr "" -"O arquivo de configuração do OrcaSlicer pode estar corrompido e não pode ser analisado.\n" +"O arquivo de configuração do OrcaSlicer pode estar corrompido e não pode ser processado.\n" "O OrcaSlicer tentou recriar o arquivo de configuração.\n" "Por favor, note que as configurações do aplicativo serão perdidas, mas os perfis de impressora não serão afetados." @@ -2356,8 +2356,6 @@ msgstr "Há uma atualização disponível. Abra a caixa de diálogo do pacote de msgid "%s has been removed." msgstr "%s foi removido." -msgid "Switching application language" -msgstr "Alternando o idioma do aplicativo" msgid "Select the language" msgstr "Selecione o idioma" @@ -5171,13 +5169,9 @@ msgstr "Valor %s está fora do intervalo. O intervalo válido é de %d para %d." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"É %s%% ou %s %s?\n" -"SIM para %s%%, \n" -"NÃO para %s %s." +"É %s%% ou %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -6677,7 +6671,7 @@ msgid "Failed to fetch model information from printer." msgstr "Falha ao obter informação do modelo da impressora." msgid "Failed to parse model information." -msgstr "Falha ao analisar a informação do modelo." +msgstr "Falha ao processar a informação do modelo." msgid "The .gcode.3mf file contains no G-code data. Please slice it with Orca Slicer and export a new .gcode.3mf file." msgstr "O arquivo .gcode.3mf não contém dados de G-code. Por favor, fatie com Orca Slicer e exporte um novo arquivo .gcode.3mf." @@ -8572,8 +8566,6 @@ msgstr "Você deseja continuar?" msgid "Language selection" msgstr "Seleção de idioma" -msgid "Switching application language while some presets are modified." -msgstr "Alternando idioma do aplicativo enquanto algumas predefinições são modificadas." msgid "Asia-Pacific" msgstr "Ásia-Pacífico" @@ -8641,7 +8633,7 @@ msgstr "" "\n" "Deseja baixar e instalar esta versão agora?\n" "\n" -"Observação: o aplicativo pode precisar ser reiniciado após a instalação." +"Nota: o aplicativo pode precisar ser reiniciado após a instalação." msgid "Download Network Plug-in" msgstr "Baixar Plug-in de Rede" @@ -9139,7 +9131,7 @@ msgid "" "Note: When Stealth Mode is enabled, your user profiles will not be backed up to Orca Cloud." msgstr "" "Isso desativa todos os recursos da nuvem, incluindo a sincronização de perfis do Orca Cloud. Usuários que preferem trabalhar totalmente offline podem ativar esta opção.\n" -"Observação: quando o Modo Furtivo está ativado, seus perfis de usuário não serão copiados para o Orca Cloud." +"Nota: quando o Modo Furtivo está ativado, seus perfis de usuário não serão copiados para o Orca Cloud." msgid "Hide login side panel" msgstr "Ocultar painel lateral de autenticação" @@ -9779,7 +9771,6 @@ msgstr "A impressora falhou ao gerar a tabela de mapeamento automático do bico msgid "The current nozzle mapping may produce an extra %0.2f g of waste." msgstr "O mapeamento de bicos atual pode gerar um desperdício adicional de %0.2f g." -# AI Translated #, c-format, boost-format msgid "Recommended filament arrangement saves %s->" msgstr "A disposição de filamento recomendada economiza %s->" @@ -9838,7 +9829,6 @@ msgstr "Este processo determina os valores de fluxo dinâmico para melhorar a qu msgid "Internal" msgstr "Interno" -# AI Translated #, c-format, boost-format msgid "%s space less than 20MB. Timelapse may not save properly. You can turn it off or" msgstr "%s com espaço inferior a 20MB. O timelapse pode não ser salvo corretamente. Você pode desativá-lo ou" @@ -9846,15 +9836,12 @@ msgstr "%s com espaço inferior a 20MB. O timelapse pode não ser salvo corretam msgid "Clean up files" msgstr "Limpar arquivos" -# AI Translated msgid "Low internal storage. This timelapse will overwrite the oldest video files." msgstr "Armazenamento interno baixo. Este timelapse substituirá os arquivos de vídeo mais antigos." -# AI Translated msgid "Low external storage. This timelapse will overwrite the oldest video files." msgstr "Armazenamento externo baixo. Este timelapse substituirá os arquivos de vídeo mais antigos." -# AI Translated msgid "Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card." msgstr "Armazenamento externo insuficiente para fotografia time-lapse. Conecte ao computador para excluir arquivos ou use um cartão de memória maior." @@ -9890,7 +9877,6 @@ msgstr "Atualizando informações dos hotends (%d/%d)." msgid "There are not enough available hotends currently." msgstr "Não há hotends disponíveis em quantidade suficiente no momento." -# AI Translated msgid "Please complete the hotend rack setup and try again." msgstr "Por favor, conclua a configuração do rack de hotend e tente novamente." @@ -9907,11 +9893,9 @@ msgstr "As informações reportadas sobre o hotend podem não ser confiáveis." msgid "The printer has no nozzle matching the slicing file (%s)." msgstr "A impressora não possui um bico compatível com o arquivo de fatiamento (%s)." -# AI Translated msgid "Please install a matching nozzle in the hotend rack, or set the corresponding printer preset while slicing." msgstr "Por favor, instale um bico compatível no rack de hotend, ou defina a predefinição de impressora correspondente ao fatiar." -# AI Translated msgid "The toolhead and hotend rack are full. Please remove at least one hotend before printing." msgstr "A cabeça da ferramenta e o rack de hotend estão cheios. Por favor, remova pelo menos um hotend antes de imprimir." @@ -9937,24 +9921,19 @@ msgstr "ambas extrusoras" msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). Please verify the nozzle or material settings and try again." msgstr "A dureza do material atual (%s) excede a dureza de %s(%s). Verifique as configurações do bico ou do material e tente novamente." -# AI Translated msgid "Your current firmware version cannot start this print job. Please update to the latest version and try again." msgstr "Sua versão atual do firmware não pode iniciar este trabalho de impressão. Atualize para a versão mais recente e tente novamente." -# AI Translated #, c-format, boost-format msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). It may cause nozzle wear, leading to material leakage and unstable flow. Please exercise caution when using it." msgstr "A dureza do material atual (%s) excede a dureza de %s(%s). Isso pode causar desgaste do bico, levando a vazamento de material e fluxo instável. Tenha cuidado ao usá-lo." -# AI Translated msgid "Some filaments may switch between extruders during printing. Manual K-value calibration cannot be applied throughout the entire print, which may affect print quality. Enabling Flow Dynamics Calibration is recommended." msgstr "Alguns filamentos podem alternar entre extrusoras durante a impressão. A calibração manual do valor K não pode ser aplicada durante toda a impressão, o que pode afetar a qualidade da impressão. Recomenda-se ativar a Calibração de Dinâmica de Fluxo." -# AI Translated msgid "There is stringing-prone filament in this file. For best print quality, we recommend switching nozzle clumping detection to Auto mode." -msgstr "Há filamento propenso a fiapos neste arquivo. Para a melhor qualidade de impressão, recomendamos alternar a detecção de aglomeração no bico para o modo Automático." +msgstr "Há filamento propenso a criar fios neste arquivo. Para a melhor qualidade de impressão, recomendamos alternar a detecção de aglomeração no bico para o modo Automático." -# AI Translated msgid "If 'Dynamic Flow Calibration' is set to Auto/On, the system will use the manual calibration value or the default value and skip the flow calibration process. You can perform a manual flow calibration for TPU filament on the 'Calibration' page." msgstr "Se a 'Calibração de Fluxo Dinâmico' estiver definida como Automático/Ativado, o sistema usará o valor de calibração manual ou o valor padrão e ignorará o processo de calibração de fluxo. Você pode realizar uma calibração de fluxo manual para filamento TPU na página 'Calibração'." @@ -10061,7 +10040,6 @@ msgstr "Desative a calibração de fluxo dinâmico para habilitar o valor de flu msgid "This printer does not support printing all plates." msgstr "Esta impressora não suporta a imprimir todas as placas." -# AI Translated #, c-format, boost-format msgid "The current firmware supports a maximum of %s materials. You can either reduce the number of materials to %s or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support." msgstr "O firmware atual suporta no máximo %s materiais. Você pode reduzir o número de materiais para %s ou menos na Página de Preparação, ou tentar atualizar o firmware. Se ainda estiver restrito após a atualização, aguarde o suporte de firmware subsequente." @@ -10069,11 +10047,9 @@ msgstr "O firmware atual suporta no máximo %s materiais. Você pode reduzir o n msgid "The type of external filament is unknown or does not match with the filament type in the slicing file. Please make sure you have installed the correct filament in the external spool." msgstr "O tipo de filamento externo é desconhecido ou não corresponde ao tipo de filamento no arquivo de fatiamento. Certifique-se de ter instalado o filamento correto no carretel externo." -# AI Translated msgid "TPU 90A/TPU 85A are too soft. It is recommended to perform manual flow calibration on the 'Calibration' page. If 'Dynamic Flow Calibration' is set to auto/on, the system will use the previous calibration value and skip the flow calibration process." msgstr "TPU 90A/TPU 85A são muito macios. Recomenda-se realizar a calibração de fluxo manual na página 'Calibração'. Se a 'Calibração de Fluxo Dinâmico' estiver definida como automático/ativado, o sistema usará o valor de calibração anterior e ignorará o processo de calibração de fluxo." -# AI Translated msgid "The filament in the AMS may be insufficient for this print. Please refill or replace it." msgstr "O filamento no AMS pode ser insuficiente para esta impressão. Por favor, reabasteça ou substitua-o." @@ -10150,7 +10126,7 @@ msgid "Failed to post ticket to server" msgstr "Falha ao enviar o ticket para o servidor" msgid "Failed to parse login report reason" -msgstr "Falha ao analisar o motivo do relatório de login" +msgstr "Falha ao processar o motivo do relatório de login" msgid "Receive login report timeout" msgstr "Limite de tempo excedido ao receber o relatório de login" @@ -10246,11 +10222,9 @@ msgstr "Excluir esta predefinição" msgid "Search in preset" msgstr "Pesquisar nas predefinições" -# AI Translated msgid "Synchronization of different extruder drives or nozzle volume types is not supported." msgstr "A sincronização de diferentes acionamentos de extrusora ou tipos de volume do bico não é suportada." -# AI Translated msgid "Synchronize the modification of parameters to the corresponding parameters of another extruder." msgstr "Sincroniza a modificação de parâmetros com os parâmetros correspondentes de outra extrusora." @@ -10320,7 +10294,7 @@ msgid "Are you sure you want to enable this option?" msgstr "Tem certeza de que deseja habilitar esta opção?" msgid "Infill patterns are typically designed to handle rotation automatically to ensure proper printing and achieve their intended effects (e.g., Gyroid, Cubic). Rotating the current sparse infill pattern may lead to insufficient support. Please proceed with caution and thoroughly check for any potential printing issues. Are you sure you want to enable this option?" -msgstr "Padrões de preenchimento são projetados para lidar com a rotação automaticamente para garantir a impressão adequada e atingir os efeitos pretendidos (Ex. Giroide, Cúbico). Girar o padrão de preenchimento esparso atual pode causar suporte insuficiente. Prossiga com cautela e verifique cuidadosamente se há possíveis problemas de impressão. Tem certeza de que deseja habilitar esta opção?" +msgstr "Padrões de preenchimento são projetados para lidar com a rotação automaticamente para garantir a impressão adequada e atingir os efeitos pretendidos (ex.: Giroide, Cúbico). Girar o padrão de preenchimento esparso atual pode causar suporte insuficiente. Prossiga com cautela e verifique cuidadosamente se há possíveis problemas de impressão. Tem certeza de que deseja habilitar esta opção?" msgid "Experimental feature: Retracting and cutting off the filament at a greater distance during filament changes to minimize flush. Although it can notably reduce flush, it may also elevate the risk of nozzle clogs or other printing complications." msgstr "Funcionalidade experimental: Retrair e cortar o filamento a uma distância maior durante mudanças de filamento para minimizar a purga. Embora possa reduzir notavelmente a purga, ele também pode elevar o risco de bolhas no bico ou outras complicações de impressão." @@ -10483,7 +10457,6 @@ msgstr "G-code de mudança de tipo de extrusão" msgid "Post-processing Scripts" msgstr "Scripts de pós-processamento" -# AI Translated msgid "Slicing Pipeline Plugin" msgstr "Plugin de Pipeline de Fatiamento" @@ -10537,9 +10510,8 @@ msgstr "Temperatura da câmara de impressão" msgid "Chamber temperature" msgstr "Temperatura da câmara" -# AI Translated msgid "Target chamber temperature, and the minimal chamber temperature at which printing should start" -msgstr "Temperatura da câmara alvo, e a temperatura mínima da câmara na qual a impressão deve começar" +msgstr "Temperatura alvo da câmara, e a temperatura mínima da câmara na qual a impressão deve começar" msgid "Target" msgstr "Alvo" @@ -10777,7 +10749,6 @@ msgstr "Limites de altura da camada" msgid "Z-Hop" msgstr "Z-Hop" -# AI Translated msgid "" "The Retract before wipe option could be only 100% when using the Firmware Retraction mode.\n" "\n" @@ -11085,7 +11056,6 @@ msgstr "Se ativo, este diálogo pode ser usado para transferir valores seleciona msgid "One of the presets does not exist" msgstr "Uma das predefinições não existe" -# AI Translated msgid "Compared presets has different printer technology" msgstr "As predefinições comparadas têm tecnologia de impressora diferente" @@ -11420,9 +11390,8 @@ msgstr "Entrar" msgid "Login failed. Please try again." msgstr "Falha no login. Tente novamente." -# AI Translated msgid "parse json failed" -msgstr "falha ao analisar o json" +msgstr "falha ao processar o json" msgid "[Action Required] " msgstr "[Ação Necessária] " @@ -11613,7 +11582,6 @@ msgctxt "Keyboard Shortcut" msgid "Space" msgstr "Espaço" -# AI Translated msgid "Open actions speed dial" msgstr "Abrir menu rápido de ações" @@ -11695,11 +11663,9 @@ msgstr "informações de atualização da versão %s:" msgid "Network plug-in update" msgstr "Atualização do plug-in de rede" -# AI Translated msgid "Click OK to update the Network plug-in now. If a file is in use, the update will be applied the next time Orca Slicer launches." msgstr "Clique em OK para atualizar o plug-in de Rede agora. Se um arquivo estiver em uso, a atualização será aplicada na próxima vez que o Orca Slicer for iniciado." -# AI Translated msgid "A new Network plug-in is available. Do you want to install it?" msgstr "Um novo plug-in de Rede está disponível. Deseja instalá-lo?" @@ -11759,7 +11725,6 @@ msgstr "Nome da impressora" msgid "Where to find your printer's IP and Access Code?" msgstr "Onde encontrar o IP e o Código de Acesso da sua impressora?" -# AI Translated msgid "How to trouble shooting" msgstr "Como solucionar problemas" @@ -11907,11 +11872,9 @@ msgstr "Objeto: %1%" msgid "Parts of the object at these heights may be too thin or the object may have a faulty mesh." msgstr "Partes do objeto nessas alturas podem ser muito finas, ou o objeto pode ter uma malha com falhas." -# AI Translated msgid "Process change extrusion role G-code" msgstr "G-code de mudança de tipo de extrusão do processo" -# AI Translated msgid "Filament change extrusion role G-code" msgstr "G-code de mudança de tipo de extrusão do filamento" @@ -12086,7 +12049,6 @@ msgstr " está muito perto de uma área de exclusão, e colisões vão ocorrer.\ msgid " is too close to clumping detection area, and collisions will be caused.\n" msgstr " está muito perto da área de detecção de aglomeração, e ocorrerão colisões.\n" -# AI Translated msgid " is partially outside the printable area, and it cannot be printed.\n" msgstr " está parcialmente fora da área imprimível, e não pode ser impresso.\n" @@ -12102,7 +12064,6 @@ msgstr "Se ainda assim desejar imprimir, você pode ativar a opção em Preferê msgid "No extrusions under current settings." msgstr "Nenhuma extrusão com as configurações atuais." -# AI Translated msgid "A gradient mixed filament is used, but 'Mixed color sublayer' is disabled. The gradient will not be printed." msgstr "Um filamento misto com gradiente está em uso, mas 'Subcamada de cor mista' está desativado. O gradiente não será impresso." @@ -12142,7 +12103,6 @@ msgstr "Você pode querer reduzir o tamanho do seu modelo ou alterar as configur msgid "Variable layer height is not supported with Organic supports." msgstr "A altura de camada variável não é suportada com suportes Orgânicos." -# AI Translated msgid "The wipe tower filament cannot be a mixed filament." msgstr "O filamento da torre de purga não pode ser um filamento misto." @@ -12779,7 +12739,6 @@ msgstr "" msgid "Internal bridge flow ratio" msgstr "Taxa de fluxo em ponte interna" -# AI Translated msgid "" "This value governs the thickness of the internal bridge layer. This is the first layer over sparse infill so increasing it may increase strength and upper layer quality.\n" "Values above 1.0: Increase the amount of material while maintaining line spacing. This can improve line contact and strength.\n" @@ -13169,11 +13128,9 @@ msgstr "" "A geometria será decimada antes de detectar ângulos agudos. Este parâmetro indica o comprimento mínimo da divergência para a decimação.\n" "0 para desativar." -# AI Translated msgid "Brim ears outer only" -msgstr "Orelhas da borda apenas externas" +msgstr "Apenas orelhas da borda externas" -# AI Translated msgid "Generate mouse ears only on the outer contour of the model, excluding holes and enclosed sections." msgstr "Gera orelhas de rato apenas no contorno externo do modelo, excluindo furos e seções fechadas." @@ -13201,7 +13158,6 @@ msgstr "Por objeto" msgid "Intra-layer order" msgstr "Ordem intra-camada" -# AI Translated msgid "" "Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n" "\n" @@ -13259,7 +13215,7 @@ msgid "mm/s² or %" msgstr "mm/s² ou %" msgid "Acceleration of bridges. If the value is expressed as a percentage (e.g. 50%), it will be calculated based on the outer wall acceleration." -msgstr "Aceleração das pontes. Se o valor for expresso como uma porcentagem (por exemplo, 50%), será calculado com base na aceleração da parede externa." +msgstr "Aceleração das pontes. Se o valor for expresso como uma porcentagem (ex.: 50%), será calculado com base na aceleração da parede externa." msgid "Default filament profile" msgstr "Perfil de filamento padrão" @@ -13839,7 +13795,6 @@ msgstr "Tempo da camada" msgid "The part cooling fan will be enabled for layers where the estimated time is shorter than this value. Fan speed is interpolated between the minimum and maximum fan speeds according to layer printing time." msgstr "A ventoinha de resfriamento de peças será ativado para camadas cujo tempo estimado seja mais curto que esse valor. A velocidade da ventoinha é interpolada entre as velocidades mínima e máxima da ventoinha de acordo com o tempo de impressão da camada." -# AI Translated msgctxt "second" msgid "s" msgstr "s" @@ -14146,61 +14101,47 @@ msgstr "Material de suporte" msgid "Support material is commonly used to print supports and support interfaces." msgstr "O material de suporte é comumente usado para imprimir suportes e interfaces de suporte." -# AI Translated msgid "Is mixed filament" msgstr "É filamento misto" -# AI Translated msgid "Whether this filament slot is a mixed filament composed of multiple physical filaments" msgstr "Define se este slot de filamento é um filamento misto composto por vários filamentos físicos" -# AI Translated msgid "Mixed filament components" msgstr "Componentes do filamento misto" -# AI Translated msgid "Comma-separated 1-based indices of component filaments, e.g. \"1,3\"" msgstr "Índices (começando em 1) dos filamentos componentes, separados por vírgulas; ex.: \"1,3\"" -# AI Translated msgid "Mixed filament sublayer ratios" msgstr "Proporções de subcamada do filamento misto" -# AI Translated msgid "Comma-separated ratio values summing to 1.0, e.g. \"0.7,0.3\"" msgstr "Valores de proporção separados por vírgulas cuja soma seja 1.0; ex.: \"0.7,0.3\"" -# AI Translated msgid "Mixed filament gradient" msgstr "Gradiente do filamento misto" -# AI Translated msgid "Enable Z-direction gradient mode for mixed filament sub-layers. When enabled, the sub-layer ratios vary linearly across layers." -msgstr "Ativa o modo de gradiente na direção Z para as subcamadas do filamento misto. Quando ativado, as proporções das subcamadas variam linearmente ao longo das camadas." +msgstr "Ativa o modo de gradiente na direção Z para as subcamadas do filamento misto. Quando ativo, as proporções das subcamadas variam linearmente ao longo das camadas." -# AI Translated msgid "Mixed filament gradient range" msgstr "Faixa do gradiente do filamento misto" -# AI Translated msgid "Start and end ratios for the first component in gradient mode. Comma-separated pair, e.g. \"0.10,0.90\" means 10% to 90%." msgstr "Proporções inicial e final do primeiro componente no modo de gradiente. Par separado por vírgula; ex.: \"0.10,0.90\" significa de 10% a 90%." -# AI Translated msgid "Mixed filament gradient curve" msgstr "Curva do gradiente do filamento misto" -# AI Translated msgid "Optional Photoshop-style custom curve mapping Z progress to the first component ratio. Encoded as pipe-separated control points, either \"x,y\" (legacy) or \"x,y,m_in,m_out\" when a tangent override is needed (empty token or \"nan\" means use PCHIP default). x in [0,1]; y is clamped to the configured ratio range, e.g. \"0,0.15|0.5,0.50|1,0.85\". When empty, the linear gradient_range is used instead." -msgstr "Curva personalizada opcional, no estilo do Photoshop, que mapeia o progresso em Z para a proporção do primeiro componente. Codificada como pontos de controle separados por barras verticais, no formato \"x,y\" (legado) ou \"x,y,m_in,m_out\" quando é necessário substituir a tangente (um valor vazio ou \"nan\" usa o padrão PCHIP). x está em [0,1]; y é limitado à faixa de proporção configurada; ex.: \"0,0.15|0.5,0.50|1,0.85\". Quando vazio, o gradient_range linear é usado." +msgstr "Curva personalizada opcional no estilo do Photoshop, mapeando o progresso em Z para a proporção do primeiro componente. Codificada como pontos de controle separados por barras verticais, no formato \"x,y\" (legado) ou \"x,y,m_in,m_out\" quando é necessário substituir a tangente (um valor vazio ou \"nan\" usa o padrão PCHIP). X em [0,1]; Y é limitado à faixa de proporção configurada; ex.: \"0,0.15|0.5,0.50|1,0.85\". Quando vazio, o gradient_range linear é usado no lugar." -# AI Translated msgid "Mixed filament per-part gradient" msgstr "Gradiente por peça do filamento misto" -# AI Translated msgid "When gradient mode is enabled, apply the gradient to each part of an assembly independently rather than treating the whole assembly as one Z range." -msgstr "Quando o modo de gradiente está ativado, aplica o gradiente a cada peça de uma montagem de forma independente, em vez de tratar toda a montagem como uma única faixa Z." +msgstr "Quando o modo de gradiente está ativado, aplica o gradiente a cada peça de uma montagem de forma independente em vez de tratar toda a montagem como uma única faixa Z." msgid "Filament printable" msgstr "Filamento imprimível" @@ -14291,7 +14232,7 @@ msgid "Insert solid layers" msgstr "Inserir camadas sólidas" msgid "Insert solid infill at specific layers. Use N to insert every Nth layer, N#K to insert K consecutive solid layers every N layers (K is optional, e.g. '5#' equals '5#1'), or a comma-separated list (e.g. 1,7,9) to insert at explicit layers. Layers are 1-based." -msgstr "Insere preenchimento sólido em camadas específicas. Use N para inserir a cada enésima camada, N#K para inserir K camadas sólidas consecutivas a cada enésima camada (K é opcional, ou seja, '5#' é igual a '5#1'), ou uma lista separada por vírgulas (Ex. 1,7,9) para inserir em camadas esplícitas. Camadas são baseadas em 1." +msgstr "Insere preenchimento sólido em camadas específicas. Use N para inserir a cada enésima camada, N#K para inserir K camadas sólidas consecutivas a cada enésima camada (K é opcional, ex.: '5#' é igual a '5#1'), ou uma lista separada por vírgulas (Ex. 1,7,9) para inserir em camadas esplícitas. Camadas são baseadas em 1." msgid "Fill Multiline" msgstr "Multilinhas de Preenchimento" @@ -14369,11 +14310,9 @@ msgstr "TPMS-FK" msgid "Gyroid" msgstr "Giroide" -# AI Translated msgid "Sparse infill smooth factor" msgstr "Fator de suavização do preenchimento esparso" -# AI Translated msgid "Controls how strongly sparse infill corners are rounded. 0% keeps the original sharp path, while 100% produces the largest possible curves between adjacent infill lines." msgstr "Controla o quanto os cantos do preenchimento esparso são arredondados. 0% mantém o trajeto original com cantos vivos, enquanto 100% produz as maiores curvas possíveis entre linhas de preenchimento adjacentes." @@ -14387,10 +14326,10 @@ msgid "Acceleration of inner walls." msgstr "Aceleração das paredes internas." msgid "Acceleration of sparse infill. If the value is expressed as a percentage (e.g. 100%), it will be calculated based on the default acceleration." -msgstr "Aceleração do preenchimento esparso. Se o valor for expresso como uma porcentagem (por exemplo, 100%), será calculado com base na aceleração padrão." +msgstr "Aceleração do preenchimento esparso. Se o valor for expresso como uma porcentagem (ex.: 100%), será calculado com base na aceleração padrão." msgid "Acceleration of internal solid infill. If the value is expressed as a percentage (e.g. 100%), it will be calculated based on the default acceleration." -msgstr "Aceleração do preenchimento sólido interno. Se o valor for expresso como uma porcentagem (por exemplo, 100%), será calculado com base na aceleração padrão." +msgstr "Aceleração do preenchimento sólido interno. Se o valor for expresso como uma porcentagem (ex.: 100%), será calculado com base na aceleração padrão." msgid "This is the printing acceleration for the first layer. Using limited acceleration can improve build plate adhesion." msgstr "Esta é a aceleração para a primeira camada. Usar aceleração limitada melhorar a adesão à placa de impressão." @@ -14494,7 +14433,7 @@ msgid "" "Only available when \"No cooling for the first\" is 0.\n" "Set to -1 to disable it." msgstr "" -"Define uma velocidade exata da ventoinha para a primeira camada, substituindo todas as outras configurações de resfriamento. Útil para proteger peças impressas em 3D da cabeça da ferramenta (por exemplo, dutos ABS/ASA no estilo Voron) de uma mesa aquecida. Uma pequena quantidade de fluxo de ar resfria os dutos, sem usar o resfriamento total que pode, em certas condições, prejudicar a aderência da primeira camada.\n" +"Define uma velocidade exata da ventoinha para a primeira camada, substituindo todas as outras configurações de resfriamento. Útil para proteger peças impressas em 3D da cabeça da ferramenta (ex.: dutos ABS/ASA no estilo Voron) de uma mesa aquecida. Uma pequena quantidade de fluxo de ar resfria os dutos, sem usar o resfriamento total que pode, em certas condições, prejudicar a aderência da primeira camada.\n" "A partir da segunda camada, o resfriamento normal é retomado.\n" "Se \"Velocidade total da ventoinha na camada\" também estiver definida, a ventoinha aumenta suavemente deste valor na primeira camada até o seu alvo na camada escolhida.\n" "Disponível apenas quando \"Sem resfriamento nas primeiras\" é 0.\n" @@ -14865,7 +14804,7 @@ msgid "" "Set to 0 to deactivate." msgstr "" "Algumas ventoinhas de resfriamento de componentes não conseguem iniciar a rotação quando comandadas abaixo de um determinado ciclo de trabalho PWM. Quando definido acima de 0, qualquer comando de ventoinha de resfriamento de componentes diferente de zero será elevado para pelo menos essa porcentagem, para que a ventoinha inicie de forma confiável. Um comando de ventoinha de 0 (ventoinha desligada) é sempre atendido exatamente. Essa limitação é aplicada após cada outro cálculo da ventoinha (rampa da primeira camada, interpolação do tempo da camada, substituições de saliência/ponte/interface de suporte/alisamento), para que o dimensionamento ainda opere dentro do intervalo [este valor, 100%].\n" -"Se o seu firmware já desativa a ventoinha abaixo de um limite (por exemplo, [fan] off_below: 0.10 do Klipper desliga a ventoinha sempre que o ciclo de trabalho comandado for inferior a 10%), esta opção e o limite do firmware devem idealmente ser definidos com o mesmo valor. A correspondência entre eles (por exemplo, off_below: 0.10 no Klipper e 10% aqui) garante que o fatiador nunca emita um valor diferente de zero que o firmware emitiria a velocidade cai silenciosamente e a ventoinha nunca recebe um valor abaixo daquele que você sabe que ela pode realmente atingir.\n" +"Se o seu firmware já desativa a ventoinha abaixo de um limite (por exemplo, [fan] off_below: 0.10 do Klipper desliga a ventoinha sempre que o ciclo de trabalho comandado for inferior a 10%), esta opção e o limite do firmware devem idealmente ser definidos com o mesmo valor. A correspondência entre eles (ex.: off_below: 0.10 no Klipper e 10% aqui) garante que o fatiador nunca emita um valor diferente de zero que o firmware emitiria a velocidade cai silenciosamente e a ventoinha nunca recebe um valor abaixo daquele que você sabe que ela pode realmente atingir.\n" "Defina como 0 para desativar." msgid "Time cost" @@ -14912,13 +14851,11 @@ msgstr "Com que tipo de G-code a impressora é compatível." msgid "Klipper" msgstr "Klipper" -# AI Translated msgid "Skip G-code config block" msgstr "Omitir o bloco de configuração do G-code" -# AI Translated msgid "Do not write the CONFIG_BLOCK (slicer configuration key/value pairs) into the G-code file. This can help with printers whose firmware crashes when parsing these comment lines (e.g. Anycubic go-klipper). Note: the G-code file will no longer contain slicer settings, so importing it back into OrcaSlicer will not restore the configuration." -msgstr "Não grava o CONFIG_BLOCK (os pares chave/valor da configuração do fatiador) no arquivo G-code. Isso pode ajudar com impressoras cujo firmware trava ao interpretar essas linhas de comentário (por exemplo, Anycubic go-klipper). Observação: o arquivo G-code não conterá mais as configurações do fatiador, então importá-lo de volta no OrcaSlicer não restaurará a configuração." +msgstr "Não grava o CONFIG_BLOCK (os pares chave/valor da configuração do fatiador) no arquivo G-code. Isso pode ajudar com impressoras cujo firmware trava ao interpretar essas linhas de comentário (ex.: Anycubic go-klipper). Nota: o arquivo G-code não conterá mais as configurações do fatiador, então importá-lo de volta no OrcaSlicer não restaurará a configuração." msgid "Pellet Modded Printer" msgstr "Impressora Modificada para Pellets" @@ -14966,13 +14903,13 @@ msgid "Sparse infill rotation template" msgstr "Gabarito de rotação de preenchimento esparso" msgid "Rotate the sparse infill direction per layer using a template of angles. Enter comma-separated degrees (e.g., '0,30,60,90'). Angles are applied in order by layer and repeat when the list ends. Advanced syntax is supported: '+5' rotates +5° every layer; '+5#5' rotates +5° every 5 layers. See the Wiki for details. When a template is set, the standard infill direction setting is ignored. Note: some infill patterns (e.g., Gyroid) control rotation themselves; use with care." -msgstr "Gira a direção do preenchimento esparso por camada usando um gabarito de ângulos. Insira graus separados por vírgula (por exemplo, '0, 30, 60, 90'). Os ângulos são aplicados em ordem por camada e repetidos quando a lista termina. Sintaxe avançada suportada: '+5' gira +5° a cada camada; '+5#5' gira +5° a cada 5 camadas. Consulte a Wiki para obter detalhes. Quando um modelo é definido, a configuração padrão de direção do preenchimento é ignorada. Observação: alguns padrões de preenchimento (por exemplo, Giróide) tem seu próprio controle de rotação, use com cuidado." +msgstr "Gira a direção do preenchimento esparso por camada usando um gabarito de ângulos. Insira graus separados por vírgula (ex.: '0,30,60,90'). Os ângulos são aplicados em ordem por camada e repetidos quando a lista termina. Sintaxe avançada suportada: '+5' gira +5° a cada camada; '+5#5' gira +5° a cada 5 camadas. Consulte a Wiki para obter detalhes. Quando um modelo é definido, a configuração padrão de direção do preenchimento é ignorada. Nota: alguns padrões de preenchimento (ex.: Giróide) tem seu próprio controle de rotação, use com cuidado." msgid "Solid infill rotation template" msgstr "Gabarito de rotação de preenchimento sólido" msgid "This parameter adds a rotation of solid infill direction to each layer according to the specified template. The template is a comma-separated list of angles in degrees, e.g. '0,90'. The first angle is applied to the first layer, the second angle to the second layer, and so on. If there are more layers than angles, the angles will be repeated. Note that not all solid infill patterns support rotation." -msgstr "Este parâmetro adiciona uma rotação da direção do preenchimento sólido a cada camada, de acordo com o gabarito especificado. O gabarito é uma lista de ângulos em graus separados por vírgulas, por exemplo, '0,90'. O primeiro ângulo é aplicado à primeira camada, o segundo ângulo à segunda camada e assim por diante. Se houver mais camadas do que ângulos, os ângulos serão repetidos. Observe que nem todos os padrões de preenchimento sólido suportam rotação." +msgstr "Este parâmetro adiciona uma rotação da direção do preenchimento sólido a cada camada, de acordo com o gabarito especificado. O gabarito é uma lista de ângulos em graus separados por vírgulas, como '0,90'. O primeiro ângulo é aplicado à primeira camada, o segundo ângulo à segunda camada e assim por diante. Se houver mais camadas do que ângulos, os ângulos serão repetidos. Observe que nem todos os padrões de preenchimento sólido suportam rotação." msgid "Skeleton infill density" msgstr "Densidade de preenchimento de esqueleto" @@ -15438,7 +15375,6 @@ msgstr "Força máxima do eixo Y" msgid "The allowed maximum output force of Y axis" msgstr "A força máxima de saída permitida do eixo Y" -# AI Translated msgctxt "Newton" msgid "N" msgstr "N" @@ -15449,7 +15385,6 @@ msgstr "Massa da mesa do eixo Y" msgid "The machine bed mass load of Y axis" msgstr "A carga de massa da mesa do equipamento no eixo Y" -# AI Translated msgctxt "gram" msgid "g" msgstr "g" @@ -15974,7 +15909,7 @@ msgid "Z-hop height" msgstr "Altura de Z-hop" msgid "Whenever there is a retraction, the nozzle is lifted a little to create clearance between the nozzle and the print. This prevents the nozzle from hitting the print when traveling more. Using spiral lines to lift Z can prevent stringing." -msgstr "Sempre que há uma retração, o bico é levantado um pouco para criar folga entre o bico e a impressão. Isso evita que o bico atinja a impressão ao se mover. Usar linhas em espiral para levantar Z pode evitar stringing." +msgstr "Sempre que há uma retração, o bico é levantado um pouco para criar folga entre o bico e a impressão. Isso evita que o bico atinja a impressão ao se mover. Usar linhas em espiral para levantar Z pode evitar criação de fios." msgid "Z-hop lower boundary" msgstr "Limite inferior do Z-hop" @@ -16055,7 +15990,7 @@ msgid "Has filament switcher" msgstr "Tem trocador de filamentos" msgid "Printer has a filament switcher hardware (e.g., AMS)." -msgstr "A impressora tem um sistema de troca de filamentos (Ex.: AMS)." +msgstr "A impressora tem um sistema de troca de filamentos (ex.: AMS)." msgid "Extra length on restart" msgstr "Comprimento extra na retração" @@ -16172,7 +16107,7 @@ msgid "Scarf joint speed" msgstr "Velocidade da costura em bisel" msgid "This option sets the printing speed for scarf joints. It is recommended to print scarf joints at a slow speed (less than 100 mm/s). It's also advisable to enable 'Extrusion rate smoothing' if the set speed varies significantly from the speed of the outer or inner walls. If the speed specified here is higher than the speed of the outer or inner walls, the printer will default to the slower of the two speeds. When specified as a percentage (e.g., 80%), the speed is calculated based on the respective outer or inner wall speed. The default value is set to 100%." -msgstr "Esta opção define a velocidade de impressão para as costuras em bisel. É recomendável imprimir as costuras em bisel em uma velocidade baixa (menor que 100 mm/s). Também é aconselhável habilitar 'Suavização da taxa de extrusão' se a velocidade definida variar significativamente da velocidade das paredes externas ou internas. Se a velocidade especificada aqui for maior que a velocidade das paredes externas ou internas, a impressora utilizará a mais lenta das duas velocidades. Quando especificado como uma porcentagem (por exemplo, 80%), a velocidade é calculada com base na velocidade do perímetro externo ou interna respectiva. O valor padrão é definido como 100%." +msgstr "Esta opção define a velocidade de impressão para as costuras em bisel. É recomendável imprimir as costuras em bisel em uma velocidade baixa (menor que 100 mm/s). Também é aconselhável habilitar 'Suavização da taxa de extrusão' se a velocidade definida variar significativamente da velocidade das paredes externas ou internas. Se a velocidade especificada aqui for maior que a velocidade das paredes externas ou internas, a impressora utilizará a mais lenta das duas velocidades. Quando especificado como uma porcentagem (ex.: 80%), a velocidade é calculada com base na velocidade do perímetro externo ou interna respectiva. O valor padrão é definido como 100%." msgid "Scarf joint flow ratio" msgstr "Taxa de fluxo da costura em bisel" @@ -16242,7 +16177,7 @@ msgid "Wipe speed" msgstr "Velocidade de limpeza" msgid "The wipe speed is determined by the speed setting specified in this configuration. If the value is expressed as a percentage (e.g. 80%), it will be calculated based on the travel speed setting above. The default value for this parameter is 80%." -msgstr "A velocidade de limpeza é determinada pela velocidade especificada nesta configuração. Se o valor for expresso como uma porcentagem (por exemplo, 80%), será calculado com base na configuração de velocidade de deslocamento acima. O valor padrão para este parâmetro é 80%." +msgstr "A velocidade de limpeza é determinada pela velocidade especificada nesta configuração. Se o valor for expresso como uma porcentagem (ex.: 80%), será calculado com base na configuração de velocidade de deslocamento acima. O valor padrão para este parâmetro é 80%." msgid "Skirt distance" msgstr "Distância da saia" @@ -16280,7 +16215,7 @@ msgstr "" "Um escudo de ar é útil para proteger uma impressão ABS ou ASA de deformações e desprendimento da mesa de impressão devido à corrente de ar. Geralmente, ele é necessário apenas com impressoras de estrutura aberta, ou seja, sem um gabinete.\n" "\n" "Habilitado = a saia é tão alta quanto o objeto impresso mais alto. Caso contrário, 'Altura da saia' é usada.\n" -"Observação: com o escudo de ar ativo, a saia será impressa na distância da saia do objeto. Portanto, se as bordas estiverem ativas, ela pode se cruzar com elas. Para evitar isso, aumente o valor da distância da saia.\n" +"Nota: com o escudo de ar ativo, a saia será impressa na distância da saia do objeto. Portanto, se as bordas estiverem ativas, ela pode se cruzar com elas. Para evitar isso, aumente o valor da distância da saia.\n" msgid "Enabled" msgstr "Ativado" @@ -16421,10 +16356,10 @@ msgid "Preheat steps" msgstr "Passos de pré-aquecimento" msgid "Insert multiple preheat commands (e.g. M104.1). Only useful for Prusa XL. For other printers, please set it to 1." -msgstr "Insire múltiplos comandos de pré-aquecimento (por exemplo, M104.1). Útil apenas para Prusa XL. Para outras impressoras, defina como 1." +msgstr "Insire múltiplos comandos de pré-aquecimento (ex.: M104.1). Útil apenas para Prusa XL. Para outras impressoras, defina como 1." msgid "G-code written at the very top of the output file, before any other content. Useful for adding metadata that printer firmware reads from the first lines of the file (e.g. estimated print time, filament usage). Supports placeholders like {print_time_sec} and {used_filament_length}." -msgstr "Código G escrito no início do arquivo de saída, antes de qualquer outro conteúdo. Útil para adicionar metadados que o firmware da impressora lê das primeiras linhas do arquivo (por exemplo, tempo estimado de impressão, consumo de filamento). Suporta marcadores como {print_time_sec} e {used_filament_length}." +msgstr "Código G escrito no início do arquivo de saída, antes de qualquer outro conteúdo. Útil para adicionar metadados que o firmware da impressora lê das primeiras linhas do arquivo (ex.: tempo estimado de impressão, consumo de filamento). Suporta marcadores como {print_time_sec} e {used_filament_length}." msgid "Start G-code" msgstr "G-code Inicial" @@ -16445,7 +16380,7 @@ msgid "Manual Filament Change" msgstr "Troca Manual de Filamento" msgid "Enable this option to omit the custom Change filament G-code only at the beginning of the print. The tool change command (e.g., T0) will be skipped throughout the entire print. This is useful for manual multi-material printing, where we use M600/PAUSE to trigger the manual filament change action." -msgstr "Ative esta opção para omitir o G-code de troca de filamento personalizado apenas no início da impressão. O comando de troca de ferramenta (por exemplo, T0) será ignorado durante toda a impressão. Isso é útil para impressão manual de vários materiais, onde usamos M600/PAUSE para acionar a ação de troca manual de filamento." +msgstr "Ative esta opção para omitir o G-code de troca de filamento personalizado apenas no início da impressão. O comando de troca de ferramenta (ex.: T0) será ignorado durante toda a impressão. Isso é útil para impressão manual de vários materiais, onde usamos M600/PAUSE para acionar a ação de troca manual de filamento." msgid "Wipe tower type" msgstr "Tipo de torre de purga" @@ -17001,11 +16936,9 @@ msgstr "" "\n" "Definir um valor na configuração de quantidade de retração antes da limpeza abaixo executará qualquer retração em excesso antes da limpeza, caso contrário, será realizada após." -# AI Translated msgid "Mixed color sublayer" msgstr "Subcamada de cor mista" -# AI Translated msgid "Enable mixed color sublayer splitting. When enabled, layers containing mixed color filaments will be split into sub-layers to achieve color mixing effects." msgstr "Ativa a divisão em subcamadas de cor mista. Quando ativado, as camadas que contêm filamentos de cor mista são divididas em subcamadas para obter efeitos de mistura de cores." @@ -18065,9 +17998,8 @@ msgstr "A geração da malha do arquivo do modelo falhou ou não há forma váli msgid "The supplied file couldn't be read because it's empty." msgstr "O arquivo fornecido não pôde ser lido porque está vazio." -# AI Translated msgid "The file format is incompatible and cannot be parsed." -msgstr "O formato do arquivo é incompatível e não pode ser lido." +msgstr "O formato do arquivo é incompatível e não pode ser processado." msgid "Unknown file format: input file must have .stl, .obj, or .amf(.xml) extension." msgstr "Formato de arquivo desconhecido: o arquivo de entrada deve ter extensão .stl, .obj, .amf(.xml)." @@ -18076,10 +18008,10 @@ msgid "Unknown file format: input file must have .3mf or .zip.amf extension." msgstr "Formato de arquivo desconhecido: o arquivo de entrada deve ter extensão .3mf ou .zip.amf." msgid "load_obj: failed to parse" -msgstr "load_obj: falha ao analisar" +msgstr "load_obj: falha ao processar" msgid "load mtl in obj: failed to parse" -msgstr "carregar mtl em obj: falha ao analisar" +msgstr "carregar mtl em obj: falha ao processsar" msgid "The file contains polygons with more than 4 vertices." msgstr "O arquivo contém polígonos com mais de 4 vértices." @@ -19193,7 +19125,7 @@ msgid "Serial" msgstr "Série" msgid "e.g. Basic, Matte, Silk, Marble" -msgstr "por exemplo, Básico, Fosco, Seda, Mármore" +msgstr "Ex.: Básico, Fosco, Seda, Mármore" msgid "Filament Preset" msgstr "Predefinição de Filamento" @@ -19874,7 +19806,7 @@ msgid "Error. Can't get API token for authorization" msgstr "Erro. ​​Não foi possível obter o token de API para autorização" msgid "Could not parse server response." -msgstr "Não foi possível decifrar a resposta do servidor." +msgstr "Não foi possível processar a resposta do servidor." msgid "Error saving session to file" msgstr "Erro salvando sessão para arquivo" @@ -19942,7 +19874,7 @@ msgstr "O host respondeu, mas não parece ser o Moonraker (falta o result.klippy #, c-format, boost-format msgid "Could not parse Moonraker server response: %s" -msgstr "Não foi possível analisar a resposta do servidor Moonraker: %s" +msgstr "Não foi possível processar a resposta do servidor Moonraker: %s" msgid "Connection to OctoPrint is working correctly." msgstr "A conexão com o OctoPrint funciona corretamente." @@ -20316,7 +20248,6 @@ msgstr "Removido" msgid "Enable smart filament assign: Assign one filament to multiple nozzles to maximize savings" msgstr "Ativar atribuição inteligente de filamento: Atribui um filamento a vários bicos para maximizar a economia" -# AI Translated msgid "File Saving" msgstr "Salvamento de Arquivo" @@ -20503,7 +20434,7 @@ msgid "Connection timed out. Please check if the printer and computer network ar msgstr "Limite de tempo de conexão esgotado. Verifique se a impressora e a rede do computador estão funcionando corretamente e confirme se estão na mesma rede." msgid "The Hostname/IP/URL could not be parsed, please check it and try again." -msgstr "Não foi possível decifrar o Hostname/IP/URL; verifique-o e tente novamente." +msgstr "Não foi possível processar o Hostname/IP/URL; verifique-o e tente novamente." msgid "File/data transfer interrupted. Please check the printer and network, then try it again." msgstr "Transferência de arquivo/dados interrompida. Verifique a impressora e a rede e tente novamente." @@ -21286,15 +21217,6 @@ msgstr "" #~ msgid "Your system is missing H.264 codecs for GStreamer, which are required to play video. (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Orca Slicer?)" #~ msgstr "Seu sistema não possui codecs H.264 para o GStreamer, que são necessários para reproduzir vídeos. (Tente instalar os pacotes gstreamer1.0-plugins-bad ou gstreamer1.0-libav e depois reinicie o OrcaSlicer?)" -#~ msgid "N" -#~ msgstr "N" - -#~ msgid "g" -#~ msgstr "g" - -#~ msgid "Fila Saving" -#~ msgstr "Econo Filamento" - #~ msgid "" #~ "Layer height is too small.\n" #~ "It will set to min_layer_height\n" @@ -21622,7 +21544,7 @@ msgstr "" #~ "Pontes externas de menor densidade podem ajudar a melhorar a confiabilidade, pois há mais espaço para o ar circular ao redor da ponte extrudada, melhorando sua velocidade de resfriamento. O mínimo é 10%.\n" #~ "\n" #~ "Densidades mais altas podem produzir superfícies de ponte mais lisas, pois as linhas sobrepostas fornecem suporte adicional durante a impressão. O máximo é 120%.\n" -#~ "Observação: Densidade de ponte muito alta pode causar deformação ou sobrextrusão." +#~ "Nota: Densidade de ponte muito alta pode causar deformação ou sobrextrusão." #~ msgid "" #~ "Controls the density (spacing) of internal bridge lines. 100% means solid bridge. Default is 100%.\n" @@ -21667,7 +21589,7 @@ msgstr "" #~ "\n" #~ "Geralmente, é recomendável ter esta opção ativada, a menos que o resfriamento da impressora seja potente o suficiente ou a velocidade de impressão lenta o suficiente para que a curvatura do perímetro não aconteça. Se estiver imprimindo com uma alta velocidade de perímetro externo, este parâmetro pode introduzir pequenos artefatos ao desacelerar devido à grande variação nas velocidades de impressão. Se você notar artefatos, certifique-se de que seu pressure advance esteja ajustado corretamente.\n" #~ "\n" -#~ "Observação: quando esta opção estiver habilitada, os perímetros de saliência são tratados como saliências, o que significa que a velocidade de saliência é aplicada mesmo se o perímetro de saliência for parte de uma ponte. Por exemplo, quando os perímetros estiverem 100% salientes, sem nenhuma parede apoiando-os por baixo, a velocidade de saliência de 100% será aplicada." +#~ "Nota: quando esta opção estiver habilitada, os perímetros de saliência são tratados como saliências, o que significa que a velocidade de saliência é aplicada mesmo se o perímetro de saliência for parte de uma ponte. Por exemplo, quando os perímetros estiverem 100% salientes, sem nenhuma parede apoiando-os por baixo, a velocidade de saliência de 100% será aplicada." #~ msgid "If enabled, bridges are more reliable, can bridge longer distances, but may look worse. If disabled, bridges look better but are reliable just for shorter bridged distances." #~ msgstr "Se ativado, as pontes são mais confiáveis, podem cobrir distâncias maiores, mas podem parecer piores. Se desativado, as pontes ficam melhores, mas são confiáveis apenas para distâncias de ponte mais curtas." @@ -22587,7 +22509,7 @@ msgstr "" #~ msgstr "Contagem máxima de projetos recentes" #~ msgid "This parameter adds a rotation of sparse infill direction to each layer according to the specified template. The template is a comma-separated list of angles in degrees, e.g. '0,90'. The first angle is applied to the first layer, the second angle to the second layer, and so on. If there are more layers than angles, the angles will be repeated. Note that not all sparse infill patterns support rotation." -#~ msgstr "Este parâmetro adiciona uma rotação na direção do preenchimento esparso a cada camada, de acordo com o gabarito especificado. O gabarito é uma lista de ângulos em graus separados por vírgulas, por exemplo, '0,90'. O primeiro ângulo é aplicado à primeira camada, o segundo ângulo à segunda camada e assim por diante. Se houver mais camadas do que ângulos, os ângulos serão repetidos. Observe que nem todos os padrões de preenchimento esparso suportam rotação." +#~ msgstr "Este parâmetro adiciona uma rotação na direção do preenchimento esparso a cada camada, de acordo com o gabarito especificado. O gabarito é uma lista de ângulos em graus separados por vírgulas, como '0,90'. O primeiro ângulo é aplicado à primeira camada, o segundo ângulo à segunda camada e assim por diante. Se houver mais camadas do que ângulos, os ângulos serão repetidos. Observe que nem todos os padrões de preenchimento esparso suportam rotação." #~ msgid "Set Position" #~ msgstr "Definir Posição" diff --git a/localization/i18n/ru/OrcaSlicer_ru.po b/localization/i18n/ru/OrcaSlicer_ru.po index b5e6baffe0..972f853df4 100644 --- a/localization/i18n/ru/OrcaSlicer_ru.po +++ b/localization/i18n/ru/OrcaSlicer_ru.po @@ -2431,8 +2431,6 @@ msgstr "Доступно обновление. Проверьте меню па msgid "%s has been removed." msgstr "%s был удалён." -msgid "Switching application language" -msgstr "Изменение языка приложения" msgid "Select the language" msgstr "Выбор языка" @@ -5337,13 +5335,9 @@ msgstr "Значение %s выходит за пределы допустим #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Имелось ввиду %s%%? (введено %s %s)\n" -"Да – изменить на %s%%\n" -"Нет – оставить %s %s." +"Имелось ввиду %s%% или %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8852,8 +8846,6 @@ msgstr "Хотите продолжить?" msgid "Language selection" msgstr "Выбор языка" -msgid "Switching application language while some presets are modified." -msgstr "Смена языка приложения при изменении некоторых профилей." msgid "Asia-Pacific" msgstr "Азиатско-Тихоокеанский" diff --git a/localization/i18n/sv/OrcaSlicer_sv.po b/localization/i18n/sv/OrcaSlicer_sv.po index b4e3bf51bc..472b105744 100644 --- a/localization/i18n/sv/OrcaSlicer_sv.po +++ b/localization/i18n/sv/OrcaSlicer_sv.po @@ -2767,8 +2767,6 @@ msgstr "Det finns en uppdatering tillgänglig. Öppna dialogrutan för förinst msgid "%s has been removed." msgstr "%s har tagits bort." -msgid "Switching application language" -msgstr "Byt applikationsspråk" msgid "Select the language" msgstr "Välj språk" @@ -5907,13 +5905,9 @@ msgstr "Värdet %s ligger utanför intervallet. Giltigt intervall är från %d t #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Det är %s%% eller %s %s?\n" -"JA för %s%%, \n" -"NEJ för %s %s." +"Det är %s%% eller %s %s?" # AI Translated #, boost-format @@ -9694,8 +9688,6 @@ msgstr "Fortsätta?" msgid "Language selection" msgstr "Språkval" -msgid "Switching application language while some presets are modified." -msgstr "Byter språk medans inställningarna ändras." msgid "Asia-Pacific" msgstr "Asien-Stillahavsområdet" diff --git a/localization/i18n/th/OrcaSlicer_th.po b/localization/i18n/th/OrcaSlicer_th.po index 935bb83a3b..2865914d23 100644 --- a/localization/i18n/th/OrcaSlicer_th.po +++ b/localization/i18n/th/OrcaSlicer_th.po @@ -2456,8 +2456,6 @@ msgstr "มีอัปเดตพร้อมใช้งาน เปิด msgid "%s has been removed." msgstr "ลบ %s แล้ว" -msgid "Switching application language" -msgstr "การเปลี่ยนภาษาของแอปพลิเคชัน" msgid "Select the language" msgstr "เลือกภาษา" @@ -5321,13 +5319,9 @@ msgstr "ค่า %s อยู่นอกช่วง ช่วงที่ถ #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"มันคือ %s%% หรือ %s %s?\n" -"ใช่สำหรับ %s%% \n" -"ไม่ สำหรับ %s %s" +"มันคือ %s%% หรือ %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8758,8 +8752,6 @@ msgstr "ต้องการดำเนินการต่อหรือไ msgid "Language selection" msgstr "การเลือกภาษา" -msgid "Switching application language while some presets are modified." -msgstr "การสลับภาษาของแอปพลิเคชันในขณะที่มีการแก้ไขค่าที่ตั้งไว้บางส่วน" msgid "Asia-Pacific" msgstr "เอเชียแปซิฟิก" diff --git a/localization/i18n/tr/OrcaSlicer_tr.po b/localization/i18n/tr/OrcaSlicer_tr.po index 02d54aa39f..a5c11f5300 100644 --- a/localization/i18n/tr/OrcaSlicer_tr.po +++ b/localization/i18n/tr/OrcaSlicer_tr.po @@ -2480,8 +2480,6 @@ msgstr "Kullanılabilir bir güncelleme var. Güncellemek için ön ayar paketi msgid "%s has been removed." msgstr "%s kaldırıldı." -msgid "Switching application language" -msgstr "Uygulama dilini değiştirme" msgid "Select the language" msgstr "Dili seçin" @@ -5384,13 +5382,9 @@ msgstr "Değer %s aralık dışında. Geçerli aralık %d ile %d arasındadır." #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%% mi yoksa %s %s mi?\n" -"%s%% için EVET,\n" -"%s %s için HAYIR." +"%s%% mi yoksa %s %s mi?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8860,8 +8854,6 @@ msgstr "Devam etmek istiyor musun?" msgid "Language selection" msgstr "Dil seçimi" -msgid "Switching application language while some presets are modified." -msgstr "Bazı ön ayarlar değiştirilirken uygulama dilinin değiştirilmesi." msgid "Asia-Pacific" msgstr "Asya Pasifik" diff --git a/localization/i18n/uk/OrcaSlicer_uk.po b/localization/i18n/uk/OrcaSlicer_uk.po index d72a1f9792..c3ac201896 100644 --- a/localization/i18n/uk/OrcaSlicer_uk.po +++ b/localization/i18n/uk/OrcaSlicer_uk.po @@ -2424,8 +2424,6 @@ msgstr "Доступне оновлення. Відкрийте вікно на msgid "%s has been removed." msgstr "%s вилучено." -msgid "Switching application language" -msgstr "Зміна мови програми" msgid "Select the language" msgstr "Вибрати мову" @@ -5332,13 +5330,9 @@ msgstr "Значення %s знаходиться за межами діапа #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Це %s%% або %s %s?\n" -"ТАК для %s%%, \n" -"НІ для %s %s." +"Це %s%% або %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8874,8 +8868,6 @@ msgstr "Ви хочете продовжувати?" msgid "Language selection" msgstr "Вибір мови" -msgid "Switching application language while some presets are modified." -msgstr "Зміна мови програми при зміні деяких профілів." msgid "Asia-Pacific" msgstr "Азіатсько-Тихоокеанський регіон" diff --git a/localization/i18n/vi/OrcaSlicer_vi.po b/localization/i18n/vi/OrcaSlicer_vi.po index a0758d51ad..4504b5c95c 100644 --- a/localization/i18n/vi/OrcaSlicer_vi.po +++ b/localization/i18n/vi/OrcaSlicer_vi.po @@ -2568,8 +2568,6 @@ msgstr "Có bản cập nhật khả dụng. Hãy mở hộp thoại gói cài msgid "%s has been removed." msgstr "%s đã bị xóa." -msgid "Switching application language" -msgstr "Đang chuyển ngôn ngữ ứng dụng" msgid "Select the language" msgstr "Chọn ngôn ngữ" @@ -5637,13 +5635,9 @@ msgstr "Giá trị %s nằm ngoài phạm vi. Phạm vi hợp lệ từ %d đế #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"Là %s%% hay %s %s?\n" -"YES cho %s%%, \n" -"NO cho %s %s." +"Là %s%% hay %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -9309,8 +9303,6 @@ msgstr "Bạn có muốn tiếp tục?" msgid "Language selection" msgstr "Chọn ngôn ngữ" -msgid "Switching application language while some presets are modified." -msgstr "Đang chuyển đổi ngôn ngữ ứng dụng trong khi một số preset đã được chỉnh sửa." msgid "Asia-Pacific" msgstr "Châu Á-Thái Bình Dương" diff --git a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po index 7c474031c9..ff2e0fd940 100644 --- a/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po +++ b/localization/i18n/zh_CN/OrcaSlicer_zh_CN.po @@ -2361,8 +2361,6 @@ msgstr "有更新可用。打开预设包对话框进行更新。" msgid "%s has been removed." msgstr "%s 已被移除。" -msgid "Switching application language" -msgstr "切换应用程序语言" msgid "Select the language" msgstr "选择语言" @@ -5177,13 +5175,9 @@ msgstr "值 %s 超出了范围,有效的范围是从 %d 到 %d 。" #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"%s%%还是%s %s?\n" -"是:%s%%\n" -"否:%s %s" +"%s%%还是%s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8587,8 +8581,6 @@ msgstr "是否继续?" msgid "Language selection" msgstr "语言选择" -msgid "Switching application language while some presets are modified." -msgstr "在切换应用语言之前发现某些参数预设有更改。" msgid "Asia-Pacific" msgstr "亚太" diff --git a/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po b/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po index 83b22ca029..e0e468fccf 100644 --- a/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po +++ b/localization/i18n/zh_TW/OrcaSlicer_zh_TW.po @@ -2425,8 +2425,6 @@ msgstr "有可用的更新。請開啟預設組合對話框進行更新。" msgid "%s has been removed." msgstr "%s 已移除。" -msgid "Switching application language" -msgstr "切換應用程式語言" msgid "Select the language" msgstr "選擇語言" @@ -5306,13 +5304,9 @@ msgstr "數值 %s 超出範圍。有效範圍是從 %d 到 %d。" #, c-format, boost-format msgid "" -"Is it %s%% or %s %s?\n" -"YES for %s%%, \n" -"NO for %s %s." +"Is it %s%% or %s %s?" msgstr "" -"是 %s%% 還是 %s %s?\n" -"選『是』代表 %s%%,\n" -"選『否』代表 %s %s。" +"是 %s%% 還是 %s %s?" #, boost-format msgid "Invalid input format. Expected vector of dimensions in the following format: \"%1%\"" @@ -8753,8 +8747,6 @@ msgstr "是否繼續?" msgid "Language selection" msgstr "語言選擇" -msgid "Switching application language while some presets are modified." -msgstr "在切換應用程式語言之前發現某些參數預設有更改。" msgid "Asia-Pacific" msgstr "亞太" diff --git a/resources/images/param_add.svg b/resources/images/param_add.svg index 71ea5092af..b00140b68a 100644 --- a/resources/images/param_add.svg +++ b/resources/images/param_add.svg @@ -1,8 +1,4 @@ - - - - Layer 1 - - - - + + + + \ No newline at end of file diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.25 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.25 nozzle.json index 7fb1d1f66c..4852093cda 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.25 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.25 nozzle.json @@ -324,12 +324,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json index 64ec8774b0..16d4b54568 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json @@ -318,12 +318,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.6 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.6 nozzle.json index ea95c2b1c5..ebc7e8e514 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.6 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.6 nozzle.json @@ -324,12 +324,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.8 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.8 nozzle.json index a2cd9ecc5e..978c51007e 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.8 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra S1 Max 0.8 nozzle.json @@ -324,12 +324,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra X 0.4 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra X 0.4 nozzle.json index b0c543cebf..e2c6e84c1b 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra X 0.4 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG @Anycubic Kobra X 0.4 nozzle.json @@ -309,12 +309,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.4 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.4 nozzle.json index bbe7575b49..0774881a2b 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.4 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.4 nozzle.json @@ -318,12 +318,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.6 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.6 nozzle.json index d78fcc1d27..e3a405e192 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.6 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.6 nozzle.json @@ -324,12 +324,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.8 nozzle.json b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.8 nozzle.json index cc3e8a15fc..e28e2d859e 100644 --- a/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.8 nozzle.json +++ b/resources/profiles/Anycubic/filament/Anycubic PETG-CF @Anycubic Kobra S1 Max 0.8 nozzle.json @@ -324,12 +324,6 @@ "slow_down_min_speed": [ "10" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json b/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json index e025d0587c..b44b6ecc11 100644 --- a/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json +++ b/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra S1 Max 0.4 nozzle.json @@ -318,12 +318,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra X 0.4 nozzle.json b/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra X 0.4 nozzle.json index 78925acad9..0f238e5a5b 100644 --- a/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra X 0.4 nozzle.json +++ b/resources/profiles/Anycubic/filament/Generic PETG @Anycubic Kobra X 0.4 nozzle.json @@ -309,12 +309,6 @@ "slow_down_min_speed": [ "20" ], - "supertack_plate_temp": [ - "35" - ], - "supertack_plate_temp_initial_layer": [ - "35" - ], "support_material_interface_fan_speed": [ "-1" ], diff --git a/resources/profiles/Anycubic/filament/fdm_filament_pet.json b/resources/profiles/Anycubic/filament/fdm_filament_pet.json index 62af7c89ed..56671ade0d 100644 --- a/resources/profiles/Anycubic/filament/fdm_filament_pet.json +++ b/resources/profiles/Anycubic/filament/fdm_filament_pet.json @@ -25,6 +25,12 @@ "hot_plate_temp_initial_layer": [ "80" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "slow_down_for_layer_cooling": [ "1" ], diff --git a/resources/profiles/Flashforge.json b/resources/profiles/Flashforge.json index f3923c435a..f348a4f329 100644 --- a/resources/profiles/Flashforge.json +++ b/resources/profiles/Flashforge.json @@ -1,7 +1,7 @@ { "name": "Flashforge", "url": "", - "version": "02.04.00.05", + "version": "02.04.00.06", "force_update": "0", "description": "Flashforge configurations", "machine_model_list": [ @@ -439,6 +439,22 @@ "name": "0.14mm Standard @FF AD5X 0.25 nozzle", "sub_path": "process/0.14mm Standard @FF AD5X 0.25 nozzle.json" }, + { + "name": "0.08mm Standard @FF C5 0.25 nozzle", + "sub_path": "process/0.08mm Standard @FF C5 0.25 nozzle.json" + }, + { + "name": "0.10mm Standard @FF C5 0.25 nozzle", + "sub_path": "process/0.10mm Standard @FF C5 0.25 nozzle.json" + }, + { + "name": "0.12mm Standard @FF C5 0.25 nozzle", + "sub_path": "process/0.12mm Standard @FF C5 0.25 nozzle.json" + }, + { + "name": "0.14mm Standard @FF C5 0.25 nozzle", + "sub_path": "process/0.14mm Standard @FF C5 0.25 nozzle.json" + }, { "name": "0.14mm Standard @Flashforge AD5M 0.25 Nozzle", "sub_path": "process/0.14mm Standard @Flashforge AD5M 0.25 Nozzle.json" @@ -2903,6 +2919,10 @@ "name": "Flashforge AD5X 0.4 nozzle", "sub_path": "machine/Flashforge AD5X 0.4 nozzle.json" }, + { + "name": "Flashforge Creator 5 0.25 nozzle", + "sub_path": "machine/Flashforge Creator 5 0.25 nozzle.json" + }, { "name": "Flashforge Creator 5 0.4 nozzle", "sub_path": "machine/Flashforge Creator 5 0.4 nozzle.json" @@ -2915,6 +2935,10 @@ "name": "Flashforge Creator 5 0.8 nozzle", "sub_path": "machine/Flashforge Creator 5 0.8 nozzle.json" }, + { + "name": "Flashforge Creator 5 Pro 0.25 nozzle", + "sub_path": "machine/Flashforge Creator 5 Pro 0.25 nozzle.json" + }, { "name": "Flashforge Creator 5 Pro 0.4 nozzle", "sub_path": "machine/Flashforge Creator 5 Pro 0.4 nozzle.json" diff --git a/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4 HF.json b/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4 HF.json index 955235b442..3534f1a60b 100644 --- a/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4 HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4 HF.json @@ -87,6 +87,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ] diff --git a/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4P HF.json b/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4P HF.json index 82a8dc096a..263224a1ea 100644 --- a/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4P HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge HS PETG @FF G4P HF.json @@ -93,6 +93,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ], diff --git a/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4 HF.json b/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4 HF.json index d1c404cd19..8308064c52 100644 --- a/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4 HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4 HF.json @@ -90,6 +90,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ] diff --git a/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4P HF.json b/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4P HF.json index 1385ac8454..d8bdc53caa 100644 --- a/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4P HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge PETG Pro @FF G4P HF.json @@ -96,6 +96,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ], diff --git a/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4 HF.json b/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4 HF.json index 2736fea042..f38a075dea 100644 --- a/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4 HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4 HF.json @@ -90,6 +90,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ] diff --git a/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4P HF.json b/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4P HF.json index bc6fbecdb4..c920f757e5 100644 --- a/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4P HF.json +++ b/resources/profiles/Flashforge/filament/Flashforge PETG Transparent @FF G4P HF.json @@ -96,6 +96,12 @@ "support_material_interface_fan_speed": [ "90" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "temperature_vitrification": [ "70" ], diff --git a/resources/profiles/Flashforge/filament/fdm_filament_pet.json b/resources/profiles/Flashforge/filament/fdm_filament_pet.json index 678ab77822..a19e1faece 100644 --- a/resources/profiles/Flashforge/filament/fdm_filament_pet.json +++ b/resources/profiles/Flashforge/filament/fdm_filament_pet.json @@ -28,6 +28,12 @@ "textured_plate_temp_initial_layer": [ "85" ], + "supertack_plate_temp": [ + "60" + ], + "supertack_plate_temp_initial_layer": [ + "60" + ], "slow_down_for_layer_cooling": [ "1" ], diff --git a/resources/profiles/Flashforge/machine/Flashforge Creator 5 0.25 nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Creator 5 0.25 nozzle.json new file mode 100644 index 0000000000..54be573cf7 --- /dev/null +++ b/resources/profiles/Flashforge/machine/Flashforge Creator 5 0.25 nozzle.json @@ -0,0 +1,311 @@ +{ + "type": "machine", + "name": "Flashforge Creator 5 0.25 nozzle", + "inherits": "Flashforge Adventurer 5M Pro 0.4 Nozzle", + "from": "system", + "setting_id": "xp3cpTEGYdWjcrMM", + "instantiation": "true", + "adaptive_bed_mesh_margin": "0", + "auxiliary_fan": "1", + "bbl_use_printhost": "0", + "bed_custom_model": "", + "bed_custom_texture": "", + "bed_exclude_area": [ + "0x0" + ], + "bed_mesh_max": "99999,99999", + "bed_mesh_min": "-99999,-99999", + "bed_mesh_probe_distance": "50,50", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]", + "best_object_pos": "0.5,0.5", + "change_extrusion_role_gcode": "", + "change_filament_gcode": "", + "cooling_tube_length": "0", + "cooling_tube_retraction": "0", + "default_bed_type": "", + "default_filament_profile": [ + "Flashforge Generic PLA" + ], + "default_print_profile": "0.12mm Standard @FF C5 0.25 nozzle", + "deretraction_speed": [ + "30", + "30", + "30", + "30" + ], + "disable_m73": "1", + "emit_machine_limits_to_gcode": "1", + "enable_filament_ramming": "0", + "enable_long_retraction_when_cut": "0", + "extra_loading_move": "0", + "extruder_clearance_height_to_lid": "360", + "extruder_clearance_height_to_rod": "60", + "extruder_clearance_radius": "92", + "extruder_colour": [ + "#FCE94F", + "#FCE94F", + "#FCE94F", + "#FCE94F" + ], + "extruder_offset": [ + "0x0", + "0x0", + "0x0", + "0x0" + ], + "fan_kickstart": "0", + "fan_speedup_overhangs": "1", + "fan_speedup_time": "0", + "gcode_flavor": "klipper", + "head_wrap_detect_zone": [], + "high_current_on_filament_swap": "0", + "host_type": "octoprint", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "long_retractions_when_cut": [ + "0", + "0", + "0", + "0" + ], + "machine_end_gcode": ";end_gcode\nG1 X150 Y150 E-1.2 F12000", + "machine_load_filament_time": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "20000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "20000", + "20000" + ], + "machine_max_acceleration_x": [ + "30000", + "20000" + ], + "machine_max_acceleration_y": [ + "30000", + "20000" + ], + "machine_max_acceleration_z": [ + "300", + "500" + ], + "machine_max_jerk_e": [ + "2.5", + "2.5" + ], + "machine_max_jerk_x": [ + "9", + "9" + ], + "machine_max_jerk_y": [ + "9", + "9" + ], + "machine_max_jerk_z": [ + "3", + "3" + ], + "machine_max_junction_deviation": [ + "0", + "0" + ], + "machine_max_speed_e": [ + "30", + "30" + ], + "machine_max_speed_x": [ + "600", + "600" + ], + "machine_max_speed_y": [ + "600", + "600" + ], + "machine_max_speed_z": [ + "20", + "20" + ], + "machine_min_extruding_rate": [ + "0", + "0" + ], + "machine_min_travel_rate": [ + "0", + "0" + ], + "machine_pause_gcode": "M25", + "machine_start_gcode": ";start_gcode\nM140 S[bed_temperature_initial_layer_single]\nM106 P101 S0 ; L+R_PLA_Turbo_Fan_0-255\nM106 P2 S0 ; Center_Fresch_Air_Input_Fan_for_PLA30%_(80_0-255) \nM191 S0 ; Chamber temp. max65C\nM106 S0 ; Model_Fan_(Heat_Break_Cooler_80-255)100%=255\nM106 P3 S0 ; Filter_Unit_Fan_0-255(ABS=255)\nM104 S[nozzle_temperature_initial_layer]\nG90\nM83\nG1 Z5 F2400\nT[initial_extruder]\nM109 S[nozzle_temperature_initial_layer] T[initial_extruder]\nG1 X256 Y0 Z0.2 F6000\nG1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\nG1 X216 E10 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n;start_gcode end", + "machine_tool_change_time": "7", + "machine_unload_filament_time": "0", + "manual_filament_change": "0", + "max_layer_height": [ + "0.14", + "0.14", + "0.14", + "0.14" + ], + "max_resonance_avoidance_speed": "120", + "min_layer_height": [ + "0.08", + "0.08", + "0.08", + "0.08" + ], + "min_resonance_avoidance_speed": "70", + "nozzle_diameter": [ + "0.25", + "0.25", + "0.25", + "0.25" + ], + "nozzle_height": "4", + "nozzle_hrc": "0", + "nozzle_type": "hardened_steel", + "nozzle_volume": "0", + "parking_pos_retraction": "0", + "pellet_modded_printer": "0", + "preferred_orientation": "0", + "printable_area": [ + "0x0", + "256x0", + "256x256", + "0x256" + ], + "printable_height": "256", + "printer_model": "Flashforge Creator 5", + "printer_notes": "", + "printer_settings_id": "Flashforge Creator 5 0.25 nozzle", + "printer_structure": "corexy", + "printer_technology": "FFF", + "printer_variant": "0.25", + "printhost_authorization_type": "key", + "printhost_ssl_ignore_revoke": "0", + "printing_by_object_gcode": "", + "purge_in_prime_tower": "0", + "resonance_avoidance": "0", + "retract_before_wipe": [ + "100%", + "100%", + "100%", + "100%" + ], + "retract_length_toolchange": [ + "3", + "3", + "3", + "3" + ], + "retract_lift_above": [ + "0", + "0", + "0", + "0" + ], + "retract_lift_below": [ + "0", + "0", + "0", + "0" + ], + "retract_lift_enforce": [ + "All Surfaces", + "All Surfaces", + "All Surfaces", + "All Surfaces" + ], + "retract_restart_extra": [ + "0", + "0", + "0", + "0" + ], + "retract_restart_extra_toolchange": [ + "-0.8", + "-0.8", + "-0.8", + "-0.8" + ], + "retract_when_changing_layer": [ + "1", + "1", + "1", + "1" + ], + "retraction_distances_when_cut": [ + "18", + "18", + "18", + "18" + ], + "retraction_length": [ + "0.8", + "0.8", + "0.8", + "0.8" + ], + "retraction_minimum_travel": [ + "2", + "2", + "2", + "2" + ], + "retraction_speed": [ + "30", + "30", + "30", + "30" + ], + "scan_first_layer": "0", + "silent_mode": "0", + "single_extruder_multi_material": "0", + "support_air_filtration": "0", + "support_chamber_temp_control": "0", + "support_multi_bed_types": "1", + "template_custom_gcode": "", + "thumbnails": "140x110/PNG", + "thumbnails_format": "PNG", + "time_cost": "0", + "time_lapse_gcode": "", + "travel_slope": [ + "3", + "3", + "3", + "3" + ], + "upward_compatible_machine": [], + "use_firmware_retraction": "0", + "use_relative_e_distances": "1", + "wipe": [ + "1", + "1", + "1", + "1" + ], + "wipe_distance": [ + "2", + "2", + "2", + "2" + ], + "z_hop": [ + "0.4", + "0.4", + "0.4", + "0.4" + ], + "z_hop_types": [ + "Auto Lift", + "Auto Lift", + "Auto Lift", + "Auto Lift" + ], + "z_offset": "0" +} diff --git a/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro 0.25 nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro 0.25 nozzle.json new file mode 100644 index 0000000000..3eea1c58cf --- /dev/null +++ b/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro 0.25 nozzle.json @@ -0,0 +1,311 @@ +{ + "type": "machine", + "name": "Flashforge Creator 5 Pro 0.25 nozzle", + "inherits": "Flashforge Adventurer 5M Pro 0.4 Nozzle", + "from": "system", + "setting_id": "rpmib7bIKa85LNMR", + "instantiation": "true", + "adaptive_bed_mesh_margin": "0", + "auxiliary_fan": "1", + "bbl_use_printhost": "0", + "bed_custom_model": "", + "bed_custom_texture": "", + "bed_exclude_area": [ + "0x0" + ], + "bed_mesh_max": "99999,99999", + "bed_mesh_min": "-99999,-99999", + "bed_mesh_probe_distance": "50,50", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]", + "best_object_pos": "0.5,0.5", + "change_extrusion_role_gcode": "", + "change_filament_gcode": "", + "cooling_tube_length": "0", + "cooling_tube_retraction": "0", + "default_bed_type": "", + "default_filament_profile": [ + "Flashforge Generic PLA" + ], + "default_print_profile": "0.12mm Standard @FF C5 0.25 nozzle", + "deretraction_speed": [ + "30", + "30", + "30", + "30" + ], + "disable_m73": "1", + "emit_machine_limits_to_gcode": "1", + "enable_filament_ramming": "0", + "enable_long_retraction_when_cut": "0", + "extra_loading_move": "0", + "extruder_clearance_height_to_lid": "360", + "extruder_clearance_height_to_rod": "60", + "extruder_clearance_radius": "92", + "extruder_colour": [ + "#FCE94F", + "#FCE94F", + "#FCE94F", + "#FCE94F" + ], + "extruder_offset": [ + "0x0", + "0x0", + "0x0", + "0x0" + ], + "fan_kickstart": "0", + "fan_speedup_overhangs": "1", + "fan_speedup_time": "0", + "gcode_flavor": "klipper", + "head_wrap_detect_zone": [], + "high_current_on_filament_swap": "0", + "host_type": "octoprint", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", + "long_retractions_when_cut": [ + "0", + "0", + "0", + "0" + ], + "machine_end_gcode": ";end_gcode\nG1 X150 Y150 E-1.2 F12000", + "machine_load_filament_time": "0", + "machine_max_acceleration_e": [ + "5000", + "5000" + ], + "machine_max_acceleration_extruding": [ + "30000", + "20000" + ], + "machine_max_acceleration_retracting": [ + "5000", + "5000" + ], + "machine_max_acceleration_travel": [ + "20000", + "20000" + ], + "machine_max_acceleration_x": [ + "30000", + "20000" + ], + "machine_max_acceleration_y": [ + "30000", + "20000" + ], + "machine_max_acceleration_z": [ + "300", + "500" + ], + "machine_max_jerk_e": [ + "2.5", + "2.5" + ], + "machine_max_jerk_x": [ + "9", + "9" + ], + "machine_max_jerk_y": [ + "9", + "9" + ], + "machine_max_jerk_z": [ + "3", + "3" + ], + "machine_max_junction_deviation": [ + "0", + "0" + ], + "machine_max_speed_e": [ + "30", + "30" + ], + "machine_max_speed_x": [ + "600", + "600" + ], + "machine_max_speed_y": [ + "600", + "600" + ], + "machine_max_speed_z": [ + "20", + "20" + ], + "machine_min_extruding_rate": [ + "0", + "0" + ], + "machine_min_travel_rate": [ + "0", + "0" + ], + "machine_pause_gcode": "M25", + "machine_start_gcode": ";start_gcode\nM140 S[bed_temperature_initial_layer_single]\nM106 P101 S0 ; L+R_PLA_Turbo_Fan_0-255\nM106 P2 S0 ; Center_Fresch_Air_Input_Fan_for_PLA30%_(80_0-255) \nM191 S0 ; Chamber temp. max65C\nM106 S0 ; Model_Fan_(Heat_Break_Cooler_80-255)100%=255\nM106 P3 S0 ; Filter_Unit_Fan_0-255(ABS=255)\nM104 S[nozzle_temperature_initial_layer]\nG90\nM83\nG1 Z5 F2400\nT[initial_extruder]\nM109 S[nozzle_temperature_initial_layer] T[initial_extruder]\nG1 X256 Y0 Z0.2 F6000\nG1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\nG1 X216 E10 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}\n;start_gcode end", + "machine_tool_change_time": "7", + "machine_unload_filament_time": "0", + "manual_filament_change": "0", + "max_layer_height": [ + "0.14", + "0.14", + "0.14", + "0.14" + ], + "max_resonance_avoidance_speed": "120", + "min_layer_height": [ + "0.08", + "0.08", + "0.08", + "0.08" + ], + "min_resonance_avoidance_speed": "70", + "nozzle_diameter": [ + "0.25", + "0.25", + "0.25", + "0.25" + ], + "nozzle_height": "4", + "nozzle_hrc": "0", + "nozzle_type": "hardened_steel", + "nozzle_volume": "0", + "parking_pos_retraction": "0", + "pellet_modded_printer": "0", + "preferred_orientation": "0", + "printable_area": [ + "0x0", + "256x0", + "256x256", + "0x256" + ], + "printable_height": "256", + "printer_model": "Flashforge Creator 5 Pro", + "printer_notes": "", + "printer_settings_id": "Flashforge Creator 5 Pro 0.25 nozzle", + "printer_structure": "corexy", + "printer_technology": "FFF", + "printer_variant": "0.25", + "printhost_authorization_type": "key", + "printhost_ssl_ignore_revoke": "0", + "printing_by_object_gcode": "", + "purge_in_prime_tower": "0", + "resonance_avoidance": "0", + "retract_before_wipe": [ + "100%", + "100%", + "100%", + "100%" + ], + "retract_length_toolchange": [ + "3", + "3", + "3", + "3" + ], + "retract_lift_above": [ + "0", + "0", + "0", + "0" + ], + "retract_lift_below": [ + "0", + "0", + "0", + "0" + ], + "retract_lift_enforce": [ + "All Surfaces", + "All Surfaces", + "All Surfaces", + "All Surfaces" + ], + "retract_restart_extra": [ + "0", + "0", + "0", + "0" + ], + "retract_restart_extra_toolchange": [ + "-0.8", + "-0.8", + "-0.8", + "-0.8" + ], + "retract_when_changing_layer": [ + "1", + "1", + "1", + "1" + ], + "retraction_distances_when_cut": [ + "18", + "18", + "18", + "18" + ], + "retraction_length": [ + "0.8", + "0.8", + "0.8", + "0.8" + ], + "retraction_minimum_travel": [ + "2", + "2", + "2", + "2" + ], + "retraction_speed": [ + "30", + "30", + "30", + "30" + ], + "scan_first_layer": "0", + "silent_mode": "0", + "single_extruder_multi_material": "0", + "support_air_filtration": "1", + "support_chamber_temp_control": "1", + "support_multi_bed_types": "1", + "template_custom_gcode": "", + "thumbnails": "140x110/PNG", + "thumbnails_format": "PNG", + "time_cost": "0", + "time_lapse_gcode": "", + "travel_slope": [ + "3", + "3", + "3", + "3" + ], + "upward_compatible_machine": [], + "use_firmware_retraction": "0", + "use_relative_e_distances": "1", + "wipe": [ + "1", + "1", + "1", + "1" + ], + "wipe_distance": [ + "2", + "2", + "2", + "2" + ], + "z_hop": [ + "0.4", + "0.4", + "0.4", + "0.4" + ], + "z_hop_types": [ + "Auto Lift", + "Auto Lift", + "Auto Lift", + "Auto Lift" + ], + "z_offset": "0" +} diff --git a/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro.json b/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro.json index bf8cbf3702..4ef02628d1 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro.json +++ b/resources/profiles/Flashforge/machine/Flashforge Creator 5 Pro.json @@ -2,7 +2,7 @@ "type": "machine_model", "name": "Flashforge Creator 5 Pro", "model_id": "Flashforge-Creator-5-Pro", - "nozzle_diameter": "0.4;0.6;0.8", + "nozzle_diameter": "0.25;0.4;0.6;0.8", "machine_tech": "FFF", "family": "Flashforge", "bed_model": "flashforge_c5_buildplate_model.stl", diff --git a/resources/profiles/Flashforge/machine/Flashforge Creator 5.json b/resources/profiles/Flashforge/machine/Flashforge Creator 5.json index 8d5ac253b7..7ef9c172c0 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Creator 5.json +++ b/resources/profiles/Flashforge/machine/Flashforge Creator 5.json @@ -2,7 +2,7 @@ "type": "machine_model", "name": "Flashforge Creator 5", "model_id": "Flashforge-Creator-5", - "nozzle_diameter": "0.4;0.6;0.8", + "nozzle_diameter": "0.25;0.4;0.6;0.8", "machine_tech": "FFF", "family": "Flashforge", "bed_model": "flashforge_c5_buildplate_model.stl", diff --git a/resources/profiles/Flashforge/process/0.08mm Standard @FF C5 0.25 nozzle.json b/resources/profiles/Flashforge/process/0.08mm Standard @FF C5 0.25 nozzle.json new file mode 100644 index 0000000000..09221f10b2 --- /dev/null +++ b/resources/profiles/Flashforge/process/0.08mm Standard @FF C5 0.25 nozzle.json @@ -0,0 +1,28 @@ +{ + "type": "process", + "name": "0.08mm Standard @FF C5 0.25 nozzle", + "inherits": "0.10mm Standard @FF AD5X 0.25 nozzle", + "from": "system", + "setting_id": "KrOSCGKyvNy9v08u", + "instantiation": "true", + "bottom_solid_infill_flow_ratio": "1", + "compatible_printers": [ + "Flashforge Creator 5 0.25 nozzle", + "Flashforge Creator 5 Pro 0.25 nozzle" + ], + "initial_layer_print_height": "0.1", + "internal_bridge_flow": "1", + "layer_height": "0.08", + "ooze_prevention": "1", + "preheat_time": "20", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "print_settings_id": "0.08mm Standard @FF C5 0.25 nozzle", + "small_perimeter_speed": "30", + "standby_temperature_delta": "-100", + "top_solid_infill_flow_ratio": "1" +} diff --git a/resources/profiles/Flashforge/process/0.10mm Standard @FF C5 0.25 nozzle.json b/resources/profiles/Flashforge/process/0.10mm Standard @FF C5 0.25 nozzle.json new file mode 100644 index 0000000000..47ea8c6322 --- /dev/null +++ b/resources/profiles/Flashforge/process/0.10mm Standard @FF C5 0.25 nozzle.json @@ -0,0 +1,27 @@ +{ + "type": "process", + "name": "0.10mm Standard @FF C5 0.25 nozzle", + "inherits": "0.10mm Standard @FF AD5X 0.25 nozzle", + "from": "system", + "setting_id": "0Mep1gVTvD4RwK53", + "instantiation": "true", + "bottom_solid_infill_flow_ratio": "1", + "compatible_printers": [ + "Flashforge Creator 5 0.25 nozzle", + "Flashforge Creator 5 Pro 0.25 nozzle" + ], + "initial_layer_print_height": "0.1", + "internal_bridge_flow": "1", + "ooze_prevention": "1", + "preheat_time": "20", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "print_settings_id": "0.10mm Standard @FF C5 0.25 nozzle", + "small_perimeter_speed": "30", + "standby_temperature_delta": "-100", + "top_solid_infill_flow_ratio": "1" +} diff --git a/resources/profiles/Flashforge/process/0.12mm Standard @FF C5 0.25 nozzle.json b/resources/profiles/Flashforge/process/0.12mm Standard @FF C5 0.25 nozzle.json new file mode 100644 index 0000000000..df33cdf344 --- /dev/null +++ b/resources/profiles/Flashforge/process/0.12mm Standard @FF C5 0.25 nozzle.json @@ -0,0 +1,28 @@ +{ + "type": "process", + "name": "0.12mm Standard @FF C5 0.25 nozzle", + "inherits": "0.10mm Standard @FF AD5X 0.25 nozzle", + "from": "system", + "setting_id": "rcKQBKZJvCfrfzqV", + "instantiation": "true", + "bottom_solid_infill_flow_ratio": "1", + "compatible_printers": [ + "Flashforge Creator 5 0.25 nozzle", + "Flashforge Creator 5 Pro 0.25 nozzle" + ], + "initial_layer_print_height": "0.1", + "internal_bridge_flow": "1", + "layer_height": "0.12", + "ooze_prevention": "1", + "preheat_time": "20", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "print_settings_id": "0.12mm Standard @FF C5 0.25 nozzle", + "small_perimeter_speed": "30", + "standby_temperature_delta": "-100", + "top_solid_infill_flow_ratio": "1" +} diff --git a/resources/profiles/Flashforge/process/0.14mm Standard @FF C5 0.25 nozzle.json b/resources/profiles/Flashforge/process/0.14mm Standard @FF C5 0.25 nozzle.json new file mode 100644 index 0000000000..fd6f8f560d --- /dev/null +++ b/resources/profiles/Flashforge/process/0.14mm Standard @FF C5 0.25 nozzle.json @@ -0,0 +1,28 @@ +{ + "type": "process", + "name": "0.14mm Standard @FF C5 0.25 nozzle", + "inherits": "0.10mm Standard @FF AD5X 0.25 nozzle", + "from": "system", + "setting_id": "749mQY3CU2jk0MOd", + "instantiation": "true", + "bottom_solid_infill_flow_ratio": "1", + "compatible_printers": [ + "Flashforge Creator 5 0.25 nozzle", + "Flashforge Creator 5 Pro 0.25 nozzle" + ], + "initial_layer_print_height": "0.1", + "internal_bridge_flow": "1", + "layer_height": "0.14", + "ooze_prevention": "1", + "preheat_time": "20", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "print_settings_id": "0.14mm Standard @FF C5 0.25 nozzle", + "small_perimeter_speed": "30", + "standby_temperature_delta": "-100", + "top_solid_infill_flow_ratio": "1" +} diff --git a/resources/profiles/Qidi/filament/X5/Generic ABS @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/Generic ABS @X-Plus 5.json index accf79987f..79a52578a4 100644 --- a/resources/profiles/Qidi/filament/X5/Generic ABS @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/Generic ABS @X-Plus 5.json @@ -8,6 +8,9 @@ "box_temperature_range_high": [ "45" ], + "chamber_temperature": [ + "55" + ], "close_fan_the_first_x_layers": [ "3" ], diff --git a/resources/profiles/Qidi/filament/X5/QIDI PA12-CF @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/QIDI PA12-CF @X-Plus 5.json index 6abc420237..d4af23e160 100644 --- a/resources/profiles/Qidi/filament/X5/QIDI PA12-CF @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/QIDI PA12-CF @X-Plus 5.json @@ -14,9 +14,15 @@ "box_temperature": [ "55" ], + "chamber_temperature": [ + "55" + ], "close_fan_the_first_x_layers": [ "3" ], + "during_print_exhaust_fan_speed": [ + "0" + ], "cool_plate_temp_initial_layer": [ "60" ], diff --git a/resources/profiles/Qidi/filament/X5/QIDI PAHT-CF @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/QIDI PAHT-CF @X-Plus 5.json index ca5591813d..fec939caa8 100644 --- a/resources/profiles/Qidi/filament/X5/QIDI PAHT-CF @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/QIDI PAHT-CF @X-Plus 5.json @@ -14,9 +14,15 @@ "box_temperature": [ "60" ], + "chamber_temperature": [ + "55" + ], "close_fan_the_first_x_layers": [ "3" ], + "during_print_exhaust_fan_speed": [ + "0" + ], "cool_plate_temp_initial_layer": [ "80" ], diff --git a/resources/profiles/Qidi/filament/X5/QIDI PAHT-GF @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/QIDI PAHT-GF @X-Plus 5.json index 411ae26214..de72deb697 100644 --- a/resources/profiles/Qidi/filament/X5/QIDI PAHT-GF @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/QIDI PAHT-GF @X-Plus 5.json @@ -14,9 +14,15 @@ "box_temperature": [ "60" ], + "chamber_temperature": [ + "55" + ], "close_fan_the_first_x_layers": [ "3" ], + "during_print_exhaust_fan_speed": [ + "0" + ], "cool_plate_temp_initial_layer": [ "80" ], diff --git a/resources/profiles/Qidi/filament/X5/QIDI PPS-GF @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/QIDI PPS-GF @X-Plus 5.json index 615d895851..f973121e62 100644 --- a/resources/profiles/Qidi/filament/X5/QIDI PPS-GF @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/QIDI PPS-GF @X-Plus 5.json @@ -14,8 +14,8 @@ "box_temperature": [ "65" ], - "chamber_temperatures": [ - "0" + "chamber_temperature": [ + "55" ], "close_fan_the_first_x_layers": [ "3" diff --git a/resources/profiles/Qidi/filament/X5/QIDI UltraPA-CF25 @X-Plus 5.json b/resources/profiles/Qidi/filament/X5/QIDI UltraPA-CF25 @X-Plus 5.json index 2ea27efa4b..82c261fbd5 100644 --- a/resources/profiles/Qidi/filament/X5/QIDI UltraPA-CF25 @X-Plus 5.json +++ b/resources/profiles/Qidi/filament/X5/QIDI UltraPA-CF25 @X-Plus 5.json @@ -14,6 +14,9 @@ "box_temperature": [ "60" ], + "chamber_temperature": [ + "55" + ], "close_fan_the_first_x_layers": [ "3" ], diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 0407dca2ec..393271d0e5 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.04.00.12", + "version": "02.04.00.13", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/filament/Polymaker/Fiberon ASA-CF08 @Snapmaker U1 base.json b/resources/profiles/Snapmaker/filament/Polymaker/Fiberon ASA-CF08 @Snapmaker U1 base.json index 693553bdcb..86648d6096 100644 --- a/resources/profiles/Snapmaker/filament/Polymaker/Fiberon ASA-CF08 @Snapmaker U1 base.json +++ b/resources/profiles/Snapmaker/filament/Polymaker/Fiberon ASA-CF08 @Snapmaker U1 base.json @@ -15,13 +15,13 @@ "1" ], "cool_plate_temp": [ - "105" + "100" ], "cool_plate_temp_initial_layer": [ - "105" + "100" ], "eng_plate_temp": [ - "105" + "100" ], "eng_plate_temp_initial_layer": [ "100" @@ -48,7 +48,7 @@ "Polymaker" ], "hot_plate_temp": [ - "105" + "100" ], "hot_plate_temp_initial_layer": [ "100" @@ -72,7 +72,7 @@ "110.8" ], "textured_plate_temp": [ - "105" + "100" ], "textured_plate_temp_initial_layer": [ "100" diff --git a/resources/profiles/Snapmaker/filament/Polymaker/Fiberon PPS-GF20 @Snapmaker U1 base.json b/resources/profiles/Snapmaker/filament/Polymaker/Fiberon PPS-GF20 @Snapmaker U1 base.json index 5e1cfa7c61..ee28c2b059 100644 --- a/resources/profiles/Snapmaker/filament/Polymaker/Fiberon PPS-GF20 @Snapmaker U1 base.json +++ b/resources/profiles/Snapmaker/filament/Polymaker/Fiberon PPS-GF20 @Snapmaker U1 base.json @@ -15,16 +15,16 @@ "1" ], "cool_plate_temp": [ - "105" + "100" ], "cool_plate_temp_initial_layer": [ - "105" + "100" ], "eng_plate_temp": [ - "105" + "100" ], "eng_plate_temp_initial_layer": [ - "105" + "100" ], "fan_cooling_layer_time": [ "12" @@ -51,10 +51,10 @@ "Polymaker" ], "hot_plate_temp": [ - "105" + "100" ], "hot_plate_temp_initial_layer": [ - "105" + "100" ], "nozzle_temperature": [ "300" @@ -81,10 +81,10 @@ "110" ], "textured_plate_temp": [ - "105" + "100" ], "textured_plate_temp_initial_layer": [ - "105" + "100" ], "filament_type": [ "ABS" diff --git a/resources/profiles/Snapmaker/filament/Snapmaker ABS @U1 base.json b/resources/profiles/Snapmaker/filament/Snapmaker ABS @U1 base.json index 67754ade09..48740f94bc 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker ABS @U1 base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker ABS @U1 base.json @@ -9,10 +9,10 @@ "" ], "hot_plate_temp": [ - "110" + "100" ], "hot_plate_temp_initial_layer": [ - "105" + "100" ], "overhang_fan_speed": [ "20" diff --git a/resources/profiles/Snapmaker/filament/Snapmaker ASA @U1 base.json b/resources/profiles/Snapmaker/filament/Snapmaker ASA @U1 base.json index 413c14cebb..b75f8d84d3 100644 --- a/resources/profiles/Snapmaker/filament/Snapmaker ASA @U1 base.json +++ b/resources/profiles/Snapmaker/filament/Snapmaker ASA @U1 base.json @@ -9,7 +9,7 @@ "" ], "hot_plate_temp": [ - "110" + "100" ], "hot_plate_temp_initial_layer": [ "100" diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 5463f55c20..218070fa61 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -53,6 +53,8 @@ using namespace nlohmann; #include "libslic3r/libslic3r.h" #include "libslic3r/Config.hpp" +#include "libslic3r/FilamentMixer.hpp" +#include "libslic3r/Preset.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/GCode.hpp" #include "libslic3r/Model.hpp" @@ -77,8 +79,9 @@ using namespace nlohmann; #include "libslic3r/ObjColorUtils.hpp" #include "OrcaSlicer.hpp" -//BBS: add exception handler for win32 +#include #include +//BBS: add exception handler for win32 #ifdef WIN32 #include "dev-utils/BaseException.h" #endif @@ -160,6 +163,7 @@ std::map cli_errors = { {CLI_FILAMENT_CAN_NOT_MAP, "Some filaments cannot be mapped to correct extruders for multi-extruder Printer."}, {CLI_ONLY_ONE_TPU_SUPPORTED, "Not support printing 2 or more TPU filaments."}, {CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER, "Some filaments cannot be printed on the extruder mapped to."}, + {CLI_MIXED_FILAMENT_INVALID, "A mixed filament is invalid: its components are different filament types, or it has no filament of its own."}, {CLI_SLICING_ERROR, "Failed slicing the model. Please verify the slicing of all plates on Orca Slicer before uploading."}, {CLI_GCODE_PATH_CONFLICTS, " G-code conflicts detected after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer. If the file slices normally in Orca Slicer, try moving the wipe tower further from other models, as we use more conservative parameters for it during upload."}, {CLI_GCODE_PATH_IN_UNPRINTABLE_AREA, "Found G-code in unprintable area of multi-extruder printers after slicing. Please make sure the 3mf file can be successfully sliced in the latest Orca Slicer."} @@ -1383,6 +1387,25 @@ int CLI::run(int argc, char **argv) if (downward_check_option) downward_check = downward_check_option->value; + // --export-settings - writes its JSON to stdout, so reject every action or transform that may write there + // too (--info, --help, --orient, slicing and exporting). The allowed ones do nothing when nothing is + // sliced or exported. + if (std::find(m_actions.begin(), m_actions.end(), "export_settings") != m_actions.end() && m_config.opt_string("export_settings") == "-") { + static const std::set stdout_compatible = { "export_settings", "uptodate", "load_defaultfila", "min_save", + "mtcpp", "mstpp", "no_check", "normative_check", "pipe" }; + for (const std::vector *opt_keys : { &m_actions, &m_transforms }) { + for (const std::string &opt_key : *opt_keys) { + if (stdout_compatible.count(opt_key) == 0) { + std::string flag = opt_key; + std::replace(flag.begin(), flag.end(), '_', '-'); + boost::nowide::cerr << "--export-settings - cannot be combined with --" << flag << std::endl; + record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info); + flush_and_exit(CLI_INVALID_PARAMS); + } + } + } + } + bool start_gui = m_actions.empty() && !downward_check; if (start_gui) { BOOST_LOG_TRIVIAL(info) << "no action, start gui directly" << std::endl; @@ -1466,6 +1489,10 @@ int CLI::run(int argc, char **argv) std::vector upward_compatible_printers, new_print_compatible_printers, current_print_compatible_printers, current_different_settings; std::vector current_filaments_name, current_filaments_system_name, current_inherits_group, current_extruder_variants, new_extruder_variants, current_print_extruder_variants, new_printer_extruder_variants; DynamicPrintConfig load_process_config, load_machine_config; + //ORCA: full configs of the "current" (3MF-embedded) process/printer presets, kept so that + // compatible_printers_condition can be evaluated for them below. Previously only the + // literal compatible_printers list was extracted. + DynamicPrintConfig current_process_full_config, current_printer_full_config; bool new_process_config_is_system = true, new_printer_config_is_system = true; std::string pipe_name, makerlab_name, makerlab_version, different_process_setting; const std::vector &metadata_name = m_config.option("metadata_name", true)->values; @@ -2002,19 +2029,21 @@ int CLI::run(int argc, char **argv) } }; - auto resolve_preset = [&ensure_cli_preset_bundle](const std::string &file, DynamicPrintConfig &config, + // One resolver for the whole run, so presets from the same vendor tree share its load. + std::unique_ptr system_preset_resolver; + auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config, std::string &config_type, const std::string &config_from, bool probe_type, std::string &error) { const auto *inherits = config.option(BBL_JSON_KEY_INHERITS); if (!probe_type && (inherits == nullptr || inherits->value.empty())) return true; - std::unique_ptr source_bundle; PresetBundle *bundle = nullptr; bool allow_source_manifest = false; if (config_from == "system") { - source_bundle = std::make_unique(); - bundle = source_bundle.get(); + if (!system_preset_resolver) + system_preset_resolver = std::make_unique(); + bundle = system_preset_resolver.get(); allow_source_manifest = true; } else { bundle = ensure_cli_preset_bundle(error); @@ -2046,6 +2075,51 @@ int CLI::run(int argc, char **argv) error, allow_source_manifest); }; + //ORCA: list the keys a user preset overrides relative to its system parent, for the + // `different_settings_to_system` column of an exported 3MF. Without it the CLI + // writes an empty column, so re-opening a CLI-exported project in the GUI shows + // spurious "unsaved changes" and can revert inherited process/filament/machine + // values to system defaults. + // + // The parent comes from the preset bundle that inherits resolution already builds, + // so this adds no extra loading. Returns "" whenever the parent cannot be resolved, + // which is exactly the previous behaviour. + auto cli_different_settings = [&ensure_cli_preset_bundle](const DynamicPrintConfig &resolved, + const std::string &parent_name, + Preset::Type type) -> std::string { + if (parent_name.empty()) + return std::string(); + std::string error; + PresetBundle *bundle = ensure_cli_preset_bundle(error); + if (bundle == nullptr) { + BOOST_LOG_TRIVIAL(warning) << "CLI: no preset bundle for different_settings_to_system: " << error; + return std::string(); + } + const PresetCollection *collection = nullptr; + switch (type) { + case Preset::TYPE_PRINT: collection = &bundle->prints; break; + case Preset::TYPE_FILAMENT: collection = &bundle->filaments; break; + case Preset::TYPE_PRINTER: collection = &bundle->printers; break; + default: return std::string(); + } + const Preset *parent = collection->find_preset2(parent_name, true); + if (parent == nullptr) { + BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: parent preset '%1%' not found; leaving different_settings_to_system empty")%parent_name; + return std::string(); + } + std::vector keys = resolved.diff(parent->config); + //ORCA: preset metadata, not user-tunable settings. compatible_printers / + // compatible_prints have their own tracking columns and would double-count. + keys.erase(std::remove_if(keys.begin(), keys.end(), [](const std::string &k) { + return k == "inherits" || k == "compatible_printers" || k == "compatible_prints" + || k == "compatible_printers_condition" || k == "compatible_prints_condition" + || k == "print_settings_id" || k == "filament_settings_id" || k == "printer_settings_id"; + }), + keys.end()); + BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% overrides vs parent '%2%'")%keys.size()%parent_name; + return Slic3r::escape_strings_cstyle(keys); + }; + auto load_config_file = [&resolve_preset](const std::string& file, DynamicPrintConfig& config, std::string& config_type, std::string& config_name, std::string& filament_id, std::string& config_from) { if (! boost::filesystem::exists(file)) { @@ -2635,6 +2709,8 @@ int CLI::run(int argc, char **argv) flush_and_exit(ret); } upward_compatible_printers = config.option("upward_compatible_machine", true)->values; + //ORCA: keep the full config so compatible_printers_condition can be evaluated against it below + current_printer_full_config = std::move(config); } } } @@ -2657,6 +2733,8 @@ int CLI::run(int argc, char **argv) flush_and_exit(ret); } current_print_compatible_printers = config.option("compatible_printers", true)->values; + //ORCA: keep the full config so compatible_printers_condition can be evaluated against it below + current_process_full_config = std::move(config); } } } @@ -2675,46 +2753,88 @@ int CLI::run(int argc, char **argv) for (int index = 0; index < upward_compatible_printers.size(); index++) { BOOST_LOG_TRIVIAL(info) << boost::format("index %1%, upward_compatible_printers %2%")%index %upward_compatible_printers[index]; } + //ORCA: Replace the four manual equality-loop checks below with is_compatible_with_printer(), the + // same helper the GUI uses, which also evaluates compatible_printers_condition. Process + // profiles that declare compatibility via condition only -- leaving compatible_printers + // empty -- were always reported incompatible by the literal-name match, so a CLI slice with + // such a preset exited with CLI_PROCESS_NOT_COMPATIBLE (-17) even though the GUI accepts the + // same pair. Behaviour is unchanged where an explicit list exists: is_compatible_with_printer + // does the same name match, and returns true when both list and condition are empty (which + // matches the "old 3mf, no compatible printers" path below). + auto check_compat = [](const DynamicPrintConfig &process_cfg, + const DynamicPrintConfig &printer_cfg, + const std::string &printer_name) -> bool { + return is_compatible_with_printer(process_cfg, Preset::TYPE_PRINT, printer_cfg, printer_name); + }; + + //ORCA: a 3MF's project config does not carry compatible_printers / compatible_printers_condition. + // PresetBundle::construct_full_config() erases both and re-emits them as + // print_compatible_printers and compatible_machine_expression_group; they are renamed back + // only on the PresetBundle load path, which the CLI does not take. Feeding the project config + // to the check as-is therefore presents no list and no condition, and + // is_compatible_with_printer() reads that as "no constraint" and accepts every printer. + // Translate the two keys back. Index 0 of the expression group is the print preset -- the + // group is filled print, filaments, printer (PresetBundle.cpp). + // The raw keys win whenever they carry something. A project the CLI exported itself has the + // real compatible_printers_condition AND an all-empty compatible_machine_expression_group, + // so copying the group's first entry unconditionally would overwrite a valid condition with + // "" and accept every printer. The renamed keys are only a fallback, and an empty value is + // never written over a real one. + auto cli_process_compat_config = [](const DynamicPrintConfig &project_cfg) -> DynamicPrintConfig { + DynamicPrintConfig cfg = project_cfg; + const auto *raw_list = project_cfg.option("compatible_printers"); + const auto *list = project_cfg.option("print_compatible_printers"); + if ((raw_list == nullptr || raw_list->values.empty()) && list != nullptr && !list->values.empty()) + cfg.set_key_value("compatible_printers", new ConfigOptionStrings(list->values)); + const auto *raw_cond = project_cfg.option("compatible_printers_condition"); + const auto *group = project_cfg.option("compatible_machine_expression_group"); + if ((raw_cond == nullptr || raw_cond->value.empty()) && group != nullptr && !group->values.empty() && + !group->values.front().empty()) + cfg.set_key_value("compatible_printers_condition", new ConfigOptionString(group->values.front())); + return cfg; + }; if (!new_printer_name.empty()) { if (!new_process_name.empty()) { - for (int index = 0; index < new_print_compatible_printers.size(); index++) { - if (new_print_compatible_printers[index] == new_printer_system_name) { - process_compatible = true; - break; - } - } + //new process + new printer: both configs came from --load-settings + process_compatible = check_compat(load_process_config, load_machine_config, new_printer_system_name); BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%") %new_printer_name %new_printer_system_name %new_process_name %new_process_system_name %process_compatible; } else { - for (int index = 0; index < current_print_compatible_printers.size(); index++) { - if (current_print_compatible_printers[index] == new_printer_system_name) { - process_compatible = true; - break; - } + //3MF-embedded process vs new printer. current_process_full_config is only populated from + //profiles/BBL/process_full/, so for every other vendor fall back to the 3MF's own project + //config in m_print_config, with its renamed compatibility keys translated back (see + //cli_process_compat_config above). Without this a 3MF built from a condition-only process + //is rejected when re-sliced with the very printer it was made for. + { + //ORCA: profiles/BBL/{process,machine}_full/ are gitignored and not generated in-tree, + // so current_*_full_config is always empty and this fallback is the only live path. + const DynamicPrintConfig process_cfg = current_process_full_config.empty() + ? cli_process_compat_config(m_print_config) + : current_process_full_config; + process_compatible = check_compat(process_cfg, load_machine_config, new_printer_system_name); } BOOST_LOG_TRIVIAL(info) << boost::format("new printer %1%, inherited from %2%, old process %3%, inherited from %4% ,compatible %5%") %new_printer_name %new_printer_system_name %current_process_name %current_process_system_name %process_compatible; } } else if (!new_process_name.empty()) { - for (int index = 0; index < new_print_compatible_printers.size(); index++) { - if (new_print_compatible_printers[index] == current_printer_system_name) { - process_compatible = true; - break; - } + //new process vs 3MF-embedded printer. As above, current_printer_full_config only resolves for + //BBL profiles; otherwise evaluate against the 3MF's own project config in m_print_config, which + //holds the embedded printer's printer_notes / nozzle_diameter. + { + const DynamicPrintConfig &printer_cfg = current_printer_full_config.empty() ? m_print_config : current_printer_full_config; + process_compatible = check_compat(load_process_config, printer_cfg, current_printer_system_name); } BOOST_LOG_TRIVIAL(info) << boost::format("old printer %1%, inherited from %2%, new process %3%, inherited from %4% ,compatible %5%") %current_printer_name %current_printer_system_name %new_process_name %new_process_system_name %process_compatible; } else { - //check the compatible of old printer&&process - for (int index = 0; index < current_print_compatible_printers.size(); index++) { - if (current_print_compatible_printers[index] == current_printer_system_name) { - process_compatible = true; - break; - } - } + //both sides 3MF-embedded (pure reprocess) + if (!current_process_full_config.empty() && !current_printer_full_config.empty()) + process_compatible = check_compat(current_process_full_config, current_printer_full_config, current_printer_system_name); + else + process_compatible = std::find(current_print_compatible_printers.begin(), current_print_compatible_printers.end(), current_printer_system_name) != current_print_compatible_printers.end(); if (!process_compatible && current_print_compatible_printers.empty()) { BOOST_LOG_TRIVIAL(info) << boost::format("old 3mf, no compatible printers, set to compatible"); @@ -2937,8 +3057,10 @@ int CLI::run(int argc, char **argv) } } else { - //todo: support user machine preset's different settings - different_settings[filament_count+1] = ""; + //ORCA: was a //todo — compute the user's overrides instead of writing an empty column. + different_settings[filament_count+1] = new_printer_config_is_system + ? std::string() + : cli_different_settings(load_machine_config, new_printer_system_name, Preset::TYPE_PRINTER); if (new_printer_config_is_system) inherits_group[filament_count+1] = ""; else @@ -3080,8 +3202,14 @@ int CLI::run(int argc, char **argv) print_compatible_printers = std::move(current_print_compatible_printers); } else { - //todo: support system process preset - different_settings[0] = ""; + //ORCA: was a //todo. Prefer a value the loaded JSON already carried, otherwise + // compute the overrides against the system parent. + if (!different_process_setting.empty()) + different_settings[0] = different_process_setting; + else + different_settings[0] = new_process_config_is_system + ? std::string() + : cli_different_settings(load_process_config, new_process_system_name, Preset::TYPE_PRINT); if (new_process_config_is_system) inherits_group[0] = ""; else @@ -3268,6 +3396,16 @@ int CLI::run(int argc, char **argv) int filament_index = load_filaments_index[index]; std::vector different_keys; + //ORCA: diff before load_default_gcodes_to_config, the way the process and machine + // slots above already do. That call materialises absent gcode keys via + // option(..., true), and DynamicConfig::diff only compares keys present in + // both configs -- so a gcode key the leaf did not carry would go from "not + // compared" to "compared as empty against the parent" and land in the column + // as an override the user never made. + std::string filament_different_settings; + if (load_filament_count > 0) + filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT); + load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT); if (load_filament_count > 0) { @@ -3279,8 +3417,8 @@ int CLI::run(int argc, char **argv) opt_filament_settings->set_at(filament_name_setting, filament_index-1, 0); config.erase("filament_settings_id"); - //todo: update different settings of filaments - different_settings[filament_index] = ""; + //ORCA: was a //todo — same treatment as process/machine above. + different_settings[filament_index] = filament_different_settings; inherits_group[filament_index] = load_filaments_inherit[index]; } else { @@ -3585,6 +3723,15 @@ int CLI::run(int argc, char **argv) } } + // A mixed slot never reaches a nozzle, so its row and column stay empty, as in the GUI. + // Command line options are not merged into m_print_config yet, so they win here. + const ConfigOptionBools *is_mixed_opt = m_extra_config.option("filament_is_mixed"); + if (!is_mixed_opt) + is_mixed_opt = m_print_config.option("filament_is_mixed"); + auto is_mixed_slot = [is_mixed_opt](int idx) { + return is_mixed_opt && idx < static_cast(is_mixed_opt->values.size()) && is_mixed_opt->values[idx]; + }; + for (size_t nozzle_id = 0; nozzle_id < new_extruder_count; ++nozzle_id) { std::vector flush_vol_mtx = get_flush_volumes_matrix(flush_vol_matrix, nozzle_id, new_extruder_count); for (int from_idx = 0; from_idx < project_filament_count; from_idx++) { @@ -3594,7 +3741,7 @@ int CLI::run(int argc, char **argv) bool is_from_support = filament_is_support->get_at(from_idx); for (int to_idx = 0; to_idx < project_filament_count; to_idx++) { bool is_to_support = filament_is_support->get_at(to_idx); - if (from_idx == to_idx) { + if (from_idx == to_idx || is_mixed_slot(from_idx) || is_mixed_slot(to_idx)) { flush_vol_mtx[project_filament_count * from_idx + to_idx] = 0.f; } else { int flushing_volume = 0; @@ -3731,12 +3878,113 @@ int CLI::run(int argc, char **argv) } } + //ORCA: settings passed on the command line (--sparse-infill-density 25% ...) override the loaded + // presets right here, so they belong in different_settings_to_system just as a preset + // override does. Without them re-opening the exported project in the GUI shows nothing + // modified and reverts those values to the system presets'. + // + // The keys come from m_config, not m_extra_config: read_cli() puts only what the user typed + // into m_config (setup() adds nothing but CLI-own defaults), whereas the CLI writes its own + // values into m_extra_config. Only keys whose value the override actually changed are + // recorded -- a typed value equal to the loaded one modifies nothing -- and each lands in + // the column(s) whose preset type owns it: [0] process, [1..n-2] filaments, [n-1] printer. + // + // "Changed" is judged the way the value is read: a list is compared entry by entry with a + // missing entry read as the first, as get_at() does -- so --nozzle-temperature 245 against + // 245,245,245 is no change, although the two serialize differently. + // + // A key the loaded config does not carry at all is always recorded, even if the typed value + // equals the built-in default. On reopen the GUI restores an unlisted key from the SYSTEM + // preset, which need not match that default: a 3MF written before an option existed leaves + // it absent here, and --sparse-infill-density 20% (the default) against a Prusa system 15% + // would otherwise go unrecorded and be reverted. Over-recording is cosmetic; under-recording + // loses the value. + std::map> cli_override_before; + for (const std::string &key : m_config.keys()) { + if (!m_extra_config.has(key)) + continue; + const ConfigOption *loaded = m_print_config.option(key); + cli_override_before[key].reset(loaded != nullptr ? loaded->clone() : nullptr); // null: always recorded + } + // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) m_print_config.apply(m_extra_config, true); + + if (!cli_override_before.empty()) { + std::vector &columns = m_print_config.option("different_settings_to_system", true)->values; + auto owned_by = [](const std::vector &options, const std::string &key) { + return std::find(options.begin(), options.end(), key) != options.end(); + }; + auto add_to_column = [&columns](size_t index, const std::string &key) { + std::vector keys; + Slic3r::unescape_strings_cstyle(columns[index], keys); + if (std::find(keys.begin(), keys.end(), key) == keys.end()) { + keys.push_back(key); + columns[index] = Slic3r::escape_strings_cstyle(keys); + } + }; + auto same_value = [](const ConfigOption *a, const ConfigOption *b) { + if (a == nullptr || b == nullptr) + return false; + const auto *va = dynamic_cast(a); + const auto *vb = dynamic_cast(b); + if (va == nullptr || vb == nullptr) + return va == vb && a->serialize() == b->serialize(); + const std::vector ea = va->vserialize(), eb = vb->vserialize(); + if (ea.empty() || eb.empty()) + return ea.empty() && eb.empty(); + for (size_t i = 0; i < std::max(ea.size(), eb.size()); ++i) + if (ea[i < ea.size() ? i : 0] != eb[i < eb.size() ? i : 0]) + return false; + return true; + }; + //ORCA: always true after the resize to filament_count + 2 above, and nothing in between can + // shrink the column vector -- different_settings_to_system is not a CLI option. Kept as + // a check rather than an assert: release builds compile asserts out, so an assert would + // protect nothing, while a build with _GLIBCXX_ASSERTIONS would abort on columns[0]. + if (columns.size() >= 2) { + for (const auto &[key, before] : cli_override_before) { + if (same_value(before.get(), m_print_config.option(key))) + continue; + bool recorded = false; + if (owned_by(Preset::print_options(), key)) { + add_to_column(0, key); + recorded = true; + } + if (owned_by(Preset::filament_options(), key)) { + for (size_t i = 1; i + 1 < columns.size(); ++i) + add_to_column(i, key); + recorded = true; + } + if (owned_by(Preset::printer_options(), key)) { + add_to_column(columns.size() - 1, key); + recorded = true; + } + if (recorded) + BOOST_LOG_TRIVIAL(info) << boost::format("CLI: override %1% recorded in different_settings_to_system") % key; + } + } + } // Normalizing after importing the 3MFs / AMFs m_print_config.normalize_fdm(); + // A mixed slot is virtual but still needs a filament entry of its own. Without one, feature + // filament ids aimed at it fall outside the filament count, are reset to the first filament + // and the model silently prints in a single colour. + if (const auto *is_mixed_opt = m_print_config.option("filament_is_mixed")) { + const auto &is_mixed = is_mixed_opt->values; + for (size_t slot = static_cast(std::max(filament_count, 0)); slot < is_mixed.size(); ++slot) { + if (!is_mixed[slot]) + continue; + BOOST_LOG_TRIVIAL(error) << boost::format("mixed filament slot %1% has no filament of its own, only %2% filaments are loaded; " + "load one filament per slot, including each mixed one") + % (slot + 1) % filament_count; + record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, 0, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info); + flush_and_exit(CLI_MIXED_FILAMENT_INVALID); + } + } + m_print_config.option>("printer_technology", true)->value = printer_technology; bool has_wipe_tower_position = m_print_config.option("wipe_tower_x") && m_print_config.option("wipe_tower_y"); @@ -3791,6 +4039,15 @@ int CLI::run(int argc, char **argv) bool is_smooth_timelapse = false; if (enable_timelapse && timelapse_type_opt && (timelapse_type_opt->getInt() == TimelapseType::tlSmooth)) is_smooth_timelapse = true; + // A mixed filament swaps between its components every layer, so it needs the tower even when + // every loaded preset is the same. + if (disable_wipe_tower_after_mapping) { + if (const auto *is_mixed_opt = m_print_config.option("filament_is_mixed"); + is_mixed_opt && has_any_mixed_filament(is_mixed_opt->values)) { + disable_wipe_tower_after_mapping = false; + BOOST_LOG_TRIVIAL(info) << boost::format("%1%, set disable_wipe_tower_after_mapping back to false due to a mixed filament")%__LINE__; + } + } if (disable_wipe_tower_after_mapping) { if (is_smooth_timelapse) { @@ -5112,7 +5369,7 @@ int CLI::run(int argc, char **argv) //skip this object due to be locked in plate ap.itemid = locked_aps.size(); locked_aps.emplace_back(ap); - boost::nowide::cout <<__FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx; } } } @@ -5701,7 +5958,11 @@ int CLI::run(int argc, char **argv) //FIXME check for mixing the FFF / SLA parameters. // or better save fff_print_config vs. sla_print_config //m_print_config.save(m_config.opt_string("save")); - m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION)); + const std::string &settings_file = m_config.opt_string(opt_key); + if (settings_file == "-") + m_print_config.save_to_json(boost::nowide::cout, "project_settings", "project", SoftFever_VERSION, /*replace_invalid_utf8=*/true); + else + m_print_config.save_to_json(settings_file, std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION)); } else if (opt_key == "info") { // --info works on unrepaired model for (Model &model : m_models) { @@ -5997,6 +6258,36 @@ int CLI::run(int argc, char **argv) flush_and_exit(CLI_ONLY_ONE_TPU_SUPPORTED); } + // Same type gate as the GUI's Sidebar::has_broken_mixed_filament: refuse a plate that uses a + // mixed slot whose components are different filament types. Missing or out-of-range + // components never get here, validate() already rejects them for the whole project. + const auto *is_mixed_opt = m_print_config.option("filament_is_mixed"); + const auto *components_opt = m_print_config.option("filament_mixed_components"); + if (is_mixed_opt && components_opt && has_any_mixed_filament(is_mixed_opt->values)) { + const auto &is_mixed = is_mixed_opt->values; + const auto &components = components_opt->values; + const size_t num_physical = static_cast(filament_count) - static_cast(std::count(is_mixed.begin(), is_mixed.end(), true)); + std::vector physical_types(num_physical); + for (size_t f_index = 0; f_index < num_physical; ++f_index) { + std::string displayed_type; + physical_types[f_index] = m_print_config.get_filament_type(displayed_type, static_cast(f_index)); + if (physical_types[f_index].empty()) + physical_types[f_index] = "PLA"; + } + const std::vector mismatched_slots = check_mixed_filament_type_consistency(is_mixed, components, physical_types); + // plate_filaments has mixed slots expanded to their components; the gate needs the slots. + const std::vector plate_slots = mismatched_slots.empty() ? std::vector() : + part_plate->get_extruders_under_cli(true, m_print_config, false); + for (size_t slot : mismatched_slots) { + if (std::find(plate_slots.begin(), plate_slots.end(), static_cast(slot) + 1) == plate_slots.end()) + continue; + BOOST_LOG_TRIVIAL(error) << boost::format("plate %1%: mixed filament %2% mixes components of different filament types") + % (index + 1) % (slot + 1); + record_exit_reson(outfile_dir, CLI_MIXED_FILAMENT_INVALID, index + 1, cli_errors[CLI_MIXED_FILAMENT_INVALID], sliced_info); + flush_and_exit(CLI_MIXED_FILAMENT_INVALID); + } + } + if (new_extruder_count > 1) { std::vector> unprintable_filament_vec; for (const std::set& filamnt_ids : unprintable_filament_ids) { @@ -7449,6 +7740,13 @@ bool CLI::setup(int argc, char **argv) this->print_help(); return false; } + + // Orca: resolve here, while the process is still in the directory the user invoked it from. + // GUI_App's constructor moves the working directory to /log, long before the GUI + // opens these files in post_init(), and a relative path would then resolve against that. + for (std::string &input_file : m_input_files) + input_file = resolve_cli_input_path(input_file); + // Parse actions and transform options. for (auto const &opt_key : opt_order) { if (cli_actions_config_def.has(opt_key)) diff --git a/src/dev-utils/BaseException.h b/src/dev-utils/BaseException.h index 2cb65d945e..20b6fb0c89 100644 --- a/src/dev-utils/BaseException.h +++ b/src/dev-utils/BaseException.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "stackwalker.h" +#include "StackWalker.h" #include class CBaseException : public CStackWalker diff --git a/src/dev-utils/StackWalker.cpp b/src/dev-utils/StackWalker.cpp index 6038196cb0..3ef983cd86 100644 --- a/src/dev-utils/StackWalker.cpp +++ b/src/dev-utils/StackWalker.cpp @@ -425,7 +425,7 @@ LPSTACKINFO CStackWalker::StackWalker(HANDLE hThread, const CONTEXT* context) else c = *context; - STACKFRAME64 sf = {0}; + STACKFRAME64 sf = {}; DWORD imageType; //intel X86 diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e860f50280..ffc6b5cee6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -558,6 +558,12 @@ if (_opts) target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}") endif() +if (IS_CLANG_CL) + # CGAL passes /fp:strict /fp:except-. clang-cl reports the second as overriding part of + # the first; the settings cc1 receives are the same ones MSVC produces from that pair. + target_compile_options(libslic3r_cgal PRIVATE -Wno-overriding-option) +endif () + target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} admesh libigl mcut boost_libs) if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 394cfb5b74..52a46dcacf 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1515,6 +1516,19 @@ std::optional parse_capability_ref(const std::string& value //BBS: add json support void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const +{ + // Serialize first: if that throws (invalid UTF-8), the existing file stays untouched. + std::ostringstream ss; + this->save_to_json(ss, name, from, version); + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << ss.str(); + c.close(); + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; +} + +void ConfigBase::save_to_json(std::ostream &os, const std::string &name, const std::string &from, const std::string &version, bool replace_invalid_utf8) const { json j; //record the headers @@ -1561,12 +1575,7 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name, j["plugins"] = unique_refs; } - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << j.dump(1, '\t') << std::endl; - c.close(); - - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; + os << j.dump(1, '\t', false, replace_invalid_utf8 ? json::error_handler_t::replace : json::error_handler_t::strict) << std::endl; } void ConfigBase::save(const std::string &file) const diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 9e4344820d..6d23ec3770 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1006,6 +1006,7 @@ public: int getInt() const override { return this->value; } void setInt(int val) override { this->value = val; } ConfigOption* clone() const override { return new ConfigOptionInt(*this); } + using ConfigOptionSingle::operator==; bool operator==(const ConfigOptionInt &rhs) const throw() { return this->value == rhs.value; } std::string serialize() const override @@ -1048,6 +1049,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); } ConfigOptionIntsTempl& operator= (const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionVector::operator==; bool operator==(const ConfigOptionIntsTempl &rhs) const throw() { return this->values == rhs.values; } bool operator< (const ConfigOptionIntsTempl &rhs) const throw() { return this->values < rhs.values; } // Could a special "nil" value be stored inside the vector, indicating undefined value? @@ -1137,6 +1139,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionString(*this); } ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionSingle::operator==; bool operator==(const ConfigOptionString &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionString &rhs) const throw() { return this->value < rhs.value; } bool empty() const { return this->value.empty(); } @@ -1171,6 +1174,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionVector::operator==; bool operator==(const ConfigOptionStrings &rhs) const throw() { return this->values == rhs.values; } bool operator< (const ConfigOptionStrings &rhs) const throw() { return this->values < rhs.values; } bool is_nil(size_t) const override { return false; } @@ -1215,6 +1219,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } ConfigOptionPercent& operator= (const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionFloat::operator==; bool operator==(const ConfigOptionPercent &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionPercent &rhs) const throw() { return this->value < rhs.value; } @@ -1257,6 +1262,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercentsTempl(*this); } ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionFloatsTempl::operator==; bool operator==(const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_equal(this->values, rhs.values); } bool operator< (const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_lower(this->values, rhs.values); } @@ -1502,6 +1508,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionSingle::operator==; bool operator==(const ConfigOptionPoint &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionPoint &rhs) const throw() { return this->value < rhs.value; } @@ -1539,6 +1546,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator= (const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionVector::operator==; bool operator==(const ConfigOptionPoints &rhs) const throw() { return this->values == rhs.values; } bool operator< (const ConfigOptionPoints &rhs) const throw() { return std::lexicographical_compare(this->values.begin(), this->values.end(), rhs.values.begin(), rhs.values.end(), [](const auto &l, const auto &r){ return l < r; }); } @@ -1617,6 +1625,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); } ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionSingle::operator==; bool operator==(const ConfigOptionPoint3 &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionPoint3 &rhs) const throw() { return this->value.x() < rhs.value.x() || (this->value.x() == rhs.value.x() && (this->value.y() < rhs.value.y() || (this->value.y() == rhs.value.y() && this->value.z() < rhs.value.z()))); } @@ -1860,6 +1869,7 @@ public: bool getBool() const override { return this->value; } ConfigOption* clone() const override { return new ConfigOptionBool(*this); } ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionSingle::operator==; bool operator==(const ConfigOptionBool &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionBool &rhs) const throw() { return int(this->value) < int(rhs.value); } @@ -1911,6 +1921,7 @@ public: ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); } ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + using ConfigOptionVector::operator==; bool operator==(const ConfigOptionBoolsTempl &rhs) const throw() { return this->values == rhs.values; } bool operator< (const ConfigOptionBoolsTempl &rhs) const throw() { return this->values < rhs.values; } // Could a special "nil" value be stored inside the vector, indicating undefined value? @@ -2163,6 +2174,7 @@ public: ConfigOptionEnumsGenericTempl& operator= (const ConfigOption* opt) { this->set(opt); return *this; } bool operator< (const ConfigOptionInts& rhs) const throw() { return this->values < rhs.values; } + using ConfigOptionInts::operator==; bool operator==(const ConfigOptionInts& rhs) const { if (rhs.type() != this->type()) @@ -2813,6 +2825,9 @@ public: //BBS: add json support void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const; + // Same document, written to a stream. Invalid UTF-8 in a string value throws nlohmann's type_error unless + // replace_invalid_utf8 is set, which writes U+FFFD instead (for callers such as stdout with no handler). + void save_to_json(std::ostream &os, const std::string &name, const std::string &from, const std::string &version, bool replace_invalid_utf8 = false) const; // Rebuild the in-memory "plugins" manifest (the "name;uuid;capability" references the plugin // dispatchers consume) from the plugin-backed options via the registered resolver. save_to_json() diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index ef144b48d3..34d9a93590 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -968,10 +968,10 @@ EmbossStyles Emboss::get_font_list_by_register() { } // TODO: Fix global function -bool CALLBACK EnumFamCallBack(LPLOGFONT lplf, - LPNEWTEXTMETRIC lpntm, - DWORD FontType, - LPVOID aFontList) +int CALLBACK EnumFamCallBack(const LOGFONT *lplf, + const TEXTMETRIC *lpntm, + DWORD FontType, + LPARAM aFontList) { std::vector *fontList = (std::vector *) (aFontList); @@ -988,7 +988,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() { HDC hDC = GetDC(NULL); std::vector font_names; - EnumFontFamilies(hDC, (LPCTSTR) NULL, (FONTENUMPROC) EnumFamCallBack, + EnumFontFamilies(hDC, (LPCTSTR) NULL, EnumFamCallBack, (LPARAM) &font_names); EmbossStyles font_list; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index dc772580ca..28fabed8af 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -11,7 +11,7 @@ #include "AABBTreeLines.hpp" #include "ExtrusionEntity.hpp" -#include "FillBase.hpp" +#include "Fill.hpp" #include "FillRectilinear.hpp" #include "FillLightning.hpp" #include "FillConcentricInternal.hpp" @@ -1234,6 +1234,33 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p return surface_fills; } +// Orca: Anchors and printed infill must share the same body origin. Keep the choice +// here so per-model surface centering and separated sparse infill cannot drift apart. +static BoundingBox infill_bounding_box(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly, BoundingBox bbox) +{ + const auto ¶ms = fill.params; + const auto &config = layer.regions()[fill.region_id]->region().config(); + const bool external = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface; + const bool per_model = external && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model && + (params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral); + const bool separate = !external && params.separated_infills && + (is_separable_infill_pattern(params.pattern) || !config.solid_infill_rotate_template.value.empty() || + !config.sparse_infill_rotate_template.value.empty()); + if (per_model || separate) { + double best_overlap = 0.; + for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_bboxes.size(); ++i) { + const double overlap = area(intersection_ex(layer.lslices[i], expoly)); + if (overlap > best_overlap) { + best_overlap = overlap; + const Point center = layer.lslices_separated_component_bboxes[i].center(); + bbox = layer.object()->bounding_box(); + bbox.translate(center.x(), center.y()); + } + } + } + return bbox; +} + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING void export_group_fills_to_svg(const char *path, const std::vector &fills) { @@ -1353,19 +1380,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Orca: Checking the filling of a centered surface by drawing for each model parts bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface; - bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral; if (is_top_or_bottom) { params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern } - // Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly - // fall through to the default (whole-object) bounding box below. - bool is_per_model_center = is_top_or_bottom && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model && is_centered_infill; - bool is_separate_infill = !is_top_or_bottom && surface_fill.params.separated_infills && - ( - is_separable_infill_pattern(surface_fill.params.pattern) || - params.config->solid_infill_rotate_template != "" || - params.config->sparse_infill_rotate_template != "" ); - if( surface_fill.params.pattern == ipLockedZag ) { params.locked_zag = true; params.infill_lock_depth = surface_fill.params.infill_lock_depth; @@ -1389,34 +1406,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.can_reverse = false; for (ExPolygon& expoly : surface_fill.expolygons) { - // Orca: separate infill / per-model pattern centering. - // - // Center the pattern on each connected body of the object independently, so every piece - // is filled exactly as if it were sliced on its own: touching/overlapping parts merge - // into one body sharing a center, while separate parts and disconnected islands (even - // interleaved-but-not-touching ones, e.g. chain links) each get their own. The body each - // island belongs to, and its full bounding box, were resolved in 3D by PrintObject:: - // infill() (lslices_separated_component_bboxes, aligned with this layer's lslices). We - // match this fill region to the island it overlaps most, then re-use the whole-object - // bounding box (origin-centered — identical extent to the default, so coverage and cost - // are unchanged) re-centered on that body. - if (is_per_model_center || is_separate_infill) { - double best_overlap = 0.; - BoundingBox best_component; - for (size_t r = 0; r < this->lslices.size() && r < this->lslices_separated_component_bboxes.size(); ++ r) { - const double overlap = area(intersection_ex(this->lslices[r], expoly)); - if (overlap > best_overlap) { - best_overlap = overlap; - best_component = this->lslices_separated_component_bboxes[r]; - } - } - if (best_component.defined) { - const Point c = best_component.center(); - BoundingBox part_bbox = bbox; // origin-centered, whole-object extent (from above) - part_bbox.translate(c.x(), c.y()); // re-center on this body - f->set_bounding_box(part_bbox); - } - } // - End: separate infill / per-model pattern centering + // Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface. + f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox)); f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); if (params.symmetric_infill_y_axis) { @@ -1583,8 +1574,14 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc params.multiline = surface_fill.params.multiline; params.gyroid_optimized = surface_fill.params.gyroid_optimized; params.smooth_factor = surface_fill.params.smooth_factor; + // Orca: Match make_fills() when choosing the origin of plane-path patterns. + // Without the sparse extrusion role, the filler uses each surface's bounds + // instead of the object's bounds, so bridge anchors shift away from printed infill. + params.extrusion_role = surface_fill.params.extrusion_role; for (ExPolygon &expoly : surface_fill.expolygons) { + // Orca: Match the per-body origin of make_fills() before generating physical anchors. + f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox)); // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. f->spacing = surface_fill.params.spacing; surface_fill.surface.expolygon = std::move(expoly); @@ -1598,6 +1595,25 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc return sparse_infill_polylines; } +// Returns the filament id (1-based) the region is ironed with, or -1 when the +// region is not ironed. AllSolid always irons. TopSurfaces and TopmostOnly need +// either some top shells or, in spiral mode, more than one bottom shell, and +// TopmostOnly additionally needs the layer to be the topmost one. +int Layer::choose_ironing_extruder(const PrintRegionConfig &cfg, + bool spiral_mode, + bool is_topmost_layer) +{ + if (cfg.ironing_type == IroningType::NoIroning) + return -1; + const bool gate = (cfg.ironing_type == IroningType::AllSolid) + || ((cfg.top_shell_layers > 0 || (spiral_mode && cfg.bottom_shell_layers > 1)) + && (cfg.ironing_type == IroningType::TopSurfaces + || (cfg.ironing_type == IroningType::TopmostOnly && is_topmost_layer))); + if (!gate) + return -1; + return cfg.top_surface_filament_id; +} + // Create ironing extrusions over top surfaces. void Layer::make_ironing() { @@ -1667,19 +1683,10 @@ void Layer::make_ironing() if (! layerm->slices.empty()) { IroningParams ironing_params; const PrintRegionConfig &config = layerm->region().config(); - if (config.ironing_type != IroningType::NoIroning && - (config.ironing_type == IroningType::AllSolid || - ((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) && - (config.ironing_type == IroningType::TopSurfaces || - (config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) { - if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) { - // Iron the whole face. - ironing_params.extruder = config.top_surface_filament_id; - } else { - // Iron just the infill. - ironing_params.extruder = config.top_surface_filament_id; - } - } + ironing_params.extruder = Layer::choose_ironing_extruder( + config, + /*spiral_mode=*/this->object()->print()->config().spiral_mode, + /*is_topmost_layer=*/layerm->layer()->upper_layer == nullptr); if (ironing_params.extruder != -1) { //TODO just_infill is currently not used. ironing_params.just_infill = false; diff --git a/src/libslic3r/Fill/Fill.hpp b/src/libslic3r/Fill/Fill.hpp index e92ab2dee5..b183cf0253 100644 --- a/src/libslic3r/Fill/Fill.hpp +++ b/src/libslic3r/Fill/Fill.hpp @@ -14,6 +14,12 @@ namespace Slic3r { class ExtrusionEntityCollection; class LayerRegion; +class PrintObject; + +// Orca: Share the layer rotation calculation between infill generation and internal +// bridge angle selection so both interpret rotation templates in the same way. +double calculate_infill_rotation_angle(const PrintObject *object, size_t layer_id, + const double &fixed_infill_angle, const std::string &template_string); // An interface class to Perl, aggregating an instance of a Fill and a FillData. class Filler diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 344bb529f0..dbaa1f2ac9 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1395,8 +1395,8 @@ void Filler::_fill_surface_single( } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - const auto hook_length = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length))); - const auto hook_length_max = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length_max))); + const auto hook_length = coordf_t(scale_(params.anchor_length)); + const auto hook_length_max = coordf_t(scale_(params.anchor_length_max)); Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length, hook_length_max) : std::move(all_polylines); diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 96d98dbe9f..e2091da1db 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -8844,7 +8844,7 @@ public: auto model = object.get_model(); auto o = m_temp_model.add_object(object); int backup_id = model->get_object_backup_id(object); - push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, 1 }); + push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, { 1 } }); } void remove_object_mesh(ModelObject& object) { @@ -8854,7 +8854,7 @@ public: void backup_soon() { boost::lock_guard lock(m_mutex); m_other_changes_backup = true; - m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq }); + m_tasks.push_back({ Backup, 0, std::string(), nullptr, { ++m_task_seq } }); m_cond.notify_all(); } @@ -8872,7 +8872,7 @@ public: m_ui_tasks.clear(); m_tasks.clear(); } - m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, removeAll }); + m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, { removeAll } }); ++m_task_seq; if (model.is_need_backup()) { m_other_changes = false; @@ -9087,7 +9087,7 @@ public: else m_cond.wait(lock); if (m_interval > 0 && boost::get_system_time() > m_next_backup) { - m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq }); + m_tasks.push_back({ Backup, 0, std::string(), nullptr, { ++m_task_seq } }); m_next_backup += boost::posix_time::seconds(m_interval); // Maybe wakeup from power sleep if (m_next_backup < boost::get_system_time()) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e9cdb620e0..4aa45a60ed 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6308,8 +6308,13 @@ LayerResult GCode::process_layer( all_label_ids.insert(inst.label_object_id); break; } - std::vector filament_instances_id(all_label_ids.begin(), all_label_ids.end()); - m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id); + // Orca: A scheduled extruder may have no object instances on this layer. + // Clear any pending mask so it cannot be emitted for the wrong toolchange. + m_filament_instances_code.clear(); + if (!all_label_ids.empty()) { + std::vector filament_instances_id(all_label_ids.begin(), all_label_ids.end()); + m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id); + } } // The inline _extrude hook may already have taken the snapshot mid-extrusion on a diff --git a/src/libslic3r/GCode/ToolOrderUtils.cpp b/src/libslic3r/GCode/ToolOrderUtils.cpp index 4e2934d967..4a67477d24 100644 --- a/src/libslic3r/GCode/ToolOrderUtils.cpp +++ b/src/libslic3r/GCode/ToolOrderUtils.cpp @@ -910,7 +910,7 @@ namespace Slic3r unsigned int iterations = (1 << all_extruders.size()); unsigned int final_state = iterations - 1; - std::vector>cache(iterations, std::vector(all_extruders.size(), 0x7fffffff)); + std::vector>cache(iterations, std::vector(all_extruders.size(), std::numeric_limits::max())); std::vector>prev(iterations, std::vector(all_extruders.size(), -1)); cache[1][0] = 0.; for (unsigned int state = 0; state < iterations; ++state) { diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 1d6c2b0703..b7ec08f856 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -187,6 +187,12 @@ void Layer::make_perimeters() { BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); + const auto clear_generated_extrusions = [](LayerRegion *layer_region) { + layer_region->perimeters.clear(); + layer_region->fills.clear(); + layer_region->thin_fills.clear(); + }; + // keep track of regions whose perimeters we have already generated std::vector done(m_regions.size(), false); @@ -217,13 +223,11 @@ void Layer::make_perimeters() if (this_region.gradient_volume_id() != other_region.gradient_volume_id()) continue; if (is_perimeter_compatible(*m_object->print(), this_region, other_region)) - { - other_layerm->perimeters.clear(); - other_layerm->fills.clear(); - other_layerm->thin_fills.clear(); - layerms.push_back(other_layerm); - done[it - m_regions.begin()] = true; - } + { + clear_generated_extrusions(other_layerm); + layerms.push_back(other_layerm); + done[it - m_regions.begin()] = true; + } } if (layerms.size() == 1) { // optimization @@ -231,6 +235,10 @@ void Layer::make_perimeters() (*layerm)->make_perimeters((*layerm)->slices, {*layerm}, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { + // Orca: Unlike the compatible regions above, the initiating region has not + // been cleared yet and may contain paths from a previous incompatible run. + clear_generated_extrusions(*layerm); + SurfaceCollection new_slices; // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. LayerRegion *layerm_config = layerms.front(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8a5aa78036..9be6b86139 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -16,6 +16,7 @@ using LayerPtrs = std::vector; class LayerRegion; using LayerRegionPtrs = std::vector; class PrintRegion; +class PrintRegionConfig; class PrintObject; class Print; @@ -200,6 +201,11 @@ public: FillAdaptive::Octree *support_fill_octree, FillLightning::Generator* lightning_generator) const; void make_ironing(); + // Returns the filament id (1-based) the region is ironed with, or -1 when the + // region is not ironed. + static int choose_ironing_extruder(const PrintRegionConfig &cfg, + bool spiral_mode, + bool is_topmost_layer); void make_contour_z(const sla::IndexedMesh &mesh); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index c74df3aa59..94453e18f7 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -30,8 +30,8 @@ bool Line::intersection_infinite(const Line &other, Point* point) const return false; double t1 = cross2(v12, v2) / denom; Vec2d result = (a1 + t1 * v1); - if (result.x() > std::numeric_limits::max() || result.x() < std::numeric_limits::lowest() || - result.y() > std::numeric_limits::max() || result.y() < std::numeric_limits::lowest()) { + if (result.x() > double(std::numeric_limits::max()) || result.x() < double(std::numeric_limits::lowest()) || + result.y() > double(std::numeric_limits::max()) || result.y() < double(std::numeric_limits::lowest())) { // Intersection has at least one of the coordinates much bigger (or smaller) than coord_t maximum value (or minimum). // So it can not be stored into the Point without integer overflows. That could mean that input lines are parallel or near parallel. return false; diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index 308752cc62..e727b29b09 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -3,6 +3,8 @@ #ifdef _WIN32 #include #endif +#include +#include #include #include diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 3cf85e8054..e974ffd7f8 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -867,6 +867,20 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre return is_compatible_with_printer(preset, active_printer, &config); } +// ORCA: see the header. The CLI resolves --load-settings into bare DynamicPrintConfigs and has no +// Preset objects to hand; without this it would have to reimplement the policy or build the shells +// at every call site. +bool is_compatible_with_printer(const DynamicPrintConfig &preset_config, Preset::Type preset_type, + const DynamicPrintConfig &printer_config, const std::string &printer_name) +{ + Preset preset(preset_type, std::string("__compat_check")); + preset.config = preset_config; + Preset printer(Preset::TYPE_PRINTER, printer_name); + printer.config = printer_config; + return is_compatible_with_printer(PresetWithVendorProfile(preset, nullptr), + PresetWithVendorProfile(printer, nullptr)); +} + void Preset::set_visible_from_appconfig(const AppConfig &app_config) { //BBS: add config related log diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 2653628ead..73052678e8 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -459,6 +459,11 @@ protected: bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer); bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config); bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer); +// ORCA: same check for callers that hold raw configs rather than Presets (the CLI). Wraps them in +// throwaway Preset shells and delegates, so the compatibility policy -- including the fail-open on a +// malformed compatible_printers_condition -- lives in one place for the GUI and the CLI alike. +bool is_compatible_with_printer(const DynamicPrintConfig &preset_config, Preset::Type preset_type, + const DynamicPrintConfig &printer_config, const std::string &printer_name); // Where a preset is being loaded from. `Auto` lets load_presets() infer from the directory path. struct PresetOrigin { diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c90ebc756b..54e5db27e4 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -484,7 +484,7 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem) compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable; - auto collection_for_type = [](PresetBundle &bundle, Preset::Type preset_type) -> PresetCollection * { + auto collection_for_type = [](const PresetBundle &bundle, Preset::Type preset_type) -> const PresetCollection * { switch (preset_type) { case Preset::TYPE_PRINT: return &bundle.prints; case Preset::TYPE_FILAMENT: return &bundle.filaments; @@ -493,15 +493,15 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ } }; - PresetCollection *collection = collection_for_type(*this, type); + const PresetCollection *collection = collection_for_type(*this, type); if (collection == nullptr) { error = "Unsupported preset type"; return false; } const boost::filesystem::path source_path = boost::filesystem::absolute(source_file).lexically_normal(); - auto find_loaded = [&](PresetBundle &bundle) -> const Preset * { - PresetCollection *loaded_collection = collection_for_type(bundle, type); + auto find_loaded = [&](const PresetBundle &bundle) -> const Preset * { + const PresetCollection *loaded_collection = collection_for_type(bundle, type); const Preset *resolved = nullptr; for (const Preset &preset : loaded_collection->get_presets()) { if (preset.file.empty()) @@ -549,30 +549,11 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ continue; try { - PresetBundle library_bundle; - const PresetBundle *base_bundle = nullptr; - if (vendor_id != ORCA_FILAMENT_LIBRARY && - boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { - library_bundle.m_preserve_vendor_source_paths = true; - library_bundle.load_vendor_configs_from_json(root_dir.string(), ORCA_FILAMENT_LIBRARY, LoadSystem, - compatibility_rule, nullptr, false); - if (library_bundle.error_count() != 0) { - error = "OrcaFilamentLibrary contains invalid presets"; - return false; - } - base_bundle = &library_bundle; - } - - PresetBundle source_bundle; - source_bundle.m_preserve_vendor_source_paths = true; - source_bundle.load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, - compatibility_rule, base_bundle, false); - if (source_bundle.error_count() != 0) { - error = "Vendor bundle contains invalid presets"; + const PresetBundle *loaded = load_source_vendor(root_dir, vendor_id, compatibility_rule, error); + if (loaded == nullptr) return false; - } - const Preset *resolved = find_loaded(source_bundle); + const Preset *resolved = find_loaded(*loaded); if (resolved == nullptr) { if (error.empty()) error = "Source file is not an instantiated preset in its vendor manifest"; @@ -591,6 +572,37 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ return false; } +const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error) +{ + auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule); + if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end()) + return it->second.get(); + + // The library loads with no base of its own, so the tree a vendor inherits from + // is the same one that resolves the library's own presets. + const PresetBundle *library = nullptr; + if (vendor_id != ORCA_FILAMENT_LIBRARY && + boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { + library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error); + if (library == nullptr) { + error = "OrcaFilamentLibrary contains invalid presets"; + return nullptr; + } + } + + auto bundle = std::make_unique(); + bundle->m_preserve_vendor_source_paths = true; + bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false); + if (bundle->error_count() != 0) { + error = "Vendor bundle contains invalid presets"; + return nullptr; + } + return m_source_vendor_bundles.emplace(std::move(key), std::move(bundle)).first->second.get(); +} + bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type, const std::string &source_file, ForwardCompatibilitySubstitutionRule compatibility_rule, @@ -4981,7 +4993,7 @@ static void apply_mixed_config_relocations(DynamicPrintConfig& case coBools: { auto* live = static_cast(opt); const auto* frozen = static_cast(snapshot.get()); - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const unsigned char cell = from < frozen->values.size() ? frozen->values[from] : 0; if (live->values.size() <= to) live->values.resize(to + 1, 0); @@ -4992,7 +5004,7 @@ static void apply_mixed_config_relocations(DynamicPrintConfig& case coStrings: { auto* live = static_cast(opt); const auto* frozen = static_cast(snapshot.get()); - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const std::string cell = from < frozen->values.size() ? frozen->values[from] : std::string(); if (live->values.size() <= to) live->values.resize(to + 1, std::string{}); @@ -5028,7 +5040,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig& auto* live = static_cast(opt); std::unique_ptr snapshot(opt->clone()); const auto* frozen = static_cast(snapshot.get()); - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const bool cell = from < frozen->values.size() ? frozen->values[from] : false; if (live->values.size() <= to) live->values.resize(to + 1, false); @@ -5044,7 +5056,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig& auto* live = static_cast(opt); std::unique_ptr snapshot(opt->clone()); const auto* frozen = static_cast(snapshot.get()); - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const std::string cell = from < frozen->values.size() ? frozen->values[from] : std::string(); if (live->values.size() <= to) live->values.resize(to + 1, std::string{}); @@ -5060,7 +5072,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig& auto* live = static_cast(opt); std::unique_ptr snapshot(opt->clone()); const auto* frozen = static_cast(snapshot.get()); - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const int cell = from < frozen->values.size() ? frozen->values[from] : 0; if (live->values.size() <= to) live->values.resize(to + 1, 0); @@ -5087,7 +5099,7 @@ static void apply_receiver_mix_relocations(DynamicPrintConfig& move_ints("filament_volume_map"); { const std::vector> frozen = ams_multi_color_filment; - for (const auto [from, to] : moves) { + for (const auto& [from, to] : moves) { const std::vector cell = from < frozen.size() ? frozen[from] : std::vector(); if (ams_multi_color_filment.size() <= to) ams_multi_color_filment.resize(to + 1, std::vector{}); diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 353b6dc07d..88455fabf3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -652,6 +653,17 @@ private: bool m_generate_vendor_caches { false }; bool m_preserve_vendor_source_paths { false }; + // Vendor trees loaded by resolve_preset_config's manifest path, so every preset + // resolved through this bundle shares one load per source root and vendor. The + // filament library is one such tree, shared by every vendor under its root. + std::map, std::unique_ptr> + m_source_vendor_bundles; + + const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error); + // Orca: validation only - flag any printer with two or more compatible // filament presets sharing one filament_id (ambiguous AMS subtype match). bool check_duplicate_filament_subtypes() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1db476eede..0b0fe71dc0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -5430,7 +5430,7 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->readonly = false; def->nullable = true; - def->set_default_value(new ConfigOptionFloatsNullable { {0.0} }); + def->set_default_value(new ConfigOptionFloatsNullable { 0.0 }); def = this->add("cooling_tube_retraction", coFloat); def->label = L("Cooling tube position"); @@ -10936,6 +10936,28 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP return variant_index; } +// Regathers a vector option's values through per-slot source indices (one input index per +// output slot). Out-of-range indices keep the first value, matching get_at's fallback. +template +static void gather_option_values(const char *caller, const std::string &key, OptType *opt, const std::vector &slot_param_indices) +{ + if (!opt || opt->values.empty()) { + BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key; + return; + } + std::vector new_values; + new_values.reserve(slot_param_indices.size()); + for (int idx : slot_param_indices) { + if (idx < 0 || static_cast(idx) >= opt->values.size()) { + BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx; + new_values.emplace_back(opt->values.front()); + } + else + new_values.emplace_back(opt->values[idx]); + } + opt->values = std::move(new_values); +} + void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set& key_set, std::string id_name, std::string variant_name) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; @@ -11013,155 +11035,18 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; continue; } + // An empty option has no first value to fall back on; give it one registered default per filament. + if (auto *vec = dynamic_cast(this->option(key)); vec && vec->empty() && optdef->default_value) + vec->resize(filament_count, optdef->default_value.get()); switch (optdef->type) { - case coStrings: - { - ConfigOptionStrings * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coInts: - { - ConfigOptionInts * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coFloats: - { - ConfigOptionFloats * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coPercents: - { - ConfigOptionPercents * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercents * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coBools: - { - ConfigOptionBools * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coEnums: - { - ConfigOptionEnumsGeneric * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } + case coStrings: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coInts: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coFloats: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coPercents: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coFloatsOrPercents: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coBools: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coEnums: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; default: BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; break; @@ -11180,28 +11065,6 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } } -// Regathers a vector option's values through per-slot source indices (one input index per -// output slot). Out-of-range indices keep the first value, matching get_at's fallback. -template -static void gather_option_values(const std::string &key, OptType *opt, const std::vector &slot_param_indices) -{ - if (!opt || opt->values.empty()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key; - return; - } - std::vector new_values; - new_values.reserve(slot_param_indices.size()); - for (int idx : slot_param_indices) { - if (idx < 0 || static_cast(idx) >= opt->values.size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx; - new_values.emplace_back(opt->values.front()); - } - else - new_values.emplace_back(opt->values[idx]); - } - opt->values = std::move(new_values); -} - void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config, const std::unordered_map>& filament_variant_uses, int extruder_count, int extruder_nozzle_volume_count, @@ -11296,13 +11159,13 @@ void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(Dy continue; } switch (optdef->type) { - case coStrings: gather_option_values(key, this->option(key), slot_param_indices); break; - case coInts: gather_option_values(key, this->option(key), slot_param_indices); break; - case coFloats: gather_option_values(key, this->option(key), slot_param_indices); break; - case coPercents: gather_option_values(key, this->option(key), slot_param_indices); break; - case coFloatsOrPercents: gather_option_values(key, this->option(key), slot_param_indices); break; - case coBools: gather_option_values(key, this->option(key), slot_param_indices); break; - case coEnums: gather_option_values(key, this->option(key), slot_param_indices); break; + case coStrings: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coInts: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coFloats: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coPercents: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coFloatsOrPercents: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coBools: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coEnums: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; default: BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; break; @@ -12053,7 +11916,7 @@ CLIActionsConfigDef::CLIActionsConfigDef() def = this->add("export_settings", coString); def->label = L("Export Settings"); - def->tooltip = L("This exports settings to a file."); + def->tooltip = L("This exports settings to a file. Use - to write them to stdout."); def->cli_params = "settings.json"; def->set_default_value(new ConfigOptionString("output.json")); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f7cbe8b2e5..18e66adb34 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1011,41 +1011,46 @@ public: \ { PrintConfigDef::handle_legacy(opt_key, value); } #define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem); -#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(KEY) cache.opt_add(BOOST_PP_STRINGIZE(KEY), base_ptr, this->KEY); -#define PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION(r, data, elem) PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2(BOOST_PP_TUPLE_ELEM(1, elem)) -#define PRINT_CONFIG_CLASS_ELEMENT_HASH(r, data, elem) boost::hash_combine(seed, BOOST_PP_TUPLE_ELEM(1, elem).hash()); -#define PRINT_CONFIG_CLASS_ELEMENT_EQUAL(r, data, elem) if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false; -#define PRINT_CONFIG_CLASS_ELEMENT_LOWER(r, data, elem) \ - if (BOOST_PP_TUPLE_ELEM(1, elem) < rhs.BOOST_PP_TUPLE_ELEM(1, elem)) return true; \ - if (! (BOOST_PP_TUPLE_ELEM(1, elem) == rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return false; +#define PRINT_CONFIG_CLASS_ELEMENT_VISIT(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), this->BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return; +// Each option list is expanded into the members and again into for_each_option_pair(), which calls +// f(key, this->option, rhs.option) in declaration order and stops when f returns false. hash(), +// operator==, operator< and initialize() iterate the options through that visitor. +#define PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \ + size_t hash() const throw() \ + { \ + size_t seed = 0; \ + this->for_each_option_pair(*this, [&seed](const char*, const auto &a, const auto&) { boost::hash_combine(seed, a.hash()); return true; }); \ + return seed; \ + } \ + bool operator==(const CLASS_NAME &rhs) const throw() \ + { \ + bool eq = true; \ + this->for_each_option_pair(rhs, [&eq](const char*, const auto &a, const auto &b) { eq = (a == b); return eq; }); \ + return eq; \ + } \ + bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ + bool operator<(const CLASS_NAME &rhs) const throw() \ + { \ + int c = 0; \ + this->for_each_option_pair(rhs, [&c](const char*, const auto &a, const auto &b) { if (a < b) c = -1; else if (! (a == b)) c = 1; return c == 0; }); \ + return c < 0; \ + } \ +protected: \ + void initialize(StaticCacheBase &cache, const char *base_ptr) \ + { \ + this->for_each_option_pair(*this, [&cache, base_ptr](const char *key, const auto &a, const auto&) { cache.opt_add(key, base_ptr, a); return true; }); \ + } #define PRINT_CONFIG_CLASS_DEFINE(CLASS_NAME, PARAMETER_DEFINITION_SEQ) \ class CLASS_NAME : public StaticPrintConfig { \ STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ public: \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \ - size_t hash() const throw() \ + template void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const \ { \ - size_t seed = 0; \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ) \ - return seed; \ - } \ - bool operator==(const CLASS_NAME &rhs) const throw() \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ) \ - return true; \ - } \ - bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ - bool operator<(const CLASS_NAME &rhs) const throw() \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_LOWER, _, PARAMETER_DEFINITION_SEQ) \ - return false; \ - } \ -protected: \ - void initialize(StaticCacheBase &cache, const char *base_ptr) \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ) \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ) \ } \ + PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \ }; #define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem @@ -1059,43 +1064,43 @@ protected: \ if (! (*static_cast(this) == static_cast(rhs))) return false; // Generic version, with or without new parameters. Don't use this directly. -#define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_REGISTRATION, PARAMETER_HASHES, PARAMETER_EQUALS) \ +#define PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION, PARAMETER_VISIT) \ class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) { \ STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ CLASS_NAME() : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 0) { assert(s_cache_##CLASS_NAME.initialized()); *this = s_cache_##CLASS_NAME.defaults(); } \ public: \ PARAMETER_DEFINITION \ + template void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { PARAMETER_VISIT } \ size_t hash() const throw() \ { \ size_t seed = 0; \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_HASH, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ - PARAMETER_HASHES \ + this->for_each_option_pair(*this, [&seed](const char*, const auto &a, const auto&) { boost::hash_combine(seed, a.hash()); return true; }); \ return seed; \ } \ bool operator==(const CLASS_NAME &rhs) const throw() \ { \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_DERIVED_EQUAL, _, BOOST_PP_TUPLE_TO_SEQ(CLASSES_PARENTS_TUPLE)) \ - PARAMETER_EQUALS \ - return true; \ + bool eq = true; \ + this->for_each_option_pair(rhs, [&eq](const char*, const auto &a, const auto &b) { eq = (a == b); return eq; }); \ + return eq; \ } \ bool operator!=(const CLASS_NAME &rhs) const throw() { return ! (*this == rhs); } \ protected: \ CLASS_NAME(int) : PRINT_CONFIG_CLASS_DERIVED_INITIALIZER(CLASSES_PARENTS_TUPLE, 1) {} \ void initialize(StaticCacheBase &cache, const char* base_ptr) { \ PRINT_CONFIG_CLASS_DERIVED_INITCACHE(CLASSES_PARENTS_TUPLE) \ - PARAMETER_REGISTRATION \ + this->for_each_option_pair(*this, [&cache, base_ptr](const char *key, const auto &a, const auto&) { cache.opt_add(key, base_ptr, a); return true; }); \ } \ }; // Variant without adding new parameters. #define PRINT_CONFIG_CLASS_DERIVED_DEFINE0(CLASS_NAME, CLASSES_PARENTS_TUPLE) \ - PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY(), BOOST_PP_EMPTY()) + PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, BOOST_PP_EMPTY(), BOOST_PP_EMPTY()) // Variant with adding new parameters. #define PRINT_CONFIG_CLASS_DERIVED_DEFINE(CLASS_NAME, CLASSES_PARENTS_TUPLE, PARAMETER_DEFINITION_SEQ) \ PRINT_CONFIG_CLASS_DERIVED_DEFINE1(CLASS_NAME, CLASSES_PARENTS_TUPLE, \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_HASH, _, PARAMETER_DEFINITION_SEQ), \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_EQUAL, _, PARAMETER_DEFINITION_SEQ)) + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ)) // This object is mapped to Perl as Slic3r::Config::PrintObject. PRINT_CONFIG_CLASS_DEFINE( @@ -2148,11 +2153,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0( #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED #undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION -#undef PRINT_CONFIG_CLASS_ELEMENT_EQUAL -#undef PRINT_CONFIG_CLASS_ELEMENT_LOWER -#undef PRINT_CONFIG_CLASS_ELEMENT_HASH -#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION -#undef PRINT_CONFIG_CLASS_ELEMENT_INITIALIZATION2 +#undef PRINT_CONFIG_CLASS_ELEMENT_VISIT +#undef PRINT_CONFIG_CLASS_COMMON_BODY #undef PRINT_CONFIG_CLASS_DEFINE #undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST #undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a228bb7436..54378b3b16 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -21,9 +21,11 @@ #include "TriangleMeshSlicer.hpp" #include "Utils.hpp" #include "Fill/FillAdaptive.hpp" +#include "Fill/Fill.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" #include "format.hpp" +#include "AABBTreeIndirect.hpp" #include "AABBTreeLines.hpp" #include @@ -672,6 +674,98 @@ void PrintObject::prepare_infill() } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + // Orca: precompute the object's 3D connected bodies for separated infills / per-model + // centering. Two islands belong to the same body when their slices overlap on adjacent + // layers; islands that only overlap in top-down projection but never touch (e.g. interleaved + // chain links) stay separate, matching "split to objects". Each layer island then records + // the full bounding box of its body, so its infill is centered on that body as if it were + // sliced alone. Compute this before bridges so anchors and extrusion share the same origin. + bool needs_separated_components = false; + for (size_t i = 0; i < this->num_printing_regions(); ++ i) { + const PrintRegionConfig &rc = this->printing_region(i).config(); + if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) { + needs_separated_components = true; + break; + } + } + // Orca: Fast path: the feature only changes anything when the object is made of more than one + // connected body. Detect that cheaply the same way as "Split to objects" — more than one + // model part, or a single part whose mesh is splittable (is_splittable() is cached). A single + // body already shares the object center, i.e. the default, so skip the connectivity pass. + if (needs_separated_components) { + int parts = 0; + const ModelVolume *first_part = nullptr; + for (const ModelVolume *v : this->model_object()->volumes) + if (v->is_model_part()) { ++ parts; first_part = v; } + if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable())) + needs_separated_components = false; + } + for (Layer *layer : m_layers) + layer->lslices_separated_component_bboxes.clear(); + if (needs_separated_components) { + const size_t nl = m_layers.size(); + std::vector offset(nl + 1, 0); // Orca: flat index of the first island of each layer + for (size_t i = 0; i < nl; ++ i) + offset[i + 1] = offset[i] + m_layers[i]->lslices.size(); + const size_t nreg = offset[nl]; + // Orca: Union-find over every (layer, island). + std::vector parent(nreg); + for (size_t i = 0; i < nreg; ++ i) parent[i] = i; + auto find = [&parent](size_t x) { + while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } + return x; + }; + auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; }; + // Orca: Index the smaller of two consecutive layers instead of scanning every + // pair of islands. The tree prunes distant boxes on fragmented models; exact + // polygon intersections still decide connectivity for the remaining candidates. + for (size_t i = 0; i + 1 < nl; ++ i) { + m_print->throw_if_canceled(); + size_t layer_a = i, layer_b = i + 1; + if (m_layers[layer_a]->lslices.size() < m_layers[layer_b]->lslices.size()) + std::swap(layer_a, layer_b); + const Layer *la = m_layers[layer_a], *lb = m_layers[layer_b]; + if (lb->lslices.empty()) + continue; + + using IslandTree = AABBTreeIndirect::Tree<2, coord_t>; + std::vector bboxes; + bboxes.reserve(lb->lslices.size()); + for (size_t b = 0; b < lb->lslices.size(); ++ b) + bboxes.emplace_back(b, lb->lslices_bboxes[b]); + IslandTree tree; + tree.build_modify_input(bboxes); + for (size_t a = 0; a < la->lslices.size(); ++ a) { + const IslandTree::BoundingBox query(la->lslices_bboxes[a].min, la->lslices_bboxes[a].max); + AABBTreeIndirect::traverse(tree, + [&query](const IslandTree::Node &node) { return node.bbox.intersects(query); }, + [&](const IslandTree::Node &node) { + const size_t b = node.idx; + // Orca: Tree boxes include an epsilon, so retain the original box + // filter. Already-connected islands cannot change the partition + // and need no further polygon intersection. + if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) && + find(offset[layer_a] + a) != find(offset[layer_b] + b) && + ! intersection_ex(la->lslices[a], lb->lslices[b]).empty()) + unite(offset[layer_a] + a, offset[layer_b] + b); + return true; + }); + } + } + // Orca: Full bounding box of each body, indexed by its union-find root. + std::vector body_bbox(nreg); + for (size_t i = 0; i < nl; ++ i) + for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a) + body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]); + // Orca: Store the body bbox for every island. + for (size_t i = 0; i < nl; ++ i) { + Layer *layer = m_layers[i]; + layer->lslices_separated_component_bboxes.resize(layer->lslices.size()); + for (size_t a = 0; a < layer->lslices.size(); ++ a) + layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)]; + } + } + // the following step needs to be done before combination because it may need // to remove only half of the combined infill this->bridge_over_infill(); @@ -706,71 +800,6 @@ void PrintObject::infill() if (this->set_started(posInfill)) { m_print->set_status(35, L("Generating infill toolpath")); - // Orca: precompute the object's 3D connected bodies for separated infills / per-model - // centering. Two islands belong to the same body when their slices overlap on adjacent - // layers; islands that only overlap in top-down projection but never touch (e.g. interleaved - // chain links) stay separate, matching "split to objects". Each layer island then records - // the full bounding box of its body, so its infill is centered on that body as if it were - // sliced alone. Done once here, before the parallel fill, and only when a region needs it. - bool needs_separated_components = false; - for (size_t i = 0; i < this->num_printing_regions(); ++ i) { - const PrintRegionConfig &rc = this->printing_region(i).config(); - if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) { - needs_separated_components = true; - break; - } - } - // Fast path: the feature only changes anything when the object is made of more than one - // connected body. Detect that cheaply the same way as "Split to objects" — more than one - // model part, or a single part whose mesh is splittable (is_splittable() is cached). A single - // body already shares the object center, i.e. the default, so skip the connectivity pass. - if (needs_separated_components) { - int parts = 0; - const ModelVolume *first_part = nullptr; - for (const ModelVolume *v : this->model_object()->volumes) - if (v->is_model_part()) { ++ parts; first_part = v; } - if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable())) - needs_separated_components = false; - } - for (Layer *layer : m_layers) - layer->lslices_separated_component_bboxes.clear(); - if (needs_separated_components) { - const size_t nl = m_layers.size(); - std::vector offset(nl + 1, 0); // flat index of the first island of each layer - for (size_t i = 0; i < nl; ++ i) - offset[i + 1] = offset[i] + m_layers[i]->lslices.size(); - const size_t nreg = offset[nl]; - // Union-find over every (layer, island). - std::vector parent(nreg); - for (size_t i = 0; i < nreg; ++ i) parent[i] = i; - auto find = [&parent](size_t x) { - while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } - return x; - }; - auto unite = [&](size_t a, size_t b) { a = find(a); b = find(b); if (a != b) parent[a] = b; }; - // Join islands that overlap between two consecutive layers. - for (size_t i = 0; i + 1 < nl; ++ i) { - const Layer *la = m_layers[i], *lb = m_layers[i + 1]; - for (size_t a = 0; a < la->lslices.size(); ++ a) - for (size_t b = 0; b < lb->lslices.size(); ++ b) - if (la->lslices_bboxes[a].overlap(lb->lslices_bboxes[b]) && - ! intersection_ex(la->lslices[a], lb->lslices[b]).empty()) - unite(offset[i] + a, offset[i + 1] + b); - } - // Full bounding box of each body, indexed by its union-find root. - std::vector body_bbox(nreg); - for (size_t i = 0; i < nl; ++ i) - for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a) - body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]); - // Store the body bbox for every island. - for (size_t i = 0; i < nl; ++ i) { - Layer *layer = m_layers[i]; - layer->lslices_separated_component_bboxes.resize(layer->lslices.size()); - for (size_t a = 0; a < layer->lslices.size(); ++ a) - layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)]; - } - } - const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; @@ -1401,8 +1430,6 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "infill_anchor_max" || opt_key == "top_surface_line_width" || opt_key == "bottom_surface_density" - || opt_key == "center_of_surface_pattern" - || opt_key == "separated_infills" || opt_key == "initial_layer_line_width" || opt_key == "small_area_infill_flow_compensation" || opt_key == "lateral_lattice_angle_1" @@ -1410,6 +1437,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "infill_overhang_angle") { steps.emplace_back(posInfill); } else if (opt_key == "sparse_infill_pattern" + // Orca: Body centering now also determines bridge anchors during preparation. + // Invalidating preparation also invalidates infill, including top/bottom surfaces. + || opt_key == "center_of_surface_pattern" + || opt_key == "separated_infills" || opt_key == "sparse_infill_smooth_factor" || opt_key == "symmetric_infill_y_axis" || opt_key == "infill_shift_step" @@ -1480,13 +1511,9 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow" || opt_key == "internal_bridge_flow") { - if (m_config.support_top_z_distance > 0.) { - // Only invalidate due to bridging if bridging is enabled. - // If later "support_top_z_distance" is modified, the complete PrintObject is invalidated anyway. - steps.emplace_back(posPerimeters); - steps.emplace_back(posInfill); - steps.emplace_back(posSupportMaterial); - } + steps.emplace_back(posPerimeters); + steps.emplace_back(posInfill); + steps.emplace_back(posSupportMaterial); } else if ( opt_key == "wall_generator" || opt_key == "wall_transition_length" @@ -1604,7 +1631,9 @@ bool PrintObject::invalidate_step(PrintObjectStep step) bool PrintObject::invalidate_all_steps() { // First call the "invalidate" functions, which may cancel background processing. - bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + const bool inherited_invalidated = Inherited::invalidate_all_steps(); + const bool print_invalidated = m_print->invalidate_all_steps(); + bool result = inherited_invalidated || print_invalidated; // Then reset some of the depending values. m_slicing_params.valid = false; return result; @@ -3009,21 +3038,12 @@ void PrintObject::bridge_over_infill() return diff(layers_sparse_infill, not_sparse_infill); }; - // LAMBDA do determine optimal bridging angle - auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern, double infill_direction) { + // Orca: Derive the fallback bridge direction from the supplied anchor geometry. + // Pattern-specific angle selection belongs at the call site, where the supporting + // layer and region are known; this helper must not override it with a base config angle. + auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors) { AABBTreeLines::LinesDistancer lines_tree(anchors); - // Orca: since 3D Honeycomb was "fixed" by forcing coordf_t layerHeight = scale_(1.0), this is no longer needed. - // CorssHatch also does not need fixed angle. - // - // Check it the infill that require a fixed infill angle. - //switch (dominant_pattern) { - //case ip3DHoneycomb: - //case ipCrossHatch: - // return (infill_direction + 45.0) * 2.0 * M_PI / 360.; - //default: break; - //} - std::map counted_directions; for (const Polygon &p : bridged_area) { double acc_distance = 0; @@ -3089,18 +3109,15 @@ void PrintObject::bridge_over_infill() if (bridging_angle == 0) { bridging_angle = 0.001; } - switch (dominant_pattern) { - case ipHilbertCurve: bridging_angle += 0.25 * PI; break; - case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; - default: break; - } return bridging_angle; }; - // LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly - // generated lines - auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) { + // Orca: Extend scan sections to the nearest anchors and reconstruct the bridge area. + // scan_spacing controls boundary sampling independently of the extrusion spacing; + // anchoring overlap and smoothing thresholds still use the physical bridging flow. + auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle, + coord_t scan_spacing, bool restore_anchors = false) { auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { for (Line &l : lines) { double ax = double(l.a.x()); @@ -3127,12 +3144,12 @@ void PrintObject::bridge_over_infill() BoundingBox bb_x = get_extents(bridged_area); BoundingBox bb_y = get_extents(anchors); - const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing(); + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + scan_spacing - 1) / scan_spacing; std::vector vertical_lines(n_vlines); for (size_t i = 0; i < n_vlines; i++) { - // Orca: Make sure the line is placed in the middle of the extrusion - // coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing(); - coord_t x = bb_x.min.x() + (i + 0.5) * bridging_flow.scaled_spacing(); + // Orca: Sample the center of each reconstructed strip. Its edges lie + // half a scan step away, even when the sampling is finer than extrusion. + coord_t x = bb_x.min.x() + (i + 0.5) * scan_spacing; coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing(); coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing(); vertical_lines[i].a = Point{x, y_min}; @@ -3155,7 +3172,11 @@ void PrintObject::bridge_over_infill() auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); for (Line §ion : polygon_sections[i]) { - auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, + // Orca: A repaired boundary may already overlap its anchor by one flow width. + // Include that overlap in the search so restoring rounded corners does not + // extend every already anchored section into the next sparse infill cell. + const coord_t overlap = restore_anchors ? bridging_flow.scaled_width() + SCALED_EPSILON : 0; + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a + Point{0, overlap}, [](const Point &a, const std::pair &b) { return a.y() > b.first.y(); }); @@ -3164,7 +3185,7 @@ void PrintObject::bridge_over_infill() section.a.y() -= bridging_flow.scaled_width() * (0.5 + 0.5); } - auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b, + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b - Point{0, overlap}, [](const Point &a, const std::pair &b) { return a.y() < b.first.y(); }); @@ -3194,7 +3215,9 @@ void PrintObject::bridge_over_infill() }); } - // reconstruct polygon from polygon sections + // Orca: Reconstruct the polygon from scan sections. At discontinuities and + // strip starts/ends, use half the scan step for the X offsets; using half an + // extrusion spacing would overlap the finer strips and distort curved anchors. struct TracedPoly { Points lows; @@ -3220,8 +3243,8 @@ void PrintObject::bridge_over_infill() 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { traced_poly.lows.push_back(candidate->a); } else { - traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0}); + traced_poly.lows.push_back(candidate->a - Point{scan_spacing / 2, 0}); traced_poly.lows.push_back(candidate->a); } @@ -3229,8 +3252,8 @@ void PrintObject::bridge_over_infill() 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { traced_poly.highs.push_back(candidate->b); } else { - traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0}); + traced_poly.highs.push_back(candidate->b - Point{scan_spacing / 2, 0}); traced_poly.highs.push_back(candidate->b); } segment_added = true; @@ -3238,9 +3261,9 @@ void PrintObject::bridge_over_infill() } if (!segment_added) { - // Zero overlapping segments, we just close this polygon - traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); - traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + // Orca: No section continues this strip; close at its right edge. + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scan_spacing / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scan_spacing / 2, 0}); Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); traced_poly.lows.clear(); @@ -3255,9 +3278,9 @@ void PrintObject::bridge_over_infill() for (const auto &segment : polygon_slice) { if (used_segments.find(&segment) == used_segments.end()) { TracedPoly &new_tp = current_traced_polys.emplace_back(); - new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a - Point{scan_spacing / 2, 0}); new_tp.lows.push_back(segment.a); - new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b - Point{scan_spacing / 2, 0}); new_tp.highs.push_back(segment.b); } } @@ -3364,7 +3387,10 @@ void PrintObject::bridge_over_infill() total_fill_area = closing(total_fill_area, float(SCALED_EPSILON)); expansion_area = closing(expansion_area, float(SCALED_EPSILON)); expansion_area = intersection(expansion_area, deep_infill_area); - Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + // Orca: Preserve the real lower-layer anchors for every candidate in this + // layer. Replacing this shared set for one pattern also changes later regions, + // and synthetic straight lines can claim support where no infill is printed. + const Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); #ifdef DEBUG_BRIDGE_OVER_INFILL @@ -3375,6 +3401,9 @@ void PrintObject::bridge_over_infill() std::vector expanded_surfaces; expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { + const auto ®ion_config = candidate.region->region().config(); + const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve || + region_config.sparse_infill_pattern == ipOctagramSpiral; const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); @@ -3403,20 +3432,40 @@ void PrintObject::bridge_over_infill() to_lines(area_to_be_bridge), to_lines(boundary_plines), to_lines(anchors), to_lines(expansion_area)); #endif - double bridging_angle = 0; - if (!anchors.empty()) { - bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors), - candidate.region->region().config().sparse_infill_pattern.value, - candidate.region->region().config().infill_direction.value); - } else { - // use expansion boundaries as anchors. - // Also, use Infill pattern that is neutral for angle determination, since there are no infill lines. - bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine, 0); + double bridging_angle = -1.; + if (!anchors.empty() && turning_pattern) { + // Orca: Keep adjacent bridges over Hilbert/Octagram aligned despite + // their many local turning directions. Use the lower layer's rotation, + // since that is the infill supporting the bridge, not the current layer's. + for (const LayerRegion *lower_region : layer->lower_layer->regions()) { + // Orca: Apply the configured direction only if the same region has + // sparse infill below this bridge. A height modifier may put another + // pattern underneath, requiring the geometry-based fallback below. + if (&lower_region->region() != &candidate.region->region() || + intersection(area_to_be_bridge, to_polygons(lower_region->fill_surfaces.filter_by_type(stInternal))).empty()) + continue; + bridging_angle = calculate_infill_rotation_angle(po, layer->lower_layer->id(), region_config.infill_direction.value, + region_config.sparse_infill_rotate_template.value) + 0.5 * PI; + // Orca: Apply model alignment as infill generation does, then normalize + // the undirected bridge angle to [0, PI), including negative rotations. + if (region_config.align_infill_direction_to_model) { + const auto &m = po->trafo().matrix(); + bridging_angle += std::atan2(double(m(1, 0)), double(m(0, 0))); + } + bridging_angle = std::fmod(bridging_angle, PI); + if (bridging_angle < 0.) + bridging_angle += PI; + break; + } } + // Orca: A different region below (e.g. a height modifier) needs the actual anchor + // directions. When there are no sparse anchors, use the expansion boundaries. + if (bridging_angle < 0.) + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors.empty() ? boundary_plines : anchors)); - // ORCA: Internal bridge angle override + // Orca: Preserve the user's absolute or relative internal bridge angle + // override after automatic direction selection. if (candidate.region->region().config().internal_bridge_angle.value > 0) { - const auto ®ion_config = candidate.region->region().config(); const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value); if (region_config.relative_bridge_angle.value) bridging_angle += custom_angle_rad; @@ -3429,11 +3478,19 @@ void PrintObject::bridge_over_infill() } } + // Orca: Changing the bridge direction must not change its physical supports. + // Extend to actual sparse infill or the existing boundary anchors, never to + // a synthetic grid that merely has the same nominal angle and spacing. boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end()); if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) { boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10))); } - Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + // Orca: Use four samples per extrusion spacing for Hilbert/Octagram so the + // reconstructed boundary follows rounded anchors instead of cutting corners. + // Keep the original step for other patterns and at least one coordinate unit + // after integer division. This changes boundary accuracy, not infill density. + const coord_t scan_spacing = std::max(coord_t(1), flow.scaled_spacing() / (turning_pattern ? 4 : 1)); + Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing); // Check collision with other expanded surfaces { @@ -3447,7 +3504,9 @@ void PrintObject::bridge_over_infill() } } if (reconstruct) { - bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + // Orca: Retain the same sampling accuracy when matching a nearby + // bridge's direction; rebuilding must not lose the curved supports. + bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle, scan_spacing); } } @@ -3455,6 +3514,13 @@ void PrintObject::bridge_over_infill() // bridging_area = opening(bridging_area, flow.scaled_spacing()); bridging_area = opening(bridging_area, flow.scaled_spacing() * 0.75); bridging_area = closing(bridging_area, flow.scaled_spacing()); + // Orca: Opening/closing can pull rounded bridge ends away from their real + // supports. Restore those contacts after smoothing, preserving the cleaned + // area and the selected angle; do not smooth the restored contacts again. + if (turning_pattern && !bridging_area.empty()) { + bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow, + bridging_angle, scan_spacing, true)); + } bridging_area = intersection(bridging_area, limiting_area); bridging_area = intersection(bridging_area, total_fill_area); bridging_area = diff(bridging_area, total_top_area); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index cdefd3e10e..eb37ca578c 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1007,7 +1007,9 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) bool SLAPrintObject::invalidate_all_steps() { - return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + const bool inherited_invalidated = Inherited::invalidate_all_steps(); + const bool print_invalidated = m_print->invalidate_all_steps(); + return inherited_invalidated || print_invalidated; } double SLAPrintObject::get_elevation() const { diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 3030b6d194..edd7c2a3d0 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -30,6 +30,11 @@ static HMODULE s_hKernel32 = nullptr; static SetThreadDescriptionType s_fnSetThreadDescription = nullptr; static GetThreadDescriptionType s_fnGetThreadDescription = nullptr; +// Convert the FARPROC from GetProcAddress to Fn through a generic function pointer. +template static Fn load_proc(HMODULE module, const char* name) { + return reinterpret_cast(reinterpret_cast(::GetProcAddress(module, name))); +} + static bool WindowsGetSetThreadNameAPIInitialize() { if (! s_SetGetThreadDescriptionInitialized) { @@ -37,8 +42,8 @@ static bool WindowsGetSetThreadNameAPIInitialize() // to initialize s_hKernel32 = LoadLibraryW(L"Kernel32.dll"); if (s_hKernel32) { - s_fnSetThreadDescription = (SetThreadDescriptionType)::GetProcAddress(s_hKernel32, "SetThreadDescription"); - s_fnGetThreadDescription = (GetThreadDescriptionType)::GetProcAddress(s_hKernel32, "GetThreadDescription"); + s_fnSetThreadDescription = load_proc(s_hKernel32, "SetThreadDescription"); + s_fnGetThreadDescription = load_proc(s_hKernel32, "GetThreadDescription"); } s_SetGetThreadDescriptionInitialized = true; } diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 797894442a..b21da72fc8 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -70,6 +70,7 @@ #define CLI_FILAMENT_CAN_NOT_MAP -66 #define CLI_ONLY_ONE_TPU_SUPPORTED -67 #define CLI_FILAMENTS_NOT_SUPPORTED_BY_EXTRUDER -68 +#define CLI_MIXED_FILAMENT_INVALID -69 #define CLI_SLICING_ERROR -100 #define CLI_GCODE_PATH_CONFLICTS -101 @@ -313,6 +314,9 @@ extern unsigned get_current_pid(); std::string per_user_temp_id(); // Per-user temp root under `base`; an empty `user_id` returns `base` unchanged. std::string per_user_temp_dir(const std::string &base, const std::string &user_id); +// Completes a relative command line input path against the current working directory. Absolute +// paths and custom open protocol URLs are returned unchanged. +std::string resolve_cli_input_path(const std::string &path); // BBS: backup & restore std::string get_process_name(int pid); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 58323b29ce..9def5dad17 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1339,6 +1339,19 @@ std::string per_user_temp_dir(const std::string &base, const std::string &user_i return base + "/orcaslicer_" + user_id; } +std::string resolve_cli_input_path(const std::string &path) +{ + const boost::filesystem::path input(path); + if (path.empty() || is_supported_open_protocol(path) || input.is_absolute()) + return path; + + boost::system::error_code ec; + const boost::filesystem::path resolved = boost::filesystem::system_complete(input, ec); + if (ec) + return path; + return resolved.lexically_normal().make_preferred().string(); +} + // BBS: backup & restore std::string get_process_name(int pid) { diff --git a/src/slic3r/GUI/AMSDryControl.cpp b/src/slic3r/GUI/AMSDryControl.cpp index c191e24eac..eb5ddb5d4e 100644 --- a/src/slic3r/GUI/AMSDryControl.cpp +++ b/src/slic3r/GUI/AMSDryControl.cpp @@ -1,6 +1,7 @@ #include "AMSDryControl.hpp" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "GUI_App.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "I18N.hpp" #include "slic3r/GUI/DeviceCore/DevExtruderSystem.h" diff --git a/src/slic3r/GUI/AMSDryControl.hpp b/src/slic3r/GUI/AMSDryControl.hpp index 223fe137e2..6c5df86849 100644 --- a/src/slic3r/GUI/AMSDryControl.hpp +++ b/src/slic3r/GUI/AMSDryControl.hpp @@ -14,6 +14,7 @@ //Previous defintions class wxGrid; +class ProgressBar; namespace Slic3r { diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp index 68f1b44212..f0fdf950a0 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.cpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp @@ -2,6 +2,8 @@ #include "ExtrusionCalibration.hpp" #include "MsgDialog.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "libslic3r/Preset.hpp" #include "I18N.hpp" #include diff --git a/src/slic3r/GUI/AmsMappingPopup.cpp b/src/slic3r/GUI/AmsMappingPopup.cpp index 3e745b0a64..22ffaef034 100644 --- a/src/slic3r/GUI/AmsMappingPopup.cpp +++ b/src/slic3r/GUI/AmsMappingPopup.cpp @@ -11,6 +11,7 @@ #include "MainFrame.hpp" #include "format.hpp" #include "Widgets/ProgressDialog.hpp" +#include #include "Widgets/RoundedRectangle.hpp" #include "Widgets/StaticBox.hpp" diff --git a/src/slic3r/GUI/BaseTransparentDPIFrame.hpp b/src/slic3r/GUI/BaseTransparentDPIFrame.hpp index 35ed51ddfe..7dc83b4d46 100644 --- a/src/slic3r/GUI/BaseTransparentDPIFrame.hpp +++ b/src/slic3r/GUI/BaseTransparentDPIFrame.hpp @@ -5,8 +5,10 @@ #include #include "GUI_App.hpp" #include "GUI_Utils.hpp" +#include class Button; +class Label; class CheckBox; namespace Slic3r { namespace GUI { class CapsuleButton; diff --git a/src/slic3r/GUI/CalibrationWizard.cpp b/src/slic3r/GUI/CalibrationWizard.cpp index 7496d59a51..f80562578d 100644 --- a/src/slic3r/GUI/CalibrationWizard.cpp +++ b/src/slic3r/GUI/CalibrationWizard.cpp @@ -1,6 +1,7 @@ #include "CalibrationWizard.hpp" #include "I18N.hpp" #include "GUI_App.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "MsgDialog.hpp" #include "CalibrationWizardPage.hpp" #include "../../libslic3r/calib.hpp" diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp index c6d491a930..7a83d39dd3 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp @@ -1,5 +1,7 @@ #include #include "CalibrationWizardPresetPage.hpp" +#include "GUI.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "I18N.hpp" #include "Widgets/Label.hpp" #include "MsgDialog.hpp" diff --git a/src/slic3r/GUI/CalibrationWizardSavePage.cpp b/src/slic3r/GUI/CalibrationWizardSavePage.cpp index f7699cfab0..427f022d1c 100644 --- a/src/slic3r/GUI/CalibrationWizardSavePage.cpp +++ b/src/slic3r/GUI/CalibrationWizardSavePage.cpp @@ -1,4 +1,5 @@ #include "CalibrationWizardSavePage.hpp" +#include "GUI.hpp" #include "I18N.hpp" #include "Widgets/Label.hpp" #include "MsgDialog.hpp" diff --git a/src/slic3r/GUI/CapsuleButton.cpp b/src/slic3r/GUI/CapsuleButton.cpp index 8afe39889e..8d71f9911e 100644 --- a/src/slic3r/GUI/CapsuleButton.cpp +++ b/src/slic3r/GUI/CapsuleButton.cpp @@ -1,5 +1,6 @@ #include "GUI_App.hpp" #include "CapsuleButton.hpp" +#include "Widgets/StateColor.hpp" #include #include "wx/graphics.h" #include "Widgets/Label.hpp" diff --git a/src/slic3r/GUI/ColorDecomposeSupport.cpp b/src/slic3r/GUI/ColorDecomposeSupport.cpp index 6621b97059..ea67564208 100644 --- a/src/slic3r/GUI/ColorDecomposeSupport.cpp +++ b/src/slic3r/GUI/ColorDecomposeSupport.cpp @@ -1,4 +1,5 @@ #include "ColorDecomposeSupport.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "MixedFilamentDialog.hpp" #include "GUI_App.hpp" #include "MsgDialog.hpp" diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 8eab1c785c..5bd74e107d 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -2,6 +2,7 @@ #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" +#include "DeviceCore/DevConfigUtil.h" #include "format.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/DailyTips.cpp b/src/slic3r/GUI/DailyTips.cpp index d2f758bf5f..894c0316bb 100644 --- a/src/slic3r/GUI/DailyTips.cpp +++ b/src/slic3r/GUI/DailyTips.cpp @@ -1,4 +1,5 @@ #include "DailyTips.hpp" +#include "slic3r/GUI/Widgets/Label.hpp" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS diff --git a/src/slic3r/GUI/DeviceCore/DevCalib.cpp b/src/slic3r/GUI/DeviceCore/DevCalib.cpp index cf7ee1d90e..ddf71e0c62 100644 --- a/src/slic3r/GUI/DeviceCore/DevCalib.cpp +++ b/src/slic3r/GUI/DeviceCore/DevCalib.cpp @@ -1,5 +1,7 @@ #include #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/DeviceManager.hpp" #include "slic3r/GUI/UserNotification.hpp" #include "libslic3r/PrintConfig.hpp" diff --git a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp index b59499d9d9..7ae990730a 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaBlackList.cpp @@ -4,6 +4,8 @@ #include #include "DevFilaBlackList.h" +#include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/GUI/DeviceManager.hpp" #include "DevFilaSystem.h" #include "DevManager.h" #include "DevConfigUtil.h" diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index e0a230969b..881aa75d20 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -1,5 +1,6 @@ #include #include "DevFilaSystem.h" +#include "slic3r/Utils/NetworkAgent.hpp" #include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId // TODO: remove this include diff --git a/src/slic3r/GUI/DeviceCore/DevMapping.cpp b/src/slic3r/GUI/DeviceCore/DevMapping.cpp index 165492c9f6..0040bb05f2 100644 --- a/src/slic3r/GUI/DeviceCore/DevMapping.cpp +++ b/src/slic3r/GUI/DeviceCore/DevMapping.cpp @@ -1,3 +1,5 @@ +#include + #include #include "DevMapping.h" #include "DevFilaSystem.h" @@ -270,7 +272,7 @@ namespace Slic3r std::set picked_tar; for (int k = 0; k < distance_map.size(); k++) { - float min_val = INT_MAX; + float min_val = std::numeric_limits::max(); int picked_src_idx = -1; int picked_tar_idx = -1; for (int i = 0; i < distance_map.size(); i++) diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 782574c220..fd60f80d25 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -1,5 +1,7 @@ #include "libslic3r/libslic3r.h" #include "DeviceManager.hpp" +#include "HMS.hpp" +#include "I18N.hpp" #include "libslic3r/Time.hpp" #include "libslic3r/Thread.hpp" #include "slic3r/Utils/NetworkAgent.hpp" diff --git a/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp index 64195d97a8..16ae8812be 100644 --- a/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp @@ -1,6 +1,7 @@ #include "wgtMsgPanel.h" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/Widgets/Label.hpp" #include "slic3r/GUI/Widgets/StateColor.hpp" #include "slic3r/GUI/wxExtensions.hpp" diff --git a/src/slic3r/GUI/DragCanvas.cpp b/src/slic3r/GUI/DragCanvas.cpp index 04d51c0861..66a9acecbc 100644 --- a/src/slic3r/GUI/DragCanvas.cpp +++ b/src/slic3r/GUI/DragCanvas.cpp @@ -1,6 +1,7 @@ #include "DragCanvas.hpp" #include "wxExtensions.hpp" #include "GUI_App.hpp" +#include "Widgets/StateColor.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/EncodedFilament.cpp b/src/slic3r/GUI/EncodedFilament.cpp index f9054e7f6d..0cab200386 100644 --- a/src/slic3r/GUI/EncodedFilament.cpp +++ b/src/slic3r/GUI/EncodedFilament.cpp @@ -1,7 +1,10 @@ #include "EncodedFilament.hpp" +#include #include "GUI_App.hpp" +using json = nlohmann::json; + namespace Slic3r { diff --git a/src/slic3r/GUI/ExportPresetBundleDialog.cpp b/src/slic3r/GUI/ExportPresetBundleDialog.cpp index 6d642ee1c1..9d2f26bf6a 100644 --- a/src/slic3r/GUI/ExportPresetBundleDialog.cpp +++ b/src/slic3r/GUI/ExportPresetBundleDialog.cpp @@ -1,4 +1,5 @@ #include "ExportPresetBundleDialog.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "GUI_App.hpp" #include "ConfigWizard.hpp" #include "I18N.hpp" @@ -12,7 +13,11 @@ #include #include #include +#include #include + +using json = nlohmann::json; + namespace Slic3r { namespace GUI { ExportPresetBundleDialog::ExportPresetBundleDialog( diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 18811ef241..3abfdb82ff 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -1,6 +1,7 @@ #include "ExtraRenderers.hpp" #include "wxExtensions.hpp" #include "GUI.hpp" +#include "I18N.hpp" #include "BitmapComboBox.hpp" #include "Plater.hpp" #include "Widgets/ComboBox.hpp" diff --git a/src/slic3r/GUI/ExtrusionCalibration.cpp b/src/slic3r/GUI/ExtrusionCalibration.cpp index 933e2ac211..1f07823d61 100644 --- a/src/slic3r/GUI/ExtrusionCalibration.cpp +++ b/src/slic3r/GUI/ExtrusionCalibration.cpp @@ -1,5 +1,6 @@ #include "ExtrusionCalibration.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" #include "MsgDialog.hpp" #include "libslic3r/Preset.hpp" #include diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index b7564551f8..74ef3f87c8 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -11,6 +11,7 @@ #include "libslic3r/PrintConfig.hpp" #include +#include #include #include #include @@ -540,51 +541,95 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true case coStrings: case coFloatOrPercent: case coFloatsOrPercents: { - if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%') - { + if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && + !(m_opt.nullable && str == m_na_value)) { + bool update_control = false; + wxString numeric_str = str; double val = 0.; + const char dec_sep = is_decimal_separator_point() ? '.' : ','; const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - if (str.Replace(dec_sep_alt, dec_sep, false) != 0) - set_value(str, false); + // Orca: normalize the decimal separator and optional unit before + // detecting the percentage suffix and parsing the numeric part. + update_control |= numeric_str.Replace(dec_sep_alt, dec_sep, false) != 0; + update_control |= numeric_str.Replace(" ", "", true) != 0; + const bool has_literal_unit = numeric_str.EndsWith("mm"); + if (has_literal_unit) { + numeric_str.RemoveLast(2); + update_control = true; + } + bool is_percent = !numeric_str.IsEmpty() && numeric_str.Last() == '%'; + if (is_percent) + numeric_str.RemoveLast(); - - // remove space and "mm" substring, if any exists - str.Replace(" ", "", true); - str.Replace("m", "", true); - - if (!str.ToDouble(&val)) - { + if ((has_literal_unit && is_percent) || !numeric_str.ToDouble(&val) || !std::isfinite(val)) { if (!check_value) { m_value.clear(); break; } show_error(m_parent, _L("Invalid numeric.")); - set_value(double_to_string(val), true); - } - else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) || - (m_opt.sidetext.rfind("mm ") != std::string::npos && val > /*1*/m_opt.max_literal)) && - (m_value.empty() || into_u8(str) != boost::any_cast(m_value))) - { - if (!check_value) { - m_value.clear(); - break; + numeric_str = double_to_string(std::clamp(0., double(m_opt.min), double(m_opt.max))); + is_percent = false; + update_control = true; + } else { + const bool looks_like_missing_percent = !is_percent && !has_literal_unit && + ((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) || + (m_opt.sidetext.rfind("mm ") != std::string::npos && val > m_opt.max_literal)); + // Orca: validate explicit percentages and literal values before + // asking whether an otherwise valid literal was meant as a percentage. + const bool out_of_range = !m_opt.is_value_valid(val); + if (out_of_range) { + if (!check_value) { + m_value.clear(); + break; + } + show_error(m_parent, _L("Value is out of range.")); + val = std::clamp(val, double(m_opt.min), double(m_opt.max)); + // Orca: retain the inferred percent unit when clamping a + // suspicious unitless value, so 2000 becomes 100%, not 100 mm. + is_percent |= looks_like_missing_percent; + numeric_str = double_to_string(val); + update_control = true; + } else { + const bool value_changed = m_value.empty() || into_u8(str) != boost::any_cast(m_value); + if (looks_like_missing_percent && value_changed) { + if (!check_value) { + m_value.clear(); + break; + } + + const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; + const wxString stVal = numeric_str; + const wxString msg_text = from_u8((boost::format(_utf8(L("Is it %s%% or %s %s?"))) % + stVal % stVal % sidetext).str()); + WarningDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxYES | wxNO); + dialog.SetButtonLabel(wxID_YES, stVal + _L("%")); + dialog.SetButtonLabel(wxID_NO, stVal + " " + _L(sidetext)); + dialog.GetSizer()->SetSizeHints(&dialog); + dialog.Fit(); + dialog.CenterOnParent(); + is_percent = dialog.ShowModal() == wxID_YES; + update_control = true; + } } - const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; - const wxString stVal = double_to_string(val, 2); - const wxString msg_text = from_u8((boost::format(_utf8(L("Is it %s%% or %s %s?\n" - "YES for %s%%, \n" - "NO for %s %s."))) % - stVal % stVal % sidetext % stVal % stVal % sidetext) - .str()); - WarningDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxYES | wxNO); - if ((val > 100) && dialog.ShowModal() == wxID_YES) { - set_value(from_u8((boost::format("%s%%") % stVal).str()), false /*true*/); - str += "%%"; - } else - set_value(stVal, false); // it's no needed but can be helpful, when inputted value contained "," instead of "." + // Orca: also enforce the literal limit after clamping an explicit mm input. + if (!is_percent && m_opt.sidetext.rfind("mm ") != std::string::npos && val > m_opt.max_literal) { + if (!check_value) { + m_value.clear(); + break; + } + if (!out_of_range) + show_error(m_parent, _L("Value is out of range.")); + val = m_opt.max_literal; + numeric_str = double_to_string(val); + update_control = true; + } + } + + if (update_control) { + str = numeric_str + (is_percent ? "%" : ""); + set_value(str, true); } } if (m_opt.opt_key == "thumbnails") { @@ -2154,6 +2199,7 @@ void PrinterAgentChoice::msw_rescale() void PluginField::BUILD() { auto* panel = new wxPanel(m_parent, wxID_ANY); + panel->SetBackgroundColour(*wxWHITE); wxGetApp().UpdateDarkUI(panel); window = panel; @@ -2196,9 +2242,8 @@ void PluginField::rebuild_ui() m_rows.clear(); m_standalone_add_btn = nullptr; - if (m_values.empty()) { - add_empty_state_row(); - } else { + add_empty_state_row(); + if (!m_values.empty()) { for (size_t i = 0; i < m_values.size(); ++i) add_plugin_row(display_name_for_value(m_values[i]), i == m_values.size() - 1); } @@ -2215,94 +2260,43 @@ void PluginField::rebuild_ui() void PluginField::add_empty_state_row() { - const auto button_size = wxSize(def_width_thinner() * m_em_unit, -1); - auto row_sizer = new wxBoxSizer(wxHORIZONTAL); - - wxTextCtrl* display = new wxTextCtrl(window, wxID_ANY, _L("No plugin selected"), - wxDefaultPosition, wxSize(def_width_wider() * m_em_unit, wxDefaultCoord), - wxTE_READONLY); - display->SetEditable(false); - wxGetApp().UpdateDarkUI(display); - display->SetToolTip(_L("No plugin selected")); - - auto add_btn = new ScalableButton(window, wxID_ANY, "param_add", wxEmptyString, - button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16); - wxGetApp().UpdateDarkUI(add_btn); - add_btn->SetToolTip(_L("Add plugin")); + auto add_btn = new Button(window, _L("Add plugin"), "param_add", 0, 16); + add_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_add_clicked(); }); - row_sizer->Add(display, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - row_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL); - m_main_sizer->Add(row_sizer, 0, wxEXPAND); - - PluginRow row; - row.display = display; - row.add_btn = add_btn; - row.sizer = row_sizer; - m_rows.push_back(row); + m_main_sizer->Add(add_btn, 0, wxEXPAND | wxBOTTOM, window->FromDIP(SidebarProps::ContentMarginV())); m_standalone_add_btn = add_btn; } void PluginField::add_plugin_row(const wxString& value, bool is_last) { - const auto button_size = wxSize(def_width_thinner() * m_em_unit, -1); auto row_sizer = new wxBoxSizer(wxHORIZONTAL); - ScalableButton* select_btn = new ScalableButton(window, wxID_ANY, "search", wxEmptyString, - button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16); - wxGetApp().UpdateDarkUI(select_btn); - select_btn->SetToolTip(_L("Select plugin")); - - wxTextCtrl* display = new wxTextCtrl(window, wxID_ANY, value, - wxDefaultPosition, wxSize(def_width_wider() * m_em_unit, wxDefaultCoord), - wxTE_READONLY); - display->SetEditable(false); - wxGetApp().UpdateDarkUI(display); + ComboBox* display = new ComboBox(window, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY | CB_NO_DROP_ICON); + display->SetIcon("edit"); display->SetToolTip(get_tooltip_text(value)); - ScalableButton* remove_btn = nullptr; - if (!m_opt.readonly) { - remove_btn = new ScalableButton(window, wxID_ANY, "cross", wxEmptyString, - button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16); - wxGetApp().UpdateDarkUI(remove_btn); - remove_btn->SetToolTip(_L("Remove plugin")); - } + ScalableButton* remove_btn = new ScalableButton(window, wxID_ANY, "cross", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16); + remove_btn->SetToolTip(_L("Remove plugin")); - ScalableButton* add_btn = nullptr; - if (is_last && !m_opt.readonly) { - add_btn = new ScalableButton(window, wxID_ANY, "param_add", wxEmptyString, - button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16); - wxGetApp().UpdateDarkUI(add_btn); - add_btn->SetToolTip(_L("Add plugin")); - add_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_add_clicked(); }); - } + if (m_opt.readonly) + remove_btn->Disable(); const size_t row_index = m_rows.size(); - select_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_select_clicked(row_index); }); - if (remove_btn) - remove_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_remove_clicked(row_index); }); + display->Bind(wxEVT_LEFT_DOWN, [this, row_index](wxMouseEvent& ) { on_select_clicked(row_index); }); + remove_btn->Bind(wxEVT_BUTTON, [this, row_index](wxCommandEvent&) { on_remove_clicked(row_index); }); - row_sizer->Add(select_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - row_sizer->Add(display, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - if (remove_btn) - row_sizer->Add(remove_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - if (add_btn) - row_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL); - else if (!m_opt.readonly) { - // Reserve space equal to the add button so all rows align. - row_sizer->Add(button_size.GetWidth(), button_size.GetHeight(), 0, wxALIGN_CENTER_VERTICAL); - } + row_sizer->Add(display , 1, wxALIGN_CENTER_VERTICAL); + row_sizer->Add(remove_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, window->FromDIP(SidebarProps::ElementSpacing())); - const int bottom_gap = is_last ? 0 : 4; - m_main_sizer->Add(row_sizer, 0, wxEXPAND | (bottom_gap > 0 ? wxBOTTOM : 0), bottom_gap); + m_main_sizer->Add(row_sizer, 0, wxEXPAND | wxBOTTOM, window->FromDIP(is_last ? SidebarProps::ContentMarginV() : 4)); PluginRow row; - row.select_btn = select_btn; row.display = display; row.remove_btn = remove_btn; - row.add_btn = add_btn; row.sizer = row_sizer; m_rows.push_back(row); } @@ -2354,9 +2348,9 @@ void PluginField::on_add_clicked() m_values.push_back(selected); m_value = m_values; - rebuild_ui(); - - on_change_field(); + // Defer: don't destroy the clicked button from inside its own handler. + if(window) + window->CallAfter([this]() {rebuild_ui(); on_change_field();}); } void PluginField::on_remove_clicked(size_t index) @@ -2367,8 +2361,9 @@ void PluginField::on_remove_clicked(size_t index) m_values.erase(m_values.begin() + index); m_value = m_values; - rebuild_ui(); - on_change_field(); + // Defer: don't destroy the clicked button from inside its own handler. + if(window) + window->CallAfter([this]() {rebuild_ui(); on_change_field();}); } wxString PluginField::get_row_value(size_t index) const @@ -2382,7 +2377,7 @@ void PluginField::set_row_value(size_t index, const wxString& value) { if (index >= m_rows.size() || !m_rows[index].display) return; - m_rows[index].display->ChangeValue(value); + m_rows[index].display->SetValue(value); m_rows[index].display->SetToolTip(get_tooltip_text(value)); } @@ -2425,14 +2420,10 @@ boost::any& PluginField::get_value() void PluginField::enable() { for (auto& row : m_rows) { - if (row.select_btn) - row.select_btn->Enable(); if (row.display) row.display->Enable(); if (row.remove_btn) row.remove_btn->Enable(); - if (row.add_btn) - row.add_btn->Enable(); } if (m_standalone_add_btn) m_standalone_add_btn->Enable(); @@ -2441,14 +2432,10 @@ void PluginField::enable() void PluginField::disable() { for (auto& row : m_rows) { - if (row.select_btn) - row.select_btn->Disable(); if (row.display) row.display->Disable(); if (row.remove_btn) row.remove_btn->Disable(); - if (row.add_btn) - row.add_btn->Disable(); } if (m_standalone_add_btn) m_standalone_add_btn->Disable(); @@ -2863,11 +2850,11 @@ void PointCtrl::BUILD() //temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(y_input); - x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(x_textctrl); }), x_textctrl->GetId()); - y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(y_textctrl); }), y_textctrl->GetId()); + x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_input_value(x_textctrl); }), x_textctrl->GetId()); + y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_input_value(y_textctrl); }), y_textctrl->GetId()); - x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(x_textctrl); }), x_textctrl->GetId()); - y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(y_textctrl); }), y_textctrl->GetId()); + x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_input_value(x_textctrl); }), x_textctrl->GetId()); + y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_input_value(y_textctrl); }), y_textctrl->GetId()); // // recast as a wxWindow to fit the calling convention window = dynamic_cast(x_input); @@ -2916,7 +2903,7 @@ bool PointCtrl::value_was_changed(wxTextCtrl* win) return boost::any_cast(m_value) != boost::any_cast(val); } -void PointCtrl::propagate_value(wxTextCtrl* win) +void PointCtrl::propagate_input_value(wxTextCtrl* win) { if (win->GetValue().empty()) on_kill_focus(); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 5d5d549427..3f55bf5c5a 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -25,6 +25,7 @@ #include "wxExtensions.hpp" #include "Widgets/SpinInput.hpp" #include "Widgets/TextInput.hpp" +#include "Widgets/ComboBox.hpp" #ifdef __WXMSW__ #define wxMSW true @@ -532,10 +533,8 @@ public: private: struct PluginRow { - ScalableButton* select_btn { nullptr }; - wxTextCtrl* display { nullptr }; + ComboBox* display { nullptr }; ScalableButton* remove_btn { nullptr }; - ScalableButton* add_btn { nullptr }; wxBoxSizer* sizer { nullptr }; }; @@ -553,7 +552,7 @@ private: wxBoxSizer* m_main_sizer { nullptr }; std::vector m_rows; std::vector m_values; - ScalableButton* m_standalone_add_btn { nullptr }; + Button* m_standalone_add_btn { nullptr }; std::function m_selector; }; @@ -628,8 +627,10 @@ private: void on_button_click(wxCommandEvent &WXUNUSED(ev)); void save_colors_to_config(); private: +#if !defined(__linux__) && !defined(__LINUX__) wxColourData* m_clrData{nullptr}; wxColourPickerWidget* m_picker_widget{nullptr}; +#endif }; class PointCtrl : public Field { @@ -649,7 +650,7 @@ public: void BUILD() override; bool value_was_changed(wxTextCtrl* win); // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER - void propagate_value(wxTextCtrl* win); + void propagate_input_value(wxTextCtrl* win); void set_value(const Vec2d& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false) override; boost::any& get_value() override; diff --git a/src/slic3r/GUI/FilamentMapPanel.cpp b/src/slic3r/GUI/FilamentMapPanel.cpp index 81117bc2a6..0f3cc7217d 100644 --- a/src/slic3r/GUI/FilamentMapPanel.cpp +++ b/src/slic3r/GUI/FilamentMapPanel.cpp @@ -1,5 +1,6 @@ #include "FilamentMapPanel.hpp" #include "GUI_App.hpp" +#include "I18N.hpp" #include "Plater.hpp" #include "Widgets/MultiNozzleSync.hpp" // manuallySetNozzleCount producer for extruder_nozzle_stats #include diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 556faaa763..76192491bf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1078,56 +1078,36 @@ const double GLCanvas3D::DefaultCameraZoomToPlateMarginFactor = 1.25; void GLCanvas3D::load_arrange_settings() { - std::string dist_fff_str = - wxGetApp().app_config->get("arrange", "min_object_distance_fff"); + // Each key must match what _render_arrange_menu writes, which appends a per-mode + // postfix to the base name. + auto load_float = [](const char *key, float &out) { + // The menu writes these with float_to_string_decimal_point, so parse them back + // the same way rather than with anything locale-dependent. + std::string value = wxGetApp().app_config->get("arrange", key); + size_t parsed = 0; + double number = string_to_double_decimal_point(value, &parsed); + if (parsed > 0) + out = float(number); + }; + auto load_bool = [](const char *key, bool &out) { + std::string value = wxGetApp().app_config->get("arrange", key); + if (!value.empty()) + out = (value == "1" || value == "true"); + }; - std::string dist_fff_seq_print_str = - wxGetApp().app_config->get("arrange", "min_object_distance_seq_print_fff"); + load_float("min_object_distance_fff", m_arrange_settings_fff.distance); + load_float("min_object_distance_fff_seq_print", m_arrange_settings_fff_seq_print.distance); + load_float("min_object_distance_sla", m_arrange_settings_sla.distance); - std::string dist_sla_str = - wxGetApp().app_config->get("arrange", "min_object_distance_sla"); + load_bool("enable_rotation_fff", m_arrange_settings_fff.enable_rotation); + load_bool("enable_rotation_fff_seq_print", m_arrange_settings_fff_seq_print.enable_rotation); + load_bool("enable_rotation_sla", m_arrange_settings_sla.enable_rotation); - std::string en_rot_fff_str = - wxGetApp().app_config->get("arrange", "enable_rotation_fff"); - - std::string en_rot_fff_seqp_str = - wxGetApp().app_config->get("arrange", "enable_rotation_seq_print"); - - std::string en_rot_sla_str = - wxGetApp().app_config->get("arrange", "enable_rotation_sla"); - - std::string en_allow_multiple_materials_str = - wxGetApp().app_config->get("arrange", "allow_multi_materials_on_same_plate"); - - std::string en_avoid_region_str = - wxGetApp().app_config->get("arrange", "avoid_extrusion_cali_region"); - - - - if (!dist_fff_str.empty()) - m_arrange_settings_fff.distance = std::stof(dist_fff_str); - - if (!dist_fff_seq_print_str.empty()) - m_arrange_settings_fff_seq_print.distance = std::stof(dist_fff_seq_print_str); - - if (!dist_sla_str.empty()) - m_arrange_settings_sla.distance = std::stof(dist_sla_str); - - if (!en_rot_fff_str.empty()) - m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "true"); - - if (!en_allow_multiple_materials_str.empty()) - m_arrange_settings_fff.allow_multi_materials_on_same_plate = (en_allow_multiple_materials_str == "1" || en_allow_multiple_materials_str == "true"); - - - if (!en_rot_fff_seqp_str.empty()) - m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "true"); - - if(!en_avoid_region_str.empty()) - m_arrange_settings_fff.avoid_extrusion_cali_region = (en_avoid_region_str == "1" || en_avoid_region_str == "true"); - - if (!en_rot_sla_str.empty()) - m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "true"); + // These two keys carry no postfix, so the one stored value covers both FFF modes. + load_bool("allow_multi_materials_on_same_plate", m_arrange_settings_fff.allow_multi_materials_on_same_plate); + load_bool("allow_multi_materials_on_same_plate", m_arrange_settings_fff_seq_print.allow_multi_materials_on_same_plate); + load_bool("avoid_extrusion_cali_region", m_arrange_settings_fff.avoid_extrusion_cali_region); + load_bool("avoid_extrusion_cali_region", m_arrange_settings_fff_seq_print.avoid_extrusion_cali_region); //BBS: add specific arrange settings m_arrange_settings_fff_seq_print.is_seq_print = true; @@ -2118,12 +2098,6 @@ void GLCanvas3D::render(bool only_init) _render_selection_center(); #endif // ENABLE_RENDER_SELECTION_CENTER - // we need to set the mouse's scene position here because the depth buffer - // could be invalidated by the following gizmo render methods - // this position is used later into on_mouse() to drag the objects - if (m_picking_enabled) - m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); - // sidebar hints need to be rendered before the gizmos because the depth buffer // could be invalidated by the following gizmo render methods _render_selection_sidebar_hints(); @@ -4511,12 +4485,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box(); volume_bbox.offset(1.0); const bool is_cut_connector_selected = m_selection.is_any_connector(); - if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position) && !is_cut_connector_selected) { + const Vec3d scene_position = _mouse_to_3d(pos); + if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(scene_position) && !is_cut_connector_selected) { m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None; // The dragging operation is initiated. m_mouse.drag.move_volume_idx = volume_idx; m_selection.setup_cache(); - m_mouse.drag.start_position_3D = m_mouse.scene_position; + m_mouse.drag.start_position_3D = scene_position; m_sequential_print_clearance_first_displacement = true; m_moving = true; @@ -5059,7 +5034,21 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) } //BBS: notify instance updates to part plater list - m_selection.notify_instance_update(-1, 0); + // Only what moved: the selected instances, or every instance of an object one of whose + // parts moved. Notifying a plate about an instance that stayed put invalidates its slice + // result, and notifying instance 0 alone left a moved copy unregistered on its new plate. + { + std::set> notified; + for (unsigned int i : m_selection.get_volume_idxs()) { + const GLVolume* v = m_volumes.volumes[i]; + const int object_idx = v->object_idx(); + if (object_idx < 0 || object_idx >= static_cast(m_model->objects.size())) + continue; + const std::pair key(object_idx, selection_mode == Selection::Volume ? -1 : v->instance_idx()); + if (notified.insert(key).second) + m_selection.notify_instance_update(key.first, key.second); + } + } // Fixes sinking/flying instances (snaps object to buildplate) for (const std::pair& i : done) { @@ -5945,7 +5934,7 @@ bool GLCanvas3D::_render_orient_menu(float left, float right, float bottom, floa } //BBS: GUI refactor: adjust main toolbar position -bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, float top) +void GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, float top) { ImGuiWrapper *imgui = wxGetApp().imgui(); @@ -5970,7 +5959,6 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - ArrangeSettings settings = get_arrange_settings(); ArrangeSettings &settings_out = get_arrange_settings(); const float slider_icon_width = imgui->get_slider_icon_size().x; const float cursor_slider_left = imgui->calc_text_size(_L("Spacing")).x + imgui->scaled(1.5f); @@ -5979,13 +5967,9 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo auto &appcfg = wxGetApp().app_config; PrinterTechnology ptech = current_printer_technology(); - bool settings_changed = false; - float dist_min = 0.f; // 0 means auto std::string dist_key = "min_object_distance", rot_key = "enable_rotation"; - std::string bed_shrink_x_key = "bed_shrink_x", bed_shrink_y_key = "bed_shrink_y"; std::string multi_material_key = "allow_multi_materials_on_same_plate"; std::string avoid_extrusion_key = "avoid_extrusion_cali_region"; - std::string align_to_y_axis_key = "align_to_y_axis"; std::string postfix; //BBS: bool seq_print = false; @@ -5993,59 +5977,41 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo if (ptech == ptSLA) { postfix = "_sla"; } else if (ptech == ptFFF) { - seq_print = &settings == &m_arrange_settings_fff_seq_print; - if (seq_print) { - postfix = "_fff_seq_print"; - } else { - postfix = "_fff"; - } + seq_print = wxGetApp().global_print_sequence() == PrintSequence::ByObject; + postfix = seq_print ? "_fff_seq_print" : "_fff"; } dist_key += postfix; rot_key += postfix; - bed_shrink_x_key += postfix; - bed_shrink_y_key += postfix; ImGui::AlignTextToFramePadding(); imgui->text(_L("Spacing")); ImGui::SameLine(1.2 * cursor_slider_left); ImGui::PushItemWidth(window_width - slider_icon_width); - bool b_Spacing = imgui->bbl_slider_float_style("##Spacing", &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance; + bool b_Spacing = imgui->bbl_slider_float_style("##Spacing", &settings_out.distance, 0.f, 100.0f, "%5.2f", 1.0f, /*clamp=*/false); ImGui::SameLine(window_width - slider_icon_width + 1.3 * cursor_slider_left); ImGui::PushItemWidth(1.5 * slider_icon_width); - bool b_spacing_input = ImGui::BBLDragFloat("##spacing_input", &settings.distance, 0.05f, 0.0f, 0.0f, "%.2f"); - if (b_Spacing || b_spacing_input) - { - settings.distance = std::max(dist_min, settings.distance); - settings_out.distance = settings.distance; + bool b_spacing_input = ImGui::BBLDragFloat("##spacing_input", &settings_out.distance, 0.05f, 0.0f, 0.0f, "%.2f"); + if (b_Spacing || b_spacing_input) { + settings_out.distance = std::max(0.f, settings_out.distance); appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); - settings_changed = true; } imgui->text(_L("0 means auto spacing.")); ImGui::Separator(); - if (imgui->bbl_checkbox(_L("Auto rotate for arrangement"), settings.enable_rotation)) { - settings_out.enable_rotation = settings.enable_rotation; + if (imgui->bbl_checkbox(_L("Auto rotate for arrangement"), settings_out.enable_rotation)) appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation); - settings_changed = true; - } - if (imgui->bbl_checkbox(_L("Allow multiple materials on same plate"), settings.allow_multi_materials_on_same_plate)) { - settings_out.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate; - appcfg->set("arrange", multi_material_key.c_str(), settings_out.allow_multi_materials_on_same_plate ); - settings_changed = true; - } + if (imgui->bbl_checkbox(_L("Allow multiple materials on same plate"), settings_out.allow_multi_materials_on_same_plate)) + appcfg->set("arrange", multi_material_key.c_str(), settings_out.allow_multi_materials_on_same_plate); // only show this option if the printer has micro Lidar and can do first layer scan DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; const bool has_lidar = wxGetApp().preset_bundle->is_bbl_vendor(); auto op = current_config.option("scan_first_layer"); if (has_lidar && op && op->getBool()) { - if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings.avoid_extrusion_cali_region)) { - settings_out.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region; - appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region ? "1" : "0"); - settings_changed = true; - } + if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings_out.avoid_extrusion_cali_region)) + appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region); } else { settings_out.avoid_extrusion_cali_region = false; } @@ -6057,11 +6023,7 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo settings_out.align_to_y_axis = false; } - if (imgui->bbl_checkbox(_L("Align to Y axis"), settings.align_to_y_axis)) { - settings_out.align_to_y_axis = settings.align_to_y_axis; - appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0"); - settings_changed = true; - } + imgui->bbl_checkbox(_L("Align to Y axis"), settings_out.align_to_y_axis); if (settings_out.enable_rotation == true) { imgui->disabled_end(); } } @@ -6077,7 +6039,6 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo if (imgui->button(_L("Reset"))) { settings_out = ArrangeSettings{}; - settings_out.distance = std::max(dist_min, settings_out.distance); //BBS: add specific arrange settings if (seq_print) settings_out.is_seq_print = true; @@ -6087,18 +6048,16 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo else settings_out.align_to_y_axis = false; - appcfg->set("arrange", dist_key, float_to_string_decimal_point(settings_out.distance)); - appcfg->set("arrange", rot_key, settings_out.enable_rotation ? "1" : "0"); - appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0"); - settings_changed = true; + appcfg->erase("arrange", dist_key); + appcfg->erase("arrange", rot_key); + appcfg->erase("arrange", multi_material_key); + appcfg->erase("arrange", avoid_extrusion_key); } ImGui::PopStyleVar(1); imgui->end(); //BBS ImGuiWrapper::pop_toolbar_style(); - - return settings_changed; } static const float cameraProjection[16] = {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 84dbd5d652..c2962c3858 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -337,7 +337,6 @@ class GLCanvas3D bool dragging{ false }; Vec2d position{ DBL_MAX, DBL_MAX }; - Vec3d scene_position{ DBL_MAX, DBL_MAX, DBL_MAX }; bool ignore_left_up{ false }; Drag drag; bool ignore_right_up; @@ -656,11 +655,7 @@ public: } void load_arrange_settings(); - ArrangeSettings& get_arrange_settings();// { return get_arrange_settings(this); } - ArrangeSettings& get_arrange_settings(PrintSequence print_seq) { - return (print_seq == PrintSequence::ByObject) ? m_arrange_settings_fff_seq_print - : m_arrange_settings_fff; - } + ArrangeSettings& get_arrange_settings(); class SequentialPrintClearance { @@ -1163,17 +1158,6 @@ public: void highlight_toolbar_item(const std::string& item_name); void highlight_gizmo(const std::string& gizmo_name); - ArrangeSettings get_arrange_settings() const { - const ArrangeSettings &settings = get_arrange_settings(); - ArrangeSettings ret = settings; - if (&settings == &m_arrange_settings_fff_seq_print) { - ret.distance = std::max(ret.distance, - float(min_object_distance(*m_config))); - } - - return ret; - } - // Timestamp for FPS calculation and notification fade-outs. static int64_t timestamp_now() { #ifdef _WIN32 @@ -1308,7 +1292,7 @@ private: void _render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field, m_gizmos.get_uniform_scaling()); } //BBS: GUI refactor: adjust main toolbar position bool _render_orient_menu(float left, float right, float bottom, float top); - bool _render_arrange_menu(float left, float right, float bottom, float top); + void _render_arrange_menu(float left, float right, float bottom, float top); void _render_3d_navigator(); void _update_volumes_hover_state(); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index d670181b1b..fbdb308c56 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -9,6 +9,7 @@ #include "3DScene.hpp" #include "OpenGLManager.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" #include "GLModel.hpp" #include @@ -31,6 +32,7 @@ #include "GUI_App.hpp" #include #include +#include namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 99a829bdcc..966cf49013 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3,6 +3,14 @@ #include "libslic3r/Technologies.hpp" #include "libslic3r/Platform.hpp" #include "GUI_App.hpp" +#include "BindDialog.hpp" +#include "DeviceManager.hpp" +#include "HMS.hpp" +#include "PresetBundleDialog.hpp" +#include "WebUserLoginDialog.hpp" +#include "WebViewDialog.hpp" +#include "slic3r/Utils/BBLCloudServiceAgent.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "GUI_Init.hpp" #include "GUI_ObjectList.hpp" #include "slic3r/GUI/UserManager.hpp" @@ -598,7 +606,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } #ifdef WIN32 -static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; static void register_win32_device_notification_event() { @@ -7769,21 +7777,6 @@ void GUI_App::stop_http_server() m_http_server.stop(); } -void GUI_App::switch_staff_pick(bool on) -{ - mainframe->m_webview->SendDesignStaffpick(on); -} - -bool GUI_App::switch_language() -{ - if (select_language()) { - recreate_GUI(_L("Switching application language") + dots); - return true; - } else { - return false; - } -} - #ifdef __linux__ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, const wxLanguageInfo* system_language) @@ -7878,72 +7871,6 @@ int GUI_App::GetSingleChoiceIndex(const wxString& message, #endif } -// select language from the list of installed languages -bool GUI_App::select_language() -{ - wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < translations.GetCount(); ++ i) { - const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); - if (langinfo != nullptr) - language_infos.emplace_back(langinfo); - } - sort_remove_duplicates(language_infos); - std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); - - wxArrayString names; - names.Alloc(language_infos.size()); - - // Some valid language should be selected since the application start up. - const wxString active_language_code = current_language_code(); - const wxLanguageInfo* active_language_info = wxLocale::FindLanguageInfo(active_language_code); - const wxLanguage current_language = active_language_info != nullptr ? wxLanguage(active_language_info->Language) : wxLanguage(m_wxLocale->GetLanguage()); - const wxString active_lang_prefix = active_language_code.BeforeFirst('_'); - int init_selection = -1; - int init_selection_alt = -1; - int init_selection_default = -1; - for (size_t i = 0; i < language_infos.size(); ++ i) { - if (wxLanguage(language_infos[i]->Language) == current_language) - // The dictionary matches the active language and country. - init_selection = i; - else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == active_lang_prefix) || - // if the active language is Slovak, mark the Czech language as active. - (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && active_lang_prefix == "sk")) - // The dictionary matches the active language, it does not necessarily match the country. - init_selection_alt = i; - if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") - // This will be the default selection if the active language does not match any dictionary. - init_selection_default = i; - names.Add(language_infos[i]->Description); - } - if (init_selection == -1) - // This is the dictionary matching the active language. - init_selection = init_selection_alt; - if (init_selection != -1) - // This is the language to highlight in the choice dialog initially. - init_selection_default = init_selection; - - const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); - // Try to load a new language. - if (index != -1 && (init_selection == -1 || init_selection != index)) { - const wxLanguageInfo *new_language_info = language_infos[index]; - if (this->load_language(new_language_info->CanonicalName, false)) { - // Save language at application config. - // Which language to save as the selected dictionary language? - // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its - // stability in the future: - // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - // 2) Current locale language may not match the dictionary name, see GH issue #3901 - // m_wxLocale->GetCanonicalName() - // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. - app_config->set("language", new_language_info->CanonicalName.ToUTF8().data()); - return true; - } - } - - return false; -} // Load gettext translation files and activate them at the start of the application, // based on the "language" key stored in the application config. @@ -8330,146 +8257,6 @@ void GUI_App::show_ip_address_enter_dialog_handler(wxCommandEvent& evt) show_modal_ip_address_enter_dialog(mode == -1?false:true, title); } -//void GUI_App::add_config_menu(wxMenuBar *menu) -//void GUI_App::add_config_menu(wxMenu *menu) -//{ -// auto local_menu = new wxMenu(); -// wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); -// -// const auto config_wizard_name = _(ConfigWizard::name(true)); -// const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Open %s"))) % config_wizard_name).str()); -// // Cmd+, is standard on OS X - what about other operating systems? -// if (is_editor()) { -// local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); -// local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for Configuration Updates"), _L("Check for configuration updates")); -// local_menu->AppendSeparator(); -// } -// local_menu->Append(config_id_base + ConfigMenuPreferences, _L("Preferences") + dots + -//#ifdef __APPLE__ -// "\tCtrl+,", -//#else -// "\tCtrl+P", -//#endif -// _L("Application preferences")); -// wxMenu* mode_menu = nullptr; -// if (is_editor()) { -// local_menu->AppendSeparator(); -// mode_menu = new wxMenu(); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced Mode")); -// Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); -// Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); -// -// local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s Mode"), SLIC3R_APP_NAME)); -// } -// local_menu->AppendSeparator(); -// local_menu->Append(config_id_base + ConfigMenuLanguage, _L("Language")); -// if (is_editor()) { -// local_menu->AppendSeparator(); -// } -// -// local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { -// switch (event.GetId() - config_id_base) { -// case ConfigMenuWizard: -// run_wizard(ConfigWizard::RR_USER); -// break; -// case ConfigMenuUpdate: -// check_updates(true); -// break; -//#ifdef __linux__ -// case ConfigMenuDesktopIntegration: -// show_desktop_integration_dialog(); -// break; -//#endif -// case ConfigMenuSnapshots: -// //BBS do not support task snapshot -// break; -// case ConfigMenuPreferences: -// { -// //BBS GUI refactor: remove unuse layout logic -// //bool app_layout_changed = false; -// { -// // the dialog needs to be destroyed before the call to recreate_GUI() -// // or sometimes the application crashes into wxDialogBase() destructor -// // so we put it into an inner scope -// PreferencesDialog dlg(mainframe); -// dlg.ShowModal(); -// //BBS GUI refactor: remove unuse layout logic -// //app_layout_changed = dlg.settings_layout_changed(); -// if (dlg.seq_top_layer_only_changed()) -// this->plater_->refresh_print(); -// -// if (dlg.recreate_GUI()) { -// recreate_GUI(_L("Restart application") + dots); -// return; -// } -//#ifdef _WIN32 -// if (is_editor()) { -// if (app_config->get("associate_3mf") == "true") -// associate_3mf_files(); -// if (app_config->get("associate_stl") == "true") -// associate_stl_files(); -// } -// else { -// if (app_config->get("associate_gcode") == "true") -// associate_gcode_files(); -// } -//#endif // _WIN32 -// } -// //BBS GUI refactor: remove unuse layout logic -// /*if (app_layout_changed) { -// // hide full main_sizer for mainFrame -// mainframe->GetSizer()->Show(false); -// mainframe->update_layout(); -// mainframe->select_tab(size_t(0)); -// }*/ -// break; -// } -// case ConfigMenuLanguage: -// { -// /* Before change application language, let's check unsaved changes on 3D-Scene -// * and draw user's attention to the application restarting after a language change -// */ -// { -// // the dialog needs to be destroyed before the call to switch_language() -// // or sometimes the application crashes into wxDialogBase() destructor -// // so we put it into an inner scope -// wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); -// title += " - " + _L("Choose language"); -// //wxMessageDialog dialog(nullptr, -// MessageDialog dialog(nullptr, -// _L("Switching the language requires application restart.\n") + "\n\n" + -// _L("Do you want to continue?"), -// title, -// wxICON_QUESTION | wxOK | wxCANCEL); -// if (dialog.ShowModal() == wxID_CANCEL) -// return; -// } -// -// switch_language(); -// break; -// } -// case ConfigMenuFlashFirmware: -// //BBS FirmwareDialog::run(mainframe); -// break; -// default: -// break; -// } -// }); -// -// using std::placeholders::_1; -// -// if (mode_menu != nullptr) { -// auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; -// mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); -// mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); -// } -// -// // BBS -// //menu->Append(local_menu, _L("Configuration")); -// menu->AppendSubMenu(local_menu, _L("Configuration")); -//} - void GUI_App::open_presetbundledialog(size_t open_on_tab, const std::string& highlight_option) { bool app_layout_changed = false; @@ -9418,7 +9205,7 @@ int GUI_App::filaments_cnt() const PrintSequence GUI_App::global_print_sequence() const { PrintSequence global_print_seq = PrintSequence::ByDefault; - auto curr_preset_config = preset_bundle->prints.get_edited_preset().config; + const auto &curr_preset_config = preset_bundle->prints.get_edited_preset().config; if (curr_preset_config.has("print_sequence")) global_print_seq = curr_preset_config.option>("print_sequence")->value; return global_print_seq; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 8bf32df64c..f6f0b81c92 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -1,23 +1,17 @@ #ifndef slic3r_GUI_App_hpp_ #define slic3r_GUI_App_hpp_ +#include #include #include #include "ActionRegistry.hpp" #include "ImGuiWrapper.hpp" #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" -#include "PresetBundleDialog.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" -#include "slic3r/GUI/DeviceManager.hpp" #include "slic3r/GUI/UserNotification.hpp" -#include "slic3r/Utils/NetworkAgent.hpp" -#include "slic3r/Utils/BBLCloudServiceAgent.hpp" -#include "slic3r/GUI/WebViewDialog.hpp" -#include "slic3r/GUI/WebUserLoginDialog.hpp" -#include "slic3r/GUI/BindDialog.hpp" -#include "slic3r/GUI/HMS.hpp" +#include "slic3r/Utils/CloudProvider.hpp" #include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp" #include "slic3r/GUI/HttpServer.hpp" #include "../Utils/PrintHost.hpp" @@ -64,9 +58,14 @@ class ModelObject; class Model; class UserManager; class DeviceManager; +class MachineObject; class NetworkAgent; +class IPrinterAgent; class TaskManager; +// Same typedef as in bambu_networking.hpp, so this header need not include it. +typedef std::function WasCancelledFn; + namespace GUI{ class RemovableDriveManager; @@ -85,6 +84,8 @@ class ParamsDialog; class HMSQuery; class ModelMallDialog; class PingCodeBindDialog; +class PresetBundleDialog; +class ZUserLogin; class NetworkErrorDialog; class PluginsDialog; class SpeedDialWebDialog; @@ -569,7 +570,6 @@ public: void start_http_server(const std::string& provider = ORCA_CLOUD_PROVIDER); void start_http_server(int port, const std::string& provider = ORCA_CLOUD_PROVIDER); void stop_http_server(); - void switch_staff_pick(bool on); void on_show_check_privacy_dlg(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void show_check_privacy_dlg(wxCommandEvent& evt); @@ -583,7 +583,6 @@ public: void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); void update_ui_from_settings(); - bool switch_language(); bool load_language(wxString language, bool initial); Tab* get_tab(Preset::Type type); @@ -801,7 +800,6 @@ private: bool window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); void window_pos_sanitize(wxTopLevelWindow* window); void window_pos_center(wxTopLevelWindow *window); - bool select_language(); // Dynamic printer agent selection - internal helpers for switch_printer_agent // and the plugin load/unload callbacks (init_plugin_gui_wiring). @@ -832,7 +830,7 @@ wxDECLARE_EVENT(EVT_UPDATE_BUNDLE_COMPLETE, wxCommandEvent); bool is_support_filament(int extruder_id, bool strict_check = true); bool is_soluble_filament(int extruder_id); // check if the filament for model is in the list -bool has_filaments(const std::vector& model_filaments); +bool has_filaments(const std::vector& model_filaments); } // namespace GUI } // Slic3r diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index d8e54978fa..05248c38df 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1392,7 +1392,7 @@ void MenuFactory::create_default_menu() { wxMenu* sub_menu_primitives = append_submenu_add_generic(&m_default_menu, ModelVolumeType::INVALID); wxMenu* sub_menu_handy = append_submenu_add_handy_model(&m_default_menu, ModelVolumeType::INVALID); -#ifdef __WINDOWS__ + append_submenu(&m_default_menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part", []() {return true; }, m_parent); append_submenu(&m_default_menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part", @@ -1400,15 +1400,6 @@ void MenuFactory::create_default_menu() append_menu_item(&m_default_menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models [](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", &m_default_menu, []() {return wxGetApp().plater()->can_add_model(); }, m_parent); -#else - append_submenu(&m_default_menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "", - []() {return true; }, m_parent); - append_submenu(&m_default_menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "", - []() {return true; }, m_parent); - append_menu_item(&m_default_menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models - [](wxCommandEvent&) { plater()->add_file(); }, "", &m_default_menu, - []() {return wxGetApp().plater()->can_add_model(); }, m_parent); -#endif m_default_menu.AppendSeparator(); @@ -1789,7 +1780,6 @@ void MenuFactory::create_plate_menu() wxMenu* sub_menu_primitives = append_submenu_add_generic(menu, ModelVolumeType::INVALID); wxMenu* sub_menu_handy = append_submenu_add_handy_model(menu, ModelVolumeType::INVALID); -#ifdef __WINDOWS__ append_submenu(menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "menu_add_part", []() {return true; }, m_parent); append_submenu(menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "menu_add_part", @@ -1797,15 +1787,7 @@ void MenuFactory::create_plate_menu() append_menu_item(menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models [](wxCommandEvent&) { plater()->add_file(); }, "menu_add_part", menu, []() {return wxGetApp().plater()->can_add_model(); }, m_parent); -#else - append_submenu(menu, sub_menu_primitives, wxID_ANY, _L("Add Primitive"), "", "", - []() {return true; }, m_parent); - append_submenu(menu, sub_menu_handy, wxID_ANY, _L("Add Handy models"), "", "", - []() {return true; }, m_parent); - append_menu_item(menu, wxID_ANY, _L("Add Models"), "", // ORCA: Add Models - [](wxCommandEvent&) { plater()->add_file(); }, "", menu, - []() {return wxGetApp().plater()->can_add_model(); }, m_parent); -#endif + append_menu_item_replace_all_with_stl(menu); diff --git a/src/slic3r/GUI/GUI_ObjectTable.cpp b/src/slic3r/GUI/GUI_ObjectTable.cpp index 35508c6113..a496eca6d3 100644 --- a/src/slic3r/GUI/GUI_ObjectTable.cpp +++ b/src/slic3r/GUI/GUI_ObjectTable.cpp @@ -2578,7 +2578,7 @@ void ObjectGridTable::OnSelectCell(int row, int col) return; m_panel->m_side_window->Freeze(); if (row == 0 || col == col_filaments) { - m_panel->m_object_settings->UpdateAndShow(row, false, false, false, nullptr, nullptr, std::string()); + m_panel->m_object_settings->UpdateAndShowRow(row, false, false, false, nullptr, nullptr, std::string()); } else { ObjectGridRow* grid_row = m_grid_data[row - 1]; @@ -2588,7 +2588,7 @@ void ObjectGridTable::OnSelectCell(int row, int col) //m_panel->m_object_settings->get_og()->set_name(GUI::from_u8(grid_row->name.value)); //m_panel->m_page_text->SetLabel(GUI::from_u8(grid_row->name.value)); - m_panel->m_object_settings->UpdateAndShow(row, true, is_object, false, object, grid_row->config, grid_col->category); + m_panel->m_object_settings->UpdateAndShowRow(row, true, is_object, false, object, grid_row->config, grid_col->category); std::vector object_volume_ids; ObjectVolumeID object_volume_id; diff --git a/src/slic3r/GUI/GUI_ObjectTableSettings.cpp b/src/slic3r/GUI/GUI_ObjectTableSettings.cpp index 2290018419..4cd272840f 100644 --- a/src/slic3r/GUI/GUI_ObjectTableSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectTableSettings.cpp @@ -463,7 +463,7 @@ void ObjectTableSettings::update_config_values(bool is_object, ModelObject* obje m_table->reload_cell_data(m_current_row, category); } -void ObjectTableSettings::UpdateAndShow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category) +void ObjectTableSettings::UpdateAndShowRow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category) { m_current_row = row; m_current_category = category; diff --git a/src/slic3r/GUI/GUI_ObjectTableSettings.hpp b/src/slic3r/GUI/GUI_ObjectTableSettings.hpp index 39e7e514e2..24e3d427a9 100644 --- a/src/slic3r/GUI/GUI_ObjectTableSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectTableSettings.hpp @@ -71,7 +71,7 @@ public: //return visible count int update_extra_column_visible_status(ConfigOptionsGroup* option_group, const std::vector& option_keys, ModelConfig* config); void update_config_values(bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& changed_opt_key = ""); - void UpdateAndShow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category); + void UpdateAndShowRow(int row, const bool show, bool is_object, bool is_multiple_selection, ModelObject* object, ModelConfig* config, const std::string& category); void ValueChanged(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category, const std::string& key); void resetAllValues(int row, bool is_object, ModelObject* object, ModelConfig* config, const std::string& category); void msw_rescale(); diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index bc66d90ffd..10dd29c9c1 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -69,6 +69,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std HANDLE handlesrc = nullptr; HANDLE handledst = nullptr; CopyFileResult ret = SUCCESS; + DWORD size = 0; + DWORD dwRead = 0, dwWrite = 0; handlesrc = CreateFile(src.wc_str(), GENERIC_READ, @@ -96,9 +98,8 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std goto __finished; } - DWORD size=GetFileSize(handlesrc,NULL); + size = GetFileSize(handlesrc,NULL); buff = new char[size+1]; - DWORD dwRead=0,dwWrite; result = ReadFile(handlesrc, buff, size, &dwRead, NULL); if (!result) { DWORD errCode = GetLastError(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index 012e62b2bc..99ad00fe55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -1,5 +1,7 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoAdvancedCut.hpp" +#include "slic3r/GUI/Widgets/ProgressDialog.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp index 904e7d0a07..6d9e810815 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBrimEars.cpp @@ -1,6 +1,7 @@ #include "GLGizmoBrimEars.hpp" #include #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "slic3r/GUI/GUI_App.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp index 6689fcdcea..d807e466c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp @@ -4,6 +4,7 @@ #include "libslic3r/Print.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index e21498163a..6f7d6fed58 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -1,4 +1,5 @@ #include "GLGizmoMeasure.hpp" +#include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp index 00d608b80a..75dca0855d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoUtils.cpp @@ -32,6 +32,8 @@ */ +using namespace std::string_view_literals; + namespace Slic3r::GUI::GLGizmoUtils { void render_tooltip_button( diff --git a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp index 7c4a2afd38..5d70fde833 100644 --- a/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp +++ b/src/slic3r/GUI/Gizmos/GizmoObjectManipulation.cpp @@ -702,7 +702,7 @@ bool GizmoObjectManipulation::reset_zero_button(ImGuiWrapper *imgui_wrapper, bo for (int i = 0; i < number; i++) { - char buf[3][64] = {0}; + char buf[3][64] = {}; float buf_size[3] = {0}; for (int j = 0; j < 3; j++) { ImGui::DataTypeFormatString(buf[j], IM_ARRAYSIZE(buf[j]), ImGuiDataType_Double, (void *) &vec[i][j], "%.2f"); diff --git a/src/slic3r/GUI/HttpServer.cpp b/src/slic3r/GUI/HttpServer.cpp index afdc46e9f0..ef26f20173 100644 --- a/src/slic3r/GUI/HttpServer.cpp +++ b/src/slic3r/GUI/HttpServer.cpp @@ -4,6 +4,10 @@ #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp" +#include "libslic3r/Thread.hpp" +#include + +using json = nlohmann::json; namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/IMSlider.cpp b/src/slic3r/GUI/IMSlider.cpp index c008963646..fa777b6a37 100644 --- a/src/slic3r/GUI/IMSlider.cpp +++ b/src/slic3r/GUI/IMSlider.cpp @@ -1,6 +1,7 @@ #include "IMSlider.hpp" #include "libslic3r/GCode.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" #include "NotificationManager.hpp" #include "Widgets/StateColor.hpp" #ifndef IMGUI_DEFINE_MATH_OPERATORS @@ -790,7 +791,7 @@ void IMSlider::draw_ticks(const ImRect& slideable_region) { void IMSlider::show_tooltip(const std::string tooltip) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 6 * m_scale, 3 * m_scale }); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, { 3 * m_scale }); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * m_scale); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::PushStyleColor(ImGuiCol_Border, { 0,0,0,0 }); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); diff --git a/src/slic3r/GUI/ImageDPIFrame.hpp b/src/slic3r/GUI/ImageDPIFrame.hpp index 817ef6be18..c22d296492 100644 --- a/src/slic3r/GUI/ImageDPIFrame.hpp +++ b/src/slic3r/GUI/ImageDPIFrame.hpp @@ -3,6 +3,8 @@ #include "GUI_App.hpp" #include "GUI_Utils.hpp" +#include +#include class wxStaticBitmap; namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/ImageGrid.cpp b/src/slic3r/GUI/ImageGrid.cpp index abef6f0f12..f6bf25d77c 100644 --- a/src/slic3r/GUI/ImageGrid.cpp +++ b/src/slic3r/GUI/ImageGrid.cpp @@ -521,7 +521,7 @@ void ImageGrid::render(wxDC& dc) if (!m_status_msg.IsEmpty()) { auto si = m_status_icon.GetBmpSize(); auto st = dc.GetMultiLineTextExtent(m_status_msg); - auto rect = wxRect{0, 0, max(st.x, si.x), si.y + 26 + st.y}.CenterIn(wxRect({0, 0}, size)); + auto rect = wxRect{0, 0, std::max(st.x, si.x), si.y + 26 + st.y}.CenterIn(wxRect({0, 0}, size)); dc.DrawBitmap(m_status_icon.bmp(), rect.x + (rect.width - si.x) / 2, rect.y); dc.SetTextForeground(wxColor(0x909090)); dc.DrawText(m_status_msg, rect.x + (rect.width - st.x) / 2, rect.GetBottom() - st.y); diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index bc68a3f788..28c5176cb1 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -114,7 +114,10 @@ namespace instance_check_internal if (my_instance_hash == other_instance_hash) { BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance"; orca_slicer_hwnd = hwnd; - ShowWindow(hwnd, SW_SHOWMAXIMIZED); + // Do not alter the window state when opening a file in the existing instance. + // A minimized window still needs restoring before it can receive focus. + if (IsIconic(hwnd)) + ShowWindow(hwnd, SW_RESTORE); SetForegroundWindow(hwnd); return false; } diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 5f26f1e48f..9bfb3e2500 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -87,7 +87,6 @@ private: std::condition_variable m_thread_stop_condition; mutable std::mutex m_thread_stop_mutex; bool m_stop{ false }; - bool m_start{ true }; // background thread method void listen(); diff --git a/src/slic3r/GUI/Jobs/BindJob.cpp b/src/slic3r/GUI/Jobs/BindJob.cpp index 61c430c6e6..76af712f63 100644 --- a/src/slic3r/GUI/Jobs/BindJob.cpp +++ b/src/slic3r/GUI/Jobs/BindJob.cpp @@ -3,6 +3,10 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/DeviceManager.hpp" +#include "slic3r/GUI/HMS.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/GUI/DeviceCore/DevManager.h" diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index 67ce02b476..d27b18f24b 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -1,4 +1,6 @@ #include "SendJob.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" diff --git a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp index 6cd88ac5d3..7090f0e2e0 100644 --- a/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp +++ b/src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp @@ -2,6 +2,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/Http.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 167b2b4cda..5f36323d6e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1591,7 +1591,7 @@ void MainFrame::register_win32_callbacks() //static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED }; //static GUID GUID_DEVINTERFACE_DISK = { 0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b }; //static GUID GUID_DEVINTERFACE_VOLUME = { 0x71a27cdd, 0x812a, 0x11d0, 0xbe, 0xc7, 0x08, 0x00, 0x2b, 0xe2, 0x09, 0x2f }; - static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; + static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } }; // Register USB HID (Human Interface Devices) notifications to trigger the 3DConnexion enumeration. DEV_BROADCAST_DEVICEINTERFACE NotificationFilter = { 0 }; @@ -1631,7 +1631,7 @@ void MainFrame::register_win32_callbacks() { static constexpr int device_count = 1; - RAWINPUTDEVICE devices[device_count] = { 0 }; + RAWINPUTDEVICE devices[device_count] = {}; // multi-axis mouse (SpaceNavigator, etc.) devices[0].usUsagePage = 0x01; devices[0].usUsage = 0x08; @@ -3275,98 +3275,9 @@ void MainFrame::init_menubar_as_editor() auto preference_item = new wxMenuItem(parent_menu, ConfigMenuPreferences + config_id_base, _L("Preferences") + "\t" + ctrl + "P", ""); #endif - //auto printer_item = new wxMenuItem(parent_menu, ConfigMenuPrinter + config_id_base, _L("Printer"), ""); - //auto language_item = new wxMenuItem(parent_menu, ConfigMenuLanguage + config_id_base, _L("Switch Language"), ""); -// parent_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) { -// switch (event.GetId() - config_id_base) { -// //case ConfigMenuLanguage: -// //{ -// // /* Before change application language, let's check unsaved changes on 3D-Scene -// // * and draw user's attention to the application restarting after a language change -// // */ -// // { -// // // the dialog needs to be destroyed before the call to switch_language() -// // // or sometimes the application crashes into wxDialogBase() destructor -// // // so we put it into an inner scope -// // wxString title = _L("Language selection"); -// // wxMessageDialog dialog(nullptr, -// // _L("Switching the language requires application restart.\n") + "\n\n" + -// // _L("Do you want to continue?"), -// // title, -// // wxICON_QUESTION | wxOK | wxCANCEL); -// // if (dialog.ShowModal() == wxID_CANCEL) -// // return; -// // } -// -// // wxGetApp().switch_language(); -// // break; -// //} -// //case ConfigMenuWizard: -// //{ -// // wxGetApp().run_wizard(ConfigWizard::RR_USER); -// // break; -// //} -// case ConfigMenuPrinter: -// { -// wxGetApp().params_dialog()->Popup(); -// wxGetApp().get_tab(Preset::TYPE_PRINTER)->restore_last_select_item(); -// break; -// } -// case ConfigMenuPreferences: -// { -// CallAfter([this] { -// PreferencesDialog dlg(this); -// dlg.ShowModal(); -//#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER -// if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -//#else -// if (dlg.seq_top_layer_only_changed()) -//#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER -// plater()->refresh_print(); -//#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN -//#ifdef _WIN32 -// /* -// if (wxGetApp().app_config()->get("associate_3mf") == "true") -// wxGetApp().associate_3mf_files(); -// if (wxGetApp().app_config()->get("associate_stl") == "true") -// wxGetApp().associate_stl_files(); -// /*if (wxGetApp().app_config()->get("associate_step") == "true") -// wxGetApp().associate_step_files();*/ -//#endif // _WIN32 -//#endif -// }); -// break; -// } -// default: -// break; -// } -// }); #ifdef __APPLE__ wxString about_title = wxString::Format(_L("&About %s"), SLIC3R_APP_FULL_NAME); - //auto about_item = new wxMenuItem(parent_menu, OrcaSlicerMenuAbout + bambu_studio_id_base, about_title, ""); - //parent_menu->Bind(wxEVT_MENU, [this, bambu_studio_id_base](wxEvent& event) { - // switch (event.GetId() - bambu_studio_id_base) { - // case OrcaSlicerMenuAbout: - // Slic3r::GUI::about(); - // break; - // case OrcaSlicerMenuPreferences: - // CallAfter([this] { - // PreferencesDialog dlg(this); - // dlg.ShowModal(); - //#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - // if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) - //#else - // if (dlg.seq_top_layer_only_changed()) - //#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - // plater()->refresh_print(); - // }); - // break; - // default: - // break; - // } - //}); - //parent_menu->Insert(0, about_item); append_menu_item( parent_menu, wxID_ANY, _L(about_title), "", [](wxCommandEvent &) { Slic3r::GUI::about();}, diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 44e0547f50..5340115609 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -66,6 +66,7 @@ class Tab; class PrintHostQueueDialog; class Plater; class MainFrame; +class WebViewPanel; class ParamsDialog; #ifdef __WXGTK__ class ResizeEdgePanel; diff --git a/src/slic3r/GUI/MediaFilePanel.cpp b/src/slic3r/GUI/MediaFilePanel.cpp index 36316f8ff5..e9e1f56b03 100644 --- a/src/slic3r/GUI/MediaFilePanel.cpp +++ b/src/slic3r/GUI/MediaFilePanel.cpp @@ -2,6 +2,9 @@ #include "ImageGrid.h" #include "I18N.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" +#include "DeviceManager.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "Plater.hpp" #include "Widgets/Button.hpp" #include "Widgets/SwitchButton.hpp" diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 29c8c9f664..557d859cf7 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -3,6 +3,11 @@ #include "Widgets/CheckBox.hpp" #include "Widgets/Label.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" +#include "DeviceManager.hpp" +#include "DeviceCore/DevConfigUtil.h" +#include "slic3r/Utils/NetworkAgent.hpp" +#include "libslic3r/Thread.hpp" #include "libslic3r/AppConfig.hpp" #include "I18N.hpp" #include "MsgDialog.hpp" @@ -13,6 +18,7 @@ #include #include #include +#include #include #undef pid_t #include diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index bc6c60a360..173c5d2f13 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -297,7 +297,7 @@ void MeshClipper::recalculate_triangles() // it so it lies on our line. This will be the figure to subtract // from the cut. The coordinates must not overflow after the transform, // make the rectangle a bit smaller. - const coord_t size = (std::numeric_limits::max()/2 - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4; + const coord_t size = (double(std::numeric_limits::max()/2) - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4; Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; ep.front().rotate(angle); ep.front().translate(scale_(-e * a), scale_(-e * b)); @@ -352,7 +352,7 @@ void MeshClipper::recalculate_triangles() // To prevent overflow after scaling, downscale the input if needed: double extra_scale = 1.; - coord_t limit = coord_t(std::min(std::numeric_limits::max() / (2. * std::max(1., scale_x)), std::numeric_limits::max() / (2. * std::max(1., scale_y)))); + coord_t limit = coord_t(std::min(double(std::numeric_limits::max()) / (2. * std::max(1., scale_x)), double(std::numeric_limits::max()) / (2. * std::max(1., scale_y)))); coord_t max_coord = 0; for (const Point& pt : exp.contour) max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y()))); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 8ed91d461f..0317342412 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" #include "Mouse3DController.hpp" +#include "GUI.hpp" #include "Camera.hpp" #include "GUI_App.hpp" diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 25c13c4b8d..99151ca599 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -698,10 +698,15 @@ std::string OptionsGroup::pick_plugin(const ConfigOptionDef& opt) Slic3r::PluginManager& manager = Slic3r::PluginManager::instance(); const Slic3r::PluginCapabilityType plugin_type = Slic3r::plugin_capability_type_from_string(opt.plugin_type); if (plugin_type == Slic3r::PluginCapabilityType::Unknown) { - const std::string message = opt.plugin_type.empty() - ? "This setting does not specify a plugin capability type." - : "This setting specifies an unrecognized plugin capability type: '" + opt.plugin_type + "'."; - wxMessageBox(from_u8(message), _L("Plugin Selection"), wxOK | wxICON_WARNING, m_parent); + MessageDialog dlg(m_parent, + opt.plugin_type.empty() ? _L("This setting does not specify a plugin capability type.") + : _L("This setting specifies an unrecognized plugin capability type: ") + "'" + opt.plugin_type + "'.", + _L("Plugin Selection"), + wxOK | wxICON_WARNING + ); + dlg.CenterOnParent(); + dlg.ShowModal(); + return {}; } @@ -714,7 +719,13 @@ std::string OptionsGroup::pick_plugin(const ConfigOptionDef& opt) }); if (caps.empty()) { - wxMessageBox(_L("No plugins capabilities available for this type.\nEnable or install some to use."), _L("Plugin Selection"), wxOK | wxICON_INFORMATION, m_parent); + MessageDialog dlg(m_parent, + _L("No plugins capabilities available for this type.\nEnable or install some to use."), + _L("Plugin Selection"), + wxOK | wxICON_INFORMATION + ); + dlg.CenterOnParent(); + dlg.ShowModal(); return {}; } @@ -787,11 +798,9 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, #endif else if (opt_key == "printer_agent") { - // why: printer_agent is a coString kept out of m_opt_map. The generic non-opt_map revert - // below restores the edited config from get_value(), but a deregistered/"(missing)" saved - // id has no selectable row, so the field yields no value and the edited config keeps the - // user's interim pick -> stuck dirty. Restore the SAVED id straight into the edited config - // (displayable or not; config is the saved or system baseline), then repaint and notify. + // A deregistered/"(missing)" saved id has no selectable row, so the field yields no + // value. Restore the saved id directly instead of letting the generic revert path read + // the field value back into the edited config. const std::string saved_id = config.opt_string("printer_agent"); set_value(opt_key, saved_id); this->change_opt_value(opt_key, saved_id); diff --git a/src/slic3r/GUI/ParamsPanel.hpp b/src/slic3r/GUI/ParamsPanel.hpp index 0726db91d3..91bf3d2a7e 100644 --- a/src/slic3r/GUI/ParamsPanel.hpp +++ b/src/slic3r/GUI/ParamsPanel.hpp @@ -66,7 +66,6 @@ class ParamsPanel : public wxPanel { #if __WXOSX__ wxWindow* m_tmp_panel; - int m_size_move = -1; #endif // __WXOSX__ private: diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 90e2c96ab6..893260f934 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "libslic3r/libslic3r.h" @@ -1112,7 +1113,7 @@ void PartPlate::show_tooltip(const std::string tooltip) { const auto scale = m_plater->get_current_canvas3D()->get_scale(); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {6 * scale, 3 * scale}); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, {3 * scale}); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3 * scale); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::PushStyleColor(ImGuiCol_Border, {0, 0, 0, 0}); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 1.00f)); @@ -1716,7 +1717,7 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode, const Dynam return plate_extruders; } -std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const +std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots) const { std::vector plate_extruders; @@ -1877,7 +1878,7 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D // Expand mixed filament slots to their physical components. A mixed slot is virtual and // is never loaded into a tray, so callers (AMS mapping, filament checks) must see the // physical filaments it resolves to instead. - { + if (expand_mixed_slots) { auto* is_mixed_opt = full_config.option("filament_is_mixed"); auto* comp_strs_opt = full_config.option("filament_mixed_components"); if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) { @@ -1917,7 +1918,7 @@ std::vector PartPlate::get_extruders_without_support(bool conside_custom_gc const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) { - if (!contain_instance_totally(obj_idx, 0)) + if (!contain_any_instance_totally(obj_idx)) continue; ModelObject* mo = m_model->objects[obj_idx]; @@ -2088,7 +2089,7 @@ bool PartPlate::check_single_extruder_mixed_filament_risk(const DynamicPrintConf "which may significantly increase waste and the risk of nozzle / waste-chute clogging."); for (int obj_idx = 0; obj_idx < (int)m_model->objects.size(); ++obj_idx) { - if (!contain_instance_totally(obj_idx, 0)) + if (!contain_any_instance_totally(obj_idx)) continue; ModelObject *mo = m_model->objects[obj_idx]; int obj_ext = mo->config.has("extruder") ? mo->config.extruder() : 1; @@ -2307,7 +2308,7 @@ bool PartPlate::check_compatible_of_nozzle_and_filament(const DynamicPrintConfig return wipe_tower_size; for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) { - if (!use_global_objects && !contain_instance_totally(obj_idx, 0)) + if (!use_global_objects && !contain_any_instance_totally(obj_idx)) continue; BoundingBoxf3 bbox = m_model->objects[obj_idx]->bounding_box(); diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 6d7eb18beb..e913ebaabf 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -350,7 +350,8 @@ public: // get used filaments from config, 1 based idx std::vector get_extruders(bool conside_custom_gcode = false) const; std::vector get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const; - std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const; + // expand_mixed_slots = false keeps mixed filament slots as slots instead of their components. + std::vector get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config, bool expand_mixed_slots = true) const; std::vector get_extruders_without_support(bool conside_custom_gcode = false) const; // get used filaments from gcode result, 1 based idx std::vector get_used_filaments(); diff --git a/src/slic3r/GUI/PartSkipDialog.cpp b/src/slic3r/GUI/PartSkipDialog.cpp index b9dd7d5007..9bd6687757 100644 --- a/src/slic3r/GUI/PartSkipDialog.cpp +++ b/src/slic3r/GUI/PartSkipDialog.cpp @@ -1,5 +1,6 @@ #include "GUI_Utils.hpp" #include "GUI_App.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include #include #include diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 989cf204e1..04317ca46b 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a28add0e4d..0fdb9dcd94 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -17839,6 +17839,10 @@ void Plater::increase_instances(size_t num) model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror()); // p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec)); } + // Register the copies with the plate they land on before the scene reloads: the plate's + // filament list and wipe tower preview are read from that registry. + for (size_t i = model_object->instances.size() - num; i < model_object->instances.size(); ++i) + p->partplate_list.notify_instance_update(obj_idx, static_cast(i)); #ifdef SUPPORT_AUTO_CENTER if (p->get_config("autocenter") == "true") @@ -17869,8 +17873,10 @@ void Plater::decrease_instances(size_t num) ModelObject* model_object = p->model.objects[obj_idx]; if (model_object->instances.size() > num) { - for (size_t i = 0; i < num; ++ i) + for (size_t i = 0; i < num; ++ i) { + p->partplate_list.notify_instance_removed(obj_idx, static_cast(model_object->instances.size()) - 1); model_object->delete_last_instance(); + } p->update(); // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model. sidebar().obj_list()->decrease_object_instances(obj_idx, num); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 26cd06978b..84e64acea0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -42,6 +42,7 @@ class Button; namespace Slic3r { class BuildVolume; +class MachineObject; enum class BuildVolume_Type : char; class Model; class ModelObject; diff --git a/src/slic3r/GUI/PluginPickerDialog.cpp b/src/slic3r/GUI/PluginPickerDialog.cpp index 194dbaaf07..5109b3b483 100644 --- a/src/slic3r/GUI/PluginPickerDialog.cpp +++ b/src/slic3r/GUI/PluginPickerDialog.cpp @@ -9,12 +9,16 @@ #include "GUI.hpp" #include "I18N.hpp" +#include "GUI_App.hpp" + +#include "Widgets/DialogButtons.hpp" + namespace Slic3r { namespace GUI { PluginPickerDialog::PluginPickerDialog(wxWindow* parent, const wxString& plugin_type_label, const std::vector& plugins) - : wxDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label)) + : DPIDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label)) , m_plugins(plugins) { build_ui(plugin_type_label); @@ -24,7 +28,7 @@ PluginPickerDialog::PluginPickerDialog(wxWindow* parent, PluginPickerDialog::PluginPickerDialog(wxWindow* parent, const wxString& plugin_type_label, std::vector capabilities) - : wxDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label)) + : DPIDialog(parent, wxID_ANY, wxString::Format(_L("Select %s Plugin"), plugin_type_label)) , m_capabilities(std::move(capabilities)) { build_capability_ui(plugin_type_label); @@ -33,12 +37,18 @@ PluginPickerDialog::PluginPickerDialog(wxWindow* parent, void PluginPickerDialog::build_ui(const wxString& plugin_type_label) { + SetBackgroundColour(*wxWHITE); + const bool has_plugins = !m_plugins.empty(); auto* top_sizer = new wxBoxSizer(wxVERTICAL); auto* info_text = new wxStaticText(this, wxID_ANY, wxString::Format(_L("Choose a %s plugin from the list below."), plugin_type_label)); - top_sizer->Add(info_text, 0, wxALL | wxEXPAND, 10); + info_text->SetFont(Label::Body_14); + info_text->SetForegroundColour(wxColour("#363636")); + top_sizer->Add(info_text, 0, wxALL | wxEXPAND, FromDIP(10)); + + top_sizer->AddSpacer(FromDIP(5)); wxArrayString choices; choices.reserve(m_plugins.size()); @@ -49,84 +59,103 @@ void PluginPickerDialog::build_ui(const wxString& plugin_type_label) choices.Add(label); } - m_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + m_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + for (const wxString &opt : choices) { m_choice->Append(opt); } + if (has_plugins) { m_choice->SetSelection(0); - m_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent& evt) { + m_choice->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { update_description(evt.GetSelection()); }); } else { m_choice->Enable(false); } - top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, 10); + top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(10)); m_description = new wxStaticText(this, wxID_ANY, wxEmptyString); + m_description->SetFont(Label::Body_14); + m_description->SetForegroundColour(wxColour("#363636")); m_description->Wrap(400); - top_sizer->Add(m_description, 0, wxALL | wxEXPAND, 10); + top_sizer->Add(m_description, 0, wxALL | wxEXPAND, FromDIP(10)); if (has_plugins) update_description(0); else m_description->SetLabel(_L("No plugins found for this type.")); - auto* button_sizer = new wxStdDialogButtonSizer(); - auto* ok_button = new wxButton(this, wxID_OK); - ok_button->Enable(has_plugins); - button_sizer->AddButton(ok_button); - button_sizer->AddButton(new wxButton(this, wxID_CANCEL)); - button_sizer->Realize(); + auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"}); - top_sizer->Add(button_sizer, 0, wxALL | wxALIGN_RIGHT, 10); + dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); }); + dlg_btns->GetOK()->Enable(has_plugins); + + dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); }); + + top_sizer->Add(dlg_btns, 0, wxEXPAND); SetSizerAndFit(top_sizer); + + wxGetApp().UpdateDlgDarkUI(this); } void PluginPickerDialog::build_capability_ui(const wxString& plugin_type_label) { + SetBackgroundColour(*wxWHITE); + const bool has_capabilities = !m_capabilities.empty(); auto* top_sizer = new wxBoxSizer(wxVERTICAL); auto* info_text = new wxStaticText(this, wxID_ANY, wxString::Format(_L("Choose a %s plugin from the list below."), plugin_type_label)); - top_sizer->Add(info_text, 0, wxALL | wxEXPAND, 10); + info_text->SetFont(Label::Body_14); + info_text->SetForegroundColour(wxColour("#363636")); + + top_sizer->Add(info_text, 0, wxALL | wxEXPAND, FromDIP(10)); + + top_sizer->AddSpacer(FromDIP(5)); wxArrayString choices; choices.reserve(m_capabilities.size()); for (const auto& cap : m_capabilities) choices.Add(cap.label); - m_choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + m_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + for (const wxString &opt : choices) { m_choice->Append(opt); } + if (has_capabilities) { m_choice->SetSelection(0); - m_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent& evt) { + m_choice->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { update_capability_description(evt.GetSelection()); }); } else { m_choice->Enable(false); } - top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, 10); + top_sizer->Add(m_choice, 0, wxLEFT | wxRIGHT | wxEXPAND, FromDIP(10)); m_description = new wxStaticText(this, wxID_ANY, wxEmptyString); + m_description->SetFont(Label::Body_14); + m_description->SetForegroundColour(wxColour("#363636")); m_description->Wrap(400); - top_sizer->Add(m_description, 0, wxALL | wxEXPAND, 10); + top_sizer->Add(m_description, 0, wxALL | wxEXPAND, FromDIP(10)); if (has_capabilities) update_capability_description(0); else m_description->SetLabel(_L("No plugins found for this type.")); - auto* button_sizer = new wxStdDialogButtonSizer(); - auto* ok_button = new wxButton(this, wxID_OK); - ok_button->Enable(has_capabilities); - button_sizer->AddButton(ok_button); - button_sizer->AddButton(new wxButton(this, wxID_CANCEL)); - button_sizer->Realize(); + auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"}); - top_sizer->Add(button_sizer, 0, wxALL | wxALIGN_RIGHT, 10); + dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); }); + dlg_btns->GetOK()->Enable(has_capabilities); + + dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); }); + + top_sizer->Add(dlg_btns, 0, wxEXPAND); SetSizerAndFit(top_sizer); + + wxGetApp().UpdateDlgDarkUI(this); } PluginPickerDialog::CapabilityEntry PluginPickerDialog::selected_capability() const @@ -185,4 +214,6 @@ void PluginPickerDialog::update_description(int selection) Layout(); } +void PluginPickerDialog::on_dpi_changed(const wxRect &suggested_rect) {} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PluginPickerDialog.hpp b/src/slic3r/GUI/PluginPickerDialog.hpp index 95f00689c3..6ae4163c5f 100644 --- a/src/slic3r/GUI/PluginPickerDialog.hpp +++ b/src/slic3r/GUI/PluginPickerDialog.hpp @@ -11,9 +11,12 @@ #include "slic3r/plugin/PluginManager.hpp" +#include "GUI_Utils.hpp" +#include "Widgets/ComboBox.hpp" + namespace Slic3r { namespace GUI { -class PluginPickerDialog : public wxDialog +class PluginPickerDialog : public DPIDialog { public: // Entry for capability-level selection (plugin_type non-empty path). @@ -40,13 +43,15 @@ public: // Returns the {plugin_key, name} of the selected capability (capability path). CapabilityEntry selected_capability() const; + void on_dpi_changed(const wxRect &suggested_rect) override; + private: void build_ui(const wxString& plugin_type_label); void build_capability_ui(const wxString& plugin_type_label); void update_description(int selection); void update_capability_description(int selection); - wxChoice* m_choice { nullptr }; + ComboBox* m_choice { nullptr }; wxStaticText* m_description { nullptr }; std::vector m_plugins; std::vector m_capabilities; diff --git a/src/slic3r/GUI/PluginsConfigDialog.cpp b/src/slic3r/GUI/PluginsConfigDialog.cpp index 0241b47b8c..9a79588af1 100644 --- a/src/slic3r/GUI/PluginsConfigDialog.cpp +++ b/src/slic3r/GUI/PluginsConfigDialog.cpp @@ -1,6 +1,7 @@ #include "PluginsConfigDialog.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" #include "I18N.hpp" #include "format.hpp" diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp index a39fcbc535..2bac7eddca 100644 --- a/src/slic3r/GUI/PluginsDialog.cpp +++ b/src/slic3r/GUI/PluginsDialog.cpp @@ -2,6 +2,7 @@ #include "GUI.hpp" #include "GUI_App.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" #include "I18N.hpp" #include "OrcaCloudServiceAgent.hpp" #include "slic3r/plugin/PluginConfig.hpp" diff --git a/src/slic3r/GUI/PluginsDialog.hpp b/src/slic3r/GUI/PluginsDialog.hpp index e663de79e4..ec68d12969 100644 --- a/src/slic3r/GUI/PluginsDialog.hpp +++ b/src/slic3r/GUI/PluginsDialog.hpp @@ -2,6 +2,7 @@ #define slic3r_PluginsDialog_hpp_ #include "Widgets/WebViewHostDialog.hpp" +#include "Widgets/ProgressDialog.hpp" #include "PluginSource.hpp" #include "PluginStatus.hpp" #include "PluginSort.hpp" @@ -107,12 +108,12 @@ private: const wxString& title, const wxString& message, int maximum = 100, - int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE, + int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE, // | wxPD_CAN_ABORT for cancel button bool finish_after_dialog_destroyed = false) { const auto alive = m_alive; - wxProgressDialog* progress = new wxProgressDialog(title, message, maximum, this, style); - wxTimer* timer = new wxTimer(); + ProgressDialog* progress = new ProgressDialog(title, message, maximum, this, style); + wxTimer* timer = new wxTimer(); timer->Bind(wxEVT_TIMER, [alive, progress, message](wxTimerEvent&) { if (alive->load(std::memory_order_acquire) && progress) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 08483f3100..73f3a2c90a 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -2,6 +2,7 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" +#include "WebViewDialog.hpp" #include "Plater.hpp" #include "GLCanvas3D.hpp" // ORCA: for live preview refresh when toggling "Dim lower layers" #include "MsgDialog.hpp" @@ -507,26 +508,14 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS } } - - // the dialog needs to be destroyed before the call to switch_language() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - MessageDialog msg_wingow(nullptr, _L("Switching languages requires the application to restart.\n") + "\n" + _L("Do you want to continue?"), - L("Language selection"), wxICON_QUESTION | wxOK | wxCANCEL); - if (msg_wingow.ShowModal() == wxID_CANCEL) { + MessageDialog msg_window(nullptr, _L("Switching languages requires the application to restart.\n") + "\n" + _L("Do you want to continue?"), + _L("Language selection"), wxICON_QUESTION | wxOK | wxCANCEL); + if (msg_window.ShowModal() == wxID_CANCEL) { combobox->SetSelection(m_current_language_selected); return; } } - auto check = [](bool yes_or_no) { - // if (yes_or_no) - // return true; - int act_btns = ActionButtons::SAVE; - return wxGetApp().check_and_keep_current_preset_changes(_L("Switching application language"), - _L("Switching application language while some presets are modified."), act_btns); - }; - m_current_language_selected = combobox->GetSelection(); if (m_current_language_selected >= 0 && m_current_language_selected < vlist.size()) { m_pending_language = vlist[m_current_language_selected]->CanonicalName.ToUTF8().data(); @@ -1031,11 +1020,6 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too app_config->set_bool(param, checkbox->GetValue()); app_config->save(); - // if (param == "staff_pick_switch") { - // bool pbool = app_config->get("staff_pick_switch") == "true"; - // wxGetApp().switch_staff_pick(pbool); - // } - if (param == "sync_user_preset") { bool sync = app_config->get("sync_user_preset") == "true" ? true : false; if (sync) { diff --git a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp index 8ec6909c8c..9aa35e2cff 100644 --- a/src/slic3r/GUI/Printer/PrinterFileSystem.cpp +++ b/src/slic3r/GUI/Printer/PrinterFileSystem.cpp @@ -1803,7 +1803,7 @@ static void* get_function(const char* name) return function; #if defined(_MSC_VER) || defined(_WIN32) - function = GetProcAddress(module, name); + function = reinterpret_cast(GetProcAddress(module, name)); #else function = dlsym(module, name); #endif diff --git a/src/slic3r/GUI/PrivacyUpdateDialog.cpp b/src/slic3r/GUI/PrivacyUpdateDialog.cpp index 92d6d6c8c7..c417767a42 100644 --- a/src/slic3r/GUI/PrivacyUpdateDialog.cpp +++ b/src/slic3r/GUI/PrivacyUpdateDialog.cpp @@ -1,5 +1,6 @@ #include "PrivacyUpdateDialog.hpp" #include "GUI_App.hpp" +#include "GUI.hpp" #include "BitmapCache.hpp" #include #include diff --git a/src/slic3r/GUI/PublishSettingsDialog.cpp b/src/slic3r/GUI/PublishSettingsDialog.cpp index 16b0b6201c..e63cec7615 100644 --- a/src/slic3r/GUI/PublishSettingsDialog.cpp +++ b/src/slic3r/GUI/PublishSettingsDialog.cpp @@ -1037,9 +1037,6 @@ size_t PublishSettingsDialog::section_group_for(Section kind) section.mixed_tabs = new TabCtrl(section.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, s_tab_style); section.mixed_tabs->SetFont(Label::Body_14); section.mixed_tabs->SetBackgroundColour(GetBackgroundColour()); - // The mixed tabs carry full swatch compositions: give them a touch more room than the - // filament tabs so neighbouring compositions stay distinguishable (must precede AppendItem). - section.mixed_tabs->SetItemSpace(FromDIP(3)); page_sizer->Add(section.mixed_tabs, 0, wxEXPAND | wxTOP, FromDIP(2)); section.mixed_tabs->Hide(); } diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 96cd3b65a7..29116b12cb 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -7,6 +7,7 @@ #include "GUI.hpp" #include "GUI_App.hpp" #include "I18N.hpp" +#include "Widgets/StateColor.hpp" wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); diff --git a/src/slic3r/GUI/ReleaseNote.hpp b/src/slic3r/GUI/ReleaseNote.hpp index 0c11dc2f58..cfd372bc97 100644 --- a/src/slic3r/GUI/ReleaseNote.hpp +++ b/src/slic3r/GUI/ReleaseNote.hpp @@ -35,6 +35,7 @@ #include "Widgets/CheckBox.hpp" #include "Widgets/ComboBox.hpp" #include "Widgets/ScrolledWindow.hpp" +#include "Widgets/HyperLink.hpp" #include #include diff --git a/src/slic3r/GUI/SelectMachinePop.hpp b/src/slic3r/GUI/SelectMachinePop.hpp index 76d38be522..e34a23708c 100644 --- a/src/slic3r/GUI/SelectMachinePop.hpp +++ b/src/slic3r/GUI/SelectMachinePop.hpp @@ -183,7 +183,9 @@ private: HyperLink* m_hyperlink{nullptr}; // ORCA wxBoxSizer * m_sizer_my_devices{nullptr}; wxBoxSizer * m_sizer_other_devices{nullptr}; +#if defined(__WINDOWS__) wxBoxSizer * m_sizer_search_bar{nullptr}; +#endif wxSearchCtrl* m_search_bar{nullptr}; wxScrolledWindow * m_scrolledWindow{nullptr}; wxTimer * m_refresh_timer{nullptr}; diff --git a/src/slic3r/GUI/SendMultiMachinePage.cpp b/src/slic3r/GUI/SendMultiMachinePage.cpp index 3a52caec3f..cafbb360e6 100644 --- a/src/slic3r/GUI/SendMultiMachinePage.cpp +++ b/src/slic3r/GUI/SendMultiMachinePage.cpp @@ -3,6 +3,7 @@ #include "I18N.hpp" #include "GUI_App.hpp" +#include "slic3r/Utils/bambu_networking.hpp" #include "MainFrame.hpp" #include "Widgets/RadioBox.hpp" #include @@ -814,13 +815,13 @@ wxBoxSizer* SendMultiMachinePage::create_item_title(wxString title, wxWindow* pa wxBoxSizer* m_sizer_title = new wxBoxSizer(wxHORIZONTAL); auto m_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - m_title->SetForegroundColour(DESIGN_GRAY800_COLOR); + m_title->SetForegroundColour(SEND_DESIGN_GRAY800_COLOR); m_title->SetFont(::Label::Head_13); m_title->Wrap(-1); m_title->SetToolTip(tooltip); auto m_line = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); - m_line->SetBackgroundColour(DESIGN_GRAY400_COLOR); + m_line->SetBackgroundColour(SEND_DESIGN_GRAY400_COLOR); m_sizer_title->Add(m_title, 0, wxALIGN_CENTER | wxALL, 3); m_sizer_title->Add(0, 0, 0, wxLEFT, 9); @@ -843,7 +844,7 @@ wxBoxSizer* SendMultiMachinePage::create_item_checkbox(wxString title, wxWindow* m_sizer_checkbox->Add(0, 0, 0, wxEXPAND | wxLEFT, 8); auto checkbox_title = new wxStaticText(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, 0); - checkbox_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + checkbox_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR); checkbox_title->SetFont(::Label::Body_13); auto size = checkbox_title->GetTextExtent(title); @@ -867,12 +868,12 @@ wxBoxSizer* SendMultiMachinePage::create_item_input(wxString str_before, wxStrin { wxBoxSizer* sizer_input = new wxBoxSizer(wxHORIZONTAL); auto input_title = new wxStaticText(parent, wxID_ANY, str_before); - input_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + input_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR); input_title->SetFont(::Label::Body_13); input_title->SetToolTip(tooltip); input_title->Wrap(-1); - auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); + auto input = new ::TextInput(parent, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, SEND_DESIGN_INPUT_SIZE, wxTE_PROCESS_ENTER); StateColor input_bg(std::pair(wxColour("#F0F0F1"), StateColor::Disabled), std::pair(*wxWHITE, StateColor::Enabled)); input->SetBackgroundColor(input_bg); input->GetTextCtrl()->SetValue(app_config->get(param)); @@ -880,7 +881,7 @@ wxBoxSizer* SendMultiMachinePage::create_item_input(wxString str_before, wxStrin input->GetTextCtrl()->SetValidator(validator); auto second_title = new wxStaticText(parent, wxID_ANY, str_after, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); - second_title->SetForegroundColour(DESIGN_GRAY900_COLOR); + second_title->SetForegroundColour(SEND_DESIGN_GRAY900_COLOR); second_title->SetFont(::Label::Body_13); second_title->SetToolTip(tooltip); second_title->Wrap(-1); @@ -1337,7 +1338,7 @@ wxPanel* SendMultiMachinePage::create_page() m_tip_text->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), -1)); m_tip_text->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), -1)); m_tip_text->SetLabel(_L("Please select the devices you would like to manage here (up to 6 devices)")); - m_tip_text->SetForegroundColour(DESIGN_GRAY800_COLOR); + m_tip_text->SetForegroundColour(SEND_DESIGN_GRAY800_COLOR); m_tip_text->SetFont(::Label::Head_20); m_tip_text->Wrap(-1); diff --git a/src/slic3r/GUI/SendMultiMachinePage.hpp b/src/slic3r/GUI/SendMultiMachinePage.hpp index 7d77849bf3..eadd78eb0a 100644 --- a/src/slic3r/GUI/SendMultiMachinePage.hpp +++ b/src/slic3r/GUI/SendMultiMachinePage.hpp @@ -15,6 +15,9 @@ #include "SelectMachine.hpp" namespace Slic3r { + +struct PrintParams; + namespace GUI { #define SEND_LEFT_PADDING_LEFT 15 #define SEND_LEFT_PRINTABLE 40 @@ -22,15 +25,15 @@ namespace GUI { #define SEND_LEFT_DEV_STATUS 250 #define SEND_LEFT_TAKS_STATUS 180 -#define DESIGN_SELECTOR_NOMORE_COLOR wxColour(248, 248, 248) -#define DESIGN_GRAY900_COLOR wxColour(38, 46, 48) -#define DESIGN_GRAY800_COLOR wxColour(50, 58, 61) -#define DESIGN_GRAY600_COLOR wxColour(144, 144, 144) -#define DESIGN_GRAY400_COLOR wxColour(166, 169, 170) -#define DESIGN_RESOUTION_PREFERENCES wxSize(FromDIP(540), -1) -#define DESIGN_COMBOBOX_SIZE wxSize(FromDIP(140), -1) -#define DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(160), -1) -#define DESIGN_INPUT_SIZE wxSize(FromDIP(50), -1) +#define SEND_DESIGN_SELECTOR_NOMORE_COLOR wxColour(248, 248, 248) +#define SEND_DESIGN_GRAY900_COLOR wxColour(38, 46, 48) +#define SEND_DESIGN_GRAY800_COLOR wxColour(50, 58, 61) +#define SEND_DESIGN_GRAY600_COLOR wxColour(144, 144, 144) +#define SEND_DESIGN_GRAY400_COLOR wxColour(166, 169, 170) +#define SEND_DESIGN_RESOUTION_PREFERENCES wxSize(FromDIP(540), -1) +#define SEND_DESIGN_COMBOBOX_SIZE wxSize(FromDIP(140), -1) +#define SEND_DESIGN_LARGE_COMBOBOX_SIZE wxSize(FromDIP(160), -1) +#define SEND_DESIGN_INPUT_SIZE wxSize(FromDIP(50), -1) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2dedb23365..914e4cc7bb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -5029,28 +5029,12 @@ void TabPrinter::build_fff() auto registered_printer_agents = NetworkAgentFactory::get_registered_printer_agents(); if (!registered_printer_agents.empty()) { - ConfigOptionDef def; - def.type = coString; - def.gui_type = ConfigOptionDef::GUIType::printer_agent_select; - def.width = 3 * Field::def_width_wider() / 2; - def.label = L("Printer Agent"); - def.tooltip = L("Select the network agent implementation for printer communication. " + option = optgroup->get_option("printer_agent"); + option.opt.gui_type = ConfigOptionDef::GUIType::printer_agent_select; + option.opt.width = 3 * Field::def_width_wider() / 2; + option.opt.tooltip = L("Select the network agent implementation for printer communication. " "Available agents are registered at startup."); - def.mode = comAdvanced; - - // Create the field without get_option() so it is not registered in m_opt_map. - // ConfigOptionsGroup handles printer_agent before the generic mapped write path. - Line agent_line = optgroup->create_single_option_line(Option(def, "printer_agent")); - optgroup->append_line(agent_line); - if (Field* agent_field = get_field("printer_agent")) - { - if (auto* choice = dynamic_cast(agent_field); choice && choice->getWindow()) - choice->set_value(m_config->opt_string("printer_agent"), false); - } - - // Register by hand so the UnsavedChanges dialog can render a row for it. - wxGetApp().sidebar().get_searcher().add_key("printer_agent", m_type, optgroup->title, - optgroup->config_category()); + optgroup->append_single_option_line(option); } } @@ -5912,15 +5896,6 @@ void TabPrinter::reload_config() if (m_active_page && m_active_page->title() == "Multimaterial") m_active_page->set_value("extruders_count", int(m_extruders_count)); - // m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly. - if (Field* agent_field = get_field("printer_agent")) - { - if (auto* choice = dynamic_cast(agent_field); choice && choice->getWindow()) - { - const std::string selected_agent = m_config->opt_string("printer_agent"); - choice->set_value(selected_agent, false); - } - } } void TabPrinter::activate_selected_page(std::function throw_if_canceled) @@ -5932,15 +5907,6 @@ void TabPrinter::activate_selected_page(std::function throw_if_canceled) if (m_active_page && m_active_page->title() == "Multimaterial") m_active_page->set_value("extruders_count", int(m_extruders_count)); - // m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly. - if (Field* agent_field = get_field("printer_agent")) - { - if (auto* choice = dynamic_cast(agent_field); choice && choice->getWindow()) - { - const std::string selected_agent = m_config->opt_string("printer_agent"); - choice->set_value(selected_agent, false); - } - } } void TabPrinter::clear_pages() diff --git a/src/slic3r/GUI/TextureImportDialog.cpp b/src/slic3r/GUI/TextureImportDialog.cpp index 1bf52d792c..2e5c1145e1 100644 --- a/src/slic3r/GUI/TextureImportDialog.cpp +++ b/src/slic3r/GUI/TextureImportDialog.cpp @@ -134,7 +134,9 @@ public: } private: +#if defined(__WXMSW__) || defined(__APPLE__) int m_suspended_count = 0; +#endif }; static bool needs_filament_swatch_border(const wxColour& colour) diff --git a/src/slic3r/GUI/TroubleshootDialog.cpp b/src/slic3r/GUI/TroubleshootDialog.cpp index 5acb75c6ea..7cbb00b5d8 100644 --- a/src/slic3r/GUI/TroubleshootDialog.cpp +++ b/src/slic3r/GUI/TroubleshootDialog.cpp @@ -6,6 +6,8 @@ #include "GUI_App.hpp" #include "MainFrame.hpp" +#include +#include #include #include #include "wx/clipbrd.h" diff --git a/src/slic3r/GUI/UserManager.cpp b/src/slic3r/GUI/UserManager.cpp index 456582e896..e874c2158d 100644 --- a/src/slic3r/GUI/UserManager.cpp +++ b/src/slic3r/GUI/UserManager.cpp @@ -1,9 +1,11 @@ #include "libslic3r/libslic3r.h" #include "UserManager.hpp" #include "DeviceManager.hpp" +#include "BindDialog.hpp" #include "NetworkAgent.hpp" #include "GUI.hpp" #include "GUI_App.hpp" +#include "I18N.hpp" #include "MsgDialog.hpp" #include "DeviceCore/DevManager.h" diff --git a/src/slic3r/GUI/WebGuideDialog.hpp b/src/slic3r/GUI/WebGuideDialog.hpp index c4cfc8bf6d..1ad60175ae 100644 --- a/src/slic3r/GUI/WebGuideDialog.hpp +++ b/src/slic3r/GUI/WebGuideDialog.hpp @@ -43,6 +43,8 @@ namespace Slic3r { namespace GUI { class GuideFrame : public DPIDialog { public: + using json = nlohmann::json; + GuideFrame(GUI_App *pGUI, long style = wxCAPTION | wxCLOSE_BOX | wxSYSTEM_MENU); virtual ~GuideFrame(); diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp index 22b1c34cab..5f03636f18 100644 --- a/src/slic3r/GUI/Widgets/Button.cpp +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -311,8 +311,11 @@ void Button::render(wxDC& dc) } } auto szContent = textSize; + // Whether the measured content reserved the text/icon gap. macOS measures an empty label + // as 0-high, so the gap is skipped there; the dot must not advance past it in that case. + const bool gap_reserved = szContent.y > 0; if (icon.bmp().IsOk()) { - if (szContent.y > 0) { + if (gap_reserved) { //BBS norrow size between text and icon if (vertical) szContent.y += spacing; @@ -357,10 +360,10 @@ void Button::render(wxDC& dc) dc.DrawBitmap(icon.bmp(), pt); //BBS norrow size between text and icon if (vertical) { - pt.y += szIcon.y + spacing; + pt.y += szIcon.y + (gap_reserved ? spacing : 0); pt.x = rcContent.x; } else { - pt.x += szIcon.x + spacing; + pt.x += szIcon.x + (gap_reserved ? spacing : 0); pt.y = rcContent.y; } } diff --git a/src/slic3r/GUI/Widgets/CheckList.cpp b/src/slic3r/GUI/Widgets/CheckList.cpp index cd0dcebff3..c0101441ff 100644 --- a/src/slic3r/GUI/Widgets/CheckList.cpp +++ b/src/slic3r/GUI/Widgets/CheckList.cpp @@ -1,6 +1,7 @@ #include "CheckList.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" CheckList::CheckList( wxWindow* parent, diff --git a/src/slic3r/GUI/Widgets/FanControl.cpp b/src/slic3r/GUI/Widgets/FanControl.cpp index f10553814e..7efdfabf07 100644 --- a/src/slic3r/GUI/Widgets/FanControl.cpp +++ b/src/slic3r/GUI/Widgets/FanControl.cpp @@ -995,7 +995,7 @@ void FanControlPopupNew::init_names(MachineObject* obj) { radio_btn_name[AIR_DUCT::AIR_DUCT_HEATING_INTERNAL_FILT] = _L("Heating"); radio_btn_name[AIR_DUCT::AIR_DUCT_EXHAUST] = _L("Exhaust"); radio_btn_name[AIR_DUCT::AIR_DUCT_FULL_COOLING] = _L("Full Cooling"); - radio_btn_name[AIR_DUCT::AIR_DUCT_INIT] = L("Init"); + radio_btn_name[AIR_DUCT::AIR_DUCT_INIT] = _L("Init"); air_door_func_name[AIR_DOOR::AIR_DOOR_FUNC_CHAMBER] = _L("Chamber"); air_door_func_name[AIR_DOOR::AIR_DOOR_FUNC_INNERLOOP] = _L("Innerloop"); diff --git a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp index 20944df553..9278565c47 100644 --- a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp +++ b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include diff --git a/src/slic3r/GUI/Widgets/ProgressDialog.cpp b/src/slic3r/GUI/Widgets/ProgressDialog.cpp index 53842ecaea..c16f3bcf1f 100644 --- a/src/slic3r/GUI/Widgets/ProgressDialog.cpp +++ b/src/slic3r/GUI/Widgets/ProgressDialog.cpp @@ -178,7 +178,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int wxBoxSizer *sizer_1line = new wxBoxSizer(wxHORIZONTAL); m_msg = new wxStaticText(m_panel_1line, wxID_ANY, wxEmptyString, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0); m_msg->Wrap(-1); - m_msg->SetFont(::Label::Body_13); + m_msg->SetFont(::Label::Body_14); m_msg->SetForegroundColour(PROGRESSDIALOG_GREY_700); sizer_1line->Add(m_msg, 0, wxALIGN_CENTER, 0); m_panel_1line->SetSizer(sizer_1line); @@ -188,7 +188,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int wxBoxSizer *sizer_2line = new wxBoxSizer(wxVERTICAL); m_msg_2line = new wxStaticText(m_panel_2line, wxID_ANY, wxEmptyString, wxDefaultPosition, PROGRESSDIALOG_SIMPLEBOOK_SIZE, 0); m_msg_2line->Wrap(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x); - m_msg_2line->SetFont(::Label::Body_13); + m_msg_2line->SetFont(::Label::Body_14); m_msg_2line->SetForegroundColour(PROGRESSDIALOG_GREY_700); m_msg_2line->SetMaxSize(wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x, -1)); sizer_2line->Add(m_msg_2line, 1, wxALL, 0); @@ -204,7 +204,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int m_msg = new wxStaticText(m_msg_scrolledWindow, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x, -1), 0); m_msg->Wrap(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x); - m_msg->SetFont(::Label::Body_13); + m_msg->SetFont(::Label::Body_14); m_msg->SetForegroundColour(PROGRESSDIALOG_GREY_700); m_msg_sizer->Add(m_msg, 0, wxEXPAND | wxALL, 0); @@ -228,6 +228,9 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int if (!HasPDFlag(wxPD_NO_PROGRESS)) { m_gauge = new wxGauge(this, wxID_ANY, maximum, wxDefaultPosition, PROGRESSDIALOG_GAUGE_SIZE, gauge_style); m_gauge->SetValue(0); + m_gauge->SetForegroundColour(wxColour("#009688")); + m_gauge->SetBackgroundColour(wxColour("#D9D9D9")); + wxGetApp().UpdateDarkUI(m_gauge); m_sizer_main->Add(m_gauge, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28)); } diff --git a/src/slic3r/GUI/Widgets/ProgressDialog.hpp b/src/slic3r/GUI/Widgets/ProgressDialog.hpp index bb770298a9..9ebf31b194 100644 --- a/src/slic3r/GUI/Widgets/ProgressDialog.hpp +++ b/src/slic3r/GUI/Widgets/ProgressDialog.hpp @@ -18,7 +18,7 @@ class WXDLLIMPEXP_FWD_CORE wxWindowDisabler; #define PROGRESSDIALOG_GAUGE_SIZE wxSize(FromDIP(320), FromDIP(6)) #define PROGRESSDIALOG_CANCEL_BUTTON_SIZE wxSize(FromDIP(60), FromDIP(24)) #define PROGRESSDIALOG_DEF_BK wxColour(255,255,255) -#define PROGRESSDIALOG_GREY_700 wxColour(107,107,107) +#define PROGRESSDIALOG_GREY_700 wxColour(54,54,54) // #363636 label color #define wxPD_NO_PROGRESS 0x0100 diff --git a/src/slic3r/GUI/Widgets/TabCtrl.cpp b/src/slic3r/GUI/Widgets/TabCtrl.cpp index 34de109b8f..ef23e2c5e4 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.cpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.cpp @@ -99,7 +99,7 @@ int TabCtrl::AppendItem(const wxString& item, int image, int selImage, void* cli btns.push_back(btn); if (btns.size() > 1) sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0}); - sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, item_space); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); sizer->AddStretchSpacer(1); relayout(); return btns.size() - 1; @@ -256,12 +256,12 @@ void TabCtrl::relayout() int item = sel + 1; int first = 0; for (int i = 0; i < item; ++i) - offset += btns[i]->GetMinSize().x + item_space * 2; + offset += btns[i]->GetMinSize().x; if (item < btns.size()) - offset += btns[item]->GetMinSize().x + item_space * 2; + offset += btns[item]->GetMinSize().x; int width = GetSize().x; for (int i = 0; i < btns.size(); ++i) { - auto size = btns[i]->GetMinSize().x + item_space * 2; + auto size = btns[i]->GetMinSize().x; if (i < sel && offset > width) { sizer->Show(i * 2 + 1, false); sizer->Show(i * 2 + 2, false); @@ -284,26 +284,17 @@ void TabCtrl::relayout() if (item >= btns.size()) --item; // Keep spacing 2 ~ 10 TAB_BUTTON_SPACE - int b = GetSize().x - offset - 10 - (item + 1 - first) * item_space * 8; + int b = GetSize().x - offset - 10 - (item + 1 - first) * 16; sizer->GetItem(item * 2 + 2)->SetMinSize({b > 0 ? b : 0, 0}); Layout(); } -void TabCtrl::SetItemSpace(int space) -{ - if (space < 0 || space == item_space) - return; - item_space = space; - relayout(); - Refresh(); -} - int TabCtrl::GetFullSize() const { - // Mirrors relayout(): a 10px leading spacer plus every button's min width and spacing. + // Mirrors relayout(): a 10px leading spacer plus every button's min width. int width = 10; for (const Button* btn : btns) - width += btn->GetMinSize().x + item_space * 2; + width += btn->GetMinSize().x; return width; } diff --git a/src/slic3r/GUI/Widgets/TabCtrl.hpp b/src/slic3r/GUI/Widgets/TabCtrl.hpp index 493c4edee5..d89da145af 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.hpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.hpp @@ -14,7 +14,6 @@ class TabCtrl : public StaticBox int sel = -1; wxFont bold; - int item_space = 2; // space around each button, both sides (SetItemSpace) public: TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0); @@ -64,10 +63,6 @@ public: int GetNextVisible(int item) const; bool IsVisible(unsigned int item) const; - // Extra space around each tab button (in px on both sides). Defaults to the control-wide - // standard; call before appending items so every button picks it up. - void SetItemSpace(int space); - int GetFullSize() const; private: diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index e281d97407..a29a3cb725 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -1,9 +1,13 @@ #include "WebView.hpp" +#include "slic3r/GUI/Widgets/StateColor.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/Utils/MacDarkMode.hpp" #include +#include +#include + #include #include #if wxUSE_WEBVIEW_EDGE @@ -12,6 +16,8 @@ #include #endif #include +#include +#include #if defined(__WIN32__) || defined(__WXMAC__) #include "wx/private/jsscriptwrapper.h" #endif @@ -73,7 +79,7 @@ DWORD DownloadAndInstallWV2RT() { }) .perform_sync(); // Sleep for 1 second to wait for the buffer writen into disk - std::this_thread::sleep_for(1000ms); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); if (downloaded) { // Either Package the WebView2 Bootstrapper with your app or download it using fwlink // Then invoke install at Runtime. diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index d4fbcc6fe3..9a473c7c8a 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -6,6 +6,7 @@ #include "GUI.hpp" #include "I18N.hpp" #include "GUI_App.hpp" +#include "WebViewDialog.hpp" #include "MsgDialog.hpp" #include "format.hpp" #include "libslic3r/Color.hpp" diff --git a/src/slic3r/Utils/3DPrinterOS.cpp b/src/slic3r/Utils/3DPrinterOS.cpp index 61fcc80d5b..503dbe63e3 100755 --- a/src/slic3r/Utils/3DPrinterOS.cpp +++ b/src/slic3r/Utils/3DPrinterOS.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,8 @@ #include +using json = nlohmann::json; + namespace fs = boost::filesystem; namespace pt = boost::property_tree; diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.cpp b/src/slic3r/Utils/BBLCloudServiceAgent.cpp index 846e4ce509..801ad1cf57 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.cpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.cpp @@ -8,6 +8,9 @@ #include #include #include + +using json = nlohmann::json; + namespace Slic3r { diff --git a/src/slic3r/Utils/BBLNetworkPlugin.cpp b/src/slic3r/Utils/BBLNetworkPlugin.cpp index 607e7d16d1..d795abf354 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.cpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.cpp @@ -349,7 +349,7 @@ void* BBLNetworkPlugin::get_function(const char* name) return function; #if defined(_MSC_VER) || defined(_WIN32) - function = GetProcAddress(m_networking_module, name); + function = reinterpret_cast(GetProcAddress(m_networking_module, name)); #else function = dlsym(m_networking_module, name); #endif diff --git a/src/slic3r/Utils/BBLPrinterAgent.cpp b/src/slic3r/Utils/BBLPrinterAgent.cpp index 5e73edf84c..0c6225cc55 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.cpp +++ b/src/slic3r/Utils/BBLPrinterAgent.cpp @@ -8,6 +8,7 @@ #include using json = nlohmann::json; +#include #include namespace Slic3r { @@ -90,6 +91,16 @@ OnMessageFn to_orca_messages(OnMessageFn fn) return [fn = std::move(fn)](std::string dev_id, std::string msg) { fn(std::move(dev_id), BBLPrinterAgent::to_orca_payload(std::move(msg))); }; } +// Retypes a plug-in entry point for an older plug-in generation. The detour through the +// generic function pointer marks the signature change as deliberate, which a direct cast +// between two signatures does not. +template +To as_abi(From fn) +{ + static_assert(std::is_function_v>, "as_abi retypes a function pointer"); + return reinterpret_cast(reinterpret_cast(fn)); +} + } // namespace std::string BBLPrinterAgent::to_orca_filament_id(const std::string& printer_filament_id) const @@ -141,7 +152,7 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int // series through the legacy form would silently drop MessageFlag sign/encrypt. switch (plugin.network_abi()) { case NetworkAbi::Legacy: { - auto legacy_func = reinterpret_cast(func); + auto legacy_func = as_abi(func); return legacy_func(agent, std::move(dev_id), std::move(json_str), qos); } case NetworkAbi::V0203: @@ -185,7 +196,7 @@ int BBLPrinterAgent::send_message_to_printer(std::string dev_id, std::string jso if (func && agent) { switch (plugin.network_abi()) { case NetworkAbi::Legacy: { - auto legacy_func = reinterpret_cast(func); + auto legacy_func = as_abi(func); return legacy_func(agent, std::move(dev_id), std::move(json_str), qos); } case NetworkAbi::V0203: @@ -275,7 +286,7 @@ int BBLPrinterAgent::bind(std::string dev_ip, std::string dev_id, std::string de switch (plugin.network_abi()) { case NetworkAbi::Legacy: case NetworkAbi::V0203: { - auto older_func = reinterpret_cast(func); + auto older_func = as_abi(func); return older_func(agent, dev_ip, dev_id, sec_link, timezone, improved, update_fn); } case NetworkAbi::Current: @@ -436,9 +447,9 @@ int dispatch_start(CurrentFn func, PrintParams& params, const CallbackFns&... ca params.ams_mapping_info = BBLPrinterAgent::from_orca_payload(std::move(params.ams_mapping_info)); switch (plugin.network_abi()) { case NetworkAbi::Legacy: - return reinterpret_cast(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...); + return as_abi(func)(agent, BBLNetworkPlugin::as_legacy(params), callbacks...); case NetworkAbi::V0203: - return reinterpret_cast(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...); + return as_abi(func)(agent, BBLNetworkPlugin::as_0203(params), callbacks...); case NetworkAbi::Current: return func(agent, std::move(params), callbacks...); default: diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index 499228d13c..25aad85d2f 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -3,6 +3,7 @@ #include "../GUI/GUI_App.hpp" #include "../GUI/DeviceCore/DevStorage.h" #include "../GUI/DeviceManager.hpp" +#include "NetworkAgent.hpp" #include "../GUI/Jobs/ProgressIndicator.hpp" #include "../GUI/PartPlate.hpp" #include "libslic3r/CutUtils.hpp" diff --git a/src/slic3r/Utils/CloudProvider.hpp b/src/slic3r/Utils/CloudProvider.hpp new file mode 100644 index 0000000000..0f03683222 --- /dev/null +++ b/src/slic3r/Utils/CloudProvider.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace Slic3r { + +// Identifiers of the cloud services an ICloudServiceAgent can stand for. +static const std::string ORCA_CLOUD_PROVIDER("orca"); +static const std::string BBL_CLOUD_PROVIDER("bbl"); + +} // namespace Slic3r diff --git a/src/slic3r/Utils/CrealityPrintAgent.cpp b/src/slic3r/Utils/CrealityPrintAgent.cpp index 9b3bd5843e..f340a61267 100644 --- a/src/slic3r/Utils/CrealityPrintAgent.cpp +++ b/src/slic3r/Utils/CrealityPrintAgent.cpp @@ -12,6 +12,8 @@ #include #include +using json = nlohmann::json; + namespace Slic3r { namespace { diff --git a/src/slic3r/Utils/ICloudServiceAgent.hpp b/src/slic3r/Utils/ICloudServiceAgent.hpp index 556c253641..7d326eb4c6 100644 --- a/src/slic3r/Utils/ICloudServiceAgent.hpp +++ b/src/slic3r/Utils/ICloudServiceAgent.hpp @@ -2,6 +2,7 @@ #define __I_CLOUD_SERVICE_AGENT_HPP__ #include "bambu_networking.hpp" +#include "CloudProvider.hpp" #include "../../libslic3r/ProjectTask.hpp" #include #include @@ -37,9 +38,6 @@ namespace Slic3r { * implementation. */ -static const std::string ORCA_CLOUD_PROVIDER("orca"); -static const std::string BBL_CLOUD_PROVIDER("bbl"); - struct CloudEvent { std::string provider; // ORCA_CLOUD_PROVIDER or BBL_CLOUD_PROVIDER }; diff --git a/src/slic3r/Utils/MacDarkMode.mm b/src/slic3r/Utils/MacDarkMode.mm index cecd90044b..2bce7835e8 100644 --- a/src/slic3r/Utils/MacDarkMode.mm +++ b/src/slic3r/Utils/MacDarkMode.mm @@ -57,7 +57,7 @@ void set_miniaturizable(void * window) while(viewObject = (NSView *)[viewEnum nextObject]) { if([viewObject class] == [NSTextField self]) { //[(NSTextField*)viewObject setTextColor : NSColor.whiteColor]; - mainframe_text_field = viewObject; + mainframe_text_field = (NSTextField*)viewObject; } } } @@ -74,7 +74,7 @@ void set_title_colour_after_set_title(void * window) while(viewObject = (NSView *)[viewEnum nextObject]) { if([viewObject class] == [NSTextField self]) { [(NSTextField*)viewObject setTextColor : NSColor.whiteColor]; - mainframe_text_field = viewObject; + mainframe_text_field = (NSTextField*)viewObject; } } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 571d707a9f..384a69d51e 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/DeviceManager.hpp" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "slic3r/GUI/DeviceCore/DevManager.h" #include "../GUI/DeviceCore/DevStorage.h" diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 4bfe429cd3..5d129a490e 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index b328c43cca..69bfa4217e 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -42,6 +42,7 @@ #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/Utils/Http.hpp" +#include "slic3r/Utils/bambu_networking.hpp" #include "slic3r/Config/Version.hpp" #include "slic3r/Config/Snapshot.hpp" #include "slic3r/GUI/MarkdownTip.hpp" @@ -1106,8 +1107,8 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE) // always update configs from resource to vendor for ORCA_DEFAULT_BUNDLE || (enabled_vendors.find(vendor_name) != enabled_vendors.end()); - if (enabled_config_update) { - if (is_vendor_installed(vendor_name)) { + if (is_vendor_installed(vendor_name)) { + if (enabled_config_update) { if (is_vendor_enabled) { // Orca: whichever form of the vendor resources ships at the newer // version is the one installing lays down, and the one to judge @@ -1122,17 +1123,12 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const << resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string(); bundles.insert(vendor_name); } - } - else { - //need to be removed because not installed + } else { + // need to be removed because not installed remove_installed_vendor(vendor_name); } } - else if (is_vendor_enabled) { - bundles.insert(vendor_name); - } - } - else if (is_vendor_enabled) { + } else if (is_vendor_enabled) { bundles.insert(vendor_name); } } @@ -1625,7 +1621,7 @@ void PresetUpdater::priv::check_new_vendors(const std::set& system_ Http::get(download_url_str) .timeout_connect(5) .on_progress(check_cancel) - .on_error([&vendor_id, &retry_count, max_retries](std::string body, std::string error, unsigned http_status) { + .on_error([&vendor_id, &retry_count](std::string body, std::string error, unsigned http_status) { BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] download failed for new vendor " << vendor_id << " (attempt " << retry_count << "/" << max_retries << "): " << error; }) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 518462bbc9..96da521114 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -21,6 +21,7 @@ #include #endif +#include #include namespace Slic3r { diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index 6b05480194..1f437853ba 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -9,6 +9,8 @@ #include #include +using json = nlohmann::json; + namespace Slic3r { namespace { diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 4db1acc6b6..f8c03ceb26 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -331,7 +331,9 @@ void Serial::set_baud_rate(unsigned baud_rate) speed_t c_ispeed; speed_t c_ospeed; }; +#ifndef BOTHER #define BOTHER CBAUDEX +#endif termios2 ios; handle_errno(::ioctl(handle, TCGETS2, &ios)); diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp index ab7aa9bd52..5783738af7 100644 --- a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp @@ -6,6 +6,8 @@ #include "nlohmann/json.hpp" #include +using json = nlohmann::json; + namespace Slic3r { namespace { diff --git a/src/slic3r/plugin/PluginResolver.cpp b/src/slic3r/plugin/PluginResolver.cpp index 4ba58ee550..6e72929c63 100644 --- a/src/slic3r/plugin/PluginResolver.cpp +++ b/src/slic3r/plugin/PluginResolver.cpp @@ -3,6 +3,7 @@ #include "PluginManager.hpp" #include "../Utils/Http.hpp" #include "../Utils/OrcaCloudServiceAgent.hpp" +#include "../Utils/NetworkAgent.hpp" #include "../GUI/GUI.hpp" #include "../GUI/GUI_App.hpp" #include "../GUI/I18N.hpp" diff --git a/src/slic3r/plugin/host/PluginHostUi.cpp b/src/slic3r/plugin/host/PluginHostUi.cpp index c098ea3224..8fc3f12877 100644 --- a/src/slic3r/plugin/host/PluginHostUi.cpp +++ b/src/slic3r/plugin/host/PluginHostUi.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ #include #include +#include #include #include #include @@ -44,16 +46,20 @@ namespace { struct GilSafeCallable { py::object fn; + std::atomic_bool active{true}; explicit GilSafeCallable(py::object f) : fn(std::move(f)) {} + void disable() + { + active.store(false, std::memory_order_release); + PythonGILState gil; + if (gil) + fn = py::object(); + else + (void) fn.release(); + } ~GilSafeCallable() { - if (fn) { - PythonGILState gil; - if (gil) - fn = py::object(); - else - (void) fn.release(); - } + disable(); } }; using CallablePtr = std::shared_ptr; @@ -166,11 +172,34 @@ public: } return out; } + void bind_callback(const CallablePtr& callback, const std::string& plugin_key) + { + if (!callback) + return; + std::lock_guard lk(m_mtx); + m_callbacks[plugin_key].push_back(callback); + } + std::vector take_callbacks_for_plugin(const std::string& plugin_key) + { + std::lock_guard lk(m_mtx); + auto it = m_callbacks.find(plugin_key); + if (it == m_callbacks.end()) + return {}; + std::vector callbacks; + callbacks.reserve(it->second.size()); + for (const std::weak_ptr& weak_callback : it->second) { + if (auto callback = weak_callback.lock()) + callbacks.push_back(std::move(callback)); + } + m_callbacks.erase(it); + return callbacks; + } private: std::mutex m_mtx; std::unordered_map m_resources; std::unordered_map m_owners; + std::unordered_map>> m_callbacks; int m_next_id{1}; }; @@ -448,6 +477,46 @@ void progress_close(int id) }); } +void plater_notification(NotificationManager::NotificationLevel notification_level, const std::string& text, + const std::string& hypertext, py::object on_click) +{ + const std::string plugin_key = PluginAuditManager::instance().current_plugin(); + CallablePtr holder = make_holder(std::move(on_click)); + if (holder) + UiRegistry::instance().bind_callback(holder, plugin_key); + + std::function callback; + if (holder) { + callback = [holder](wxEvtHandler*) -> bool { + if (!holder->active.load(std::memory_order_acquire)) + return false; + + PythonGILState gil; + if (!gil) + return false; + try { + py::object result = holder->fn(); + return result.is_none() || result.cast(); + } catch (py::error_already_set& e) { + BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised: " << e.what(); + PyErr_Clear(); + return false; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised: " << e.what(); + return false; + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "orca.host.ui notification callback raised an unknown exception"; + return false; + } + }; + } + + run_on_ui_blocking([notification_level, text, hypertext, callback = std::move(callback)]() mutable { + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::CustomNotification, notification_level, text, + hypertext, std::move(callback)); + }); +} + } // namespace void PluginHostUi::RegisterBindings(pybind11::module_& host) @@ -530,6 +599,23 @@ void PluginHostUi::RegisterBindings(pybind11::module_& host) ui.def("create_progress_dialog", &ui_create_progress_dialog, py::arg("title"), py::arg("message"), py::arg("maximum") = 100, py::arg("style") = wxPD_APP_MODAL | wxPD_AUTO_HIDE, "Create a native progress dialog and return a ProgressDialog handle."); + + py::enum_(ui, "NotificationLevel") + .value("ProgressBarNotificationLevel", NotificationManager::NotificationLevel::ProgressBarNotificationLevel) + .value("HintNotificationLevel", NotificationManager::NotificationLevel::HintNotificationLevel) + .value("RegularNotificationLevel", NotificationManager::NotificationLevel::RegularNotificationLevel) + .value("PrintInfoNotificationLevel", NotificationManager::NotificationLevel::PrintInfoNotificationLevel) + .value("PrintInfoShortNotificationLevel", NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel) + .value("ImportantNotificationLevel", NotificationManager::NotificationLevel::ImportantNotificationLevel) + .value("WarningNotificationLevel", NotificationManager::NotificationLevel::WarningNotificationLevel) + .value("SeriousWarningNotificationLevel", NotificationManager::NotificationLevel::SeriousWarningNotificationLevel) + .value("ErrorNotificationLevel", NotificationManager::NotificationLevel::ErrorNotificationLevel) + .export_values(); + + ui.def("push_notification", &plater_notification, py::arg("notification_level"), py::arg("text"), + py::arg("hyper_text") = "", py::arg("on_click") = py::none(), + "Push a plater notification. hyper_text is an underlined label; on_click() is called when it is clicked " + "and may return True to close the notification."); } void PluginHostUi::close_windows_for_plugin(const std::string& plugin_key) @@ -538,6 +624,9 @@ void PluginHostUi::close_windows_for_plugin(const std::string& plugin_key) return; auto teardown = [plugin_key]() { + for (auto& callback : UiRegistry::instance().take_callbacks_for_plugin(plugin_key)) + callback->disable(); + // Destroy() bypasses wxEVT_CLOSE, so the plugin's on_close is not fired on // forced teardown (intended); the resource destructor still cleans the registry. for (auto* window : UiRegistry::instance().take_for_plugin(plugin_key)) { diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 04e5b61831..a3696c47ad 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -9,11 +9,13 @@ #include #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/libslic3r.h" @@ -675,6 +677,73 @@ TEST_CASE("Ironing follows the solid infill rotation template", "[Fill]") REQUIRE(compared > int(ironing.size()) / 2); } + +namespace { + +PrintRegionConfig ironing_config(IroningType type, + int top_surface_filament_id = 1, + int top_shell_layers = 3, + int bottom_shell_layers = 1) +{ + PrintRegionConfig cfg; + cfg.ironing_type.value = type; + cfg.top_surface_filament_id.value = top_surface_filament_id; + cfg.top_shell_layers.value = top_shell_layers; + cfg.bottom_shell_layers.value = bottom_shell_layers; + cfg.outer_wall_filament_id.value = 1; + cfg.wall_loops.value = 2; + return cfg; +} + +} // namespace + +TEST_CASE("Ironing an all-solid region uses the top surface filament on every layer", "[Fill]") +{ + const PrintRegionConfig cfg = ironing_config(IroningType::AllSolid, /*top_surface_filament_id=*/2); + const bool is_topmost_layer = GENERATE(false, true); + CAPTURE(is_topmost_layer); + REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, is_topmost_layer) == 2); +} + +TEST_CASE("Ironing top surfaces uses the top surface filament when the region has top shells", "[Fill]") +{ + const PrintRegionConfig cfg = ironing_config(IroningType::TopSurfaces, + /*top_surface_filament_id=*/3, + /*top_shell_layers=*/2); + REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == 3); +} + +TEST_CASE("Ironing top surfaces without top shells needs spiral mode and more than one bottom shell", "[Fill]") +{ + const PrintRegionConfig one_bottom_shell = ironing_config(IroningType::TopSurfaces, + /*top_surface_filament_id=*/1, + /*top_shell_layers=*/0, + /*bottom_shell_layers=*/1); + const PrintRegionConfig two_bottom_shells = ironing_config(IroningType::TopSurfaces, + /*top_surface_filament_id=*/1, + /*top_shell_layers=*/0, + /*bottom_shell_layers=*/2); + + REQUIRE(Layer::choose_ironing_extruder(two_bottom_shells, /*spiral_mode=*/true, /*is_topmost_layer=*/false) == 1); + REQUIRE(Layer::choose_ironing_extruder(one_bottom_shell, /*spiral_mode=*/true, /*is_topmost_layer=*/false) == -1); + REQUIRE(Layer::choose_ironing_extruder(two_bottom_shells, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == -1); +} + +TEST_CASE("Ironing the topmost surface only applies to the topmost layer", "[Fill]") +{ + const PrintRegionConfig cfg = ironing_config(IroningType::TopmostOnly, /*top_surface_filament_id=*/4); + REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/true) == 4); + REQUIRE(Layer::choose_ironing_extruder(cfg, /*spiral_mode=*/false, /*is_topmost_layer=*/false) == -1); +} + +TEST_CASE("A region with ironing turned off is never ironed", "[Fill]") +{ + const PrintRegionConfig cfg = ironing_config(IroningType::NoIroning); + const bool spiral_mode = GENERATE(false, true); + CAPTURE(spiral_mode); + REQUIRE(Layer::choose_ironing_extruder(cfg, spiral_mode, /*is_topmost_layer=*/true) == -1); +} + TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]") { auto angles_for = [](int direction) { @@ -1229,3 +1298,68 @@ TEST_CASE("Smoothing multiline lightning infill keeps its outlines connected", " REQUIRE(smooth.point_count > sharp.point_count); REQUIRE(smooth.sharp_turns < sharp.sharp_turns); } + +TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][InternalBridge][Regression]") +{ + // Orca: Compare generated anchors with actual extrusion across plane-path patterns, + // smoothing, multiline and rotations; an origin shift must not pass as valid support. + const std::string pattern = GENERATE("hilbertcurve", "octagramspiral", "archimedeanchords"); + const std::string smoothing = GENERATE("0%", "100%"); + const int multiline = GENERATE(1, 2); + const bool rotated = GENERATE(false, true); + const bool separated = GENERATE(false, true); + CAPTURE(pattern, smoothing, multiline, rotated, separated); + + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"sparse_infill_pattern", pattern}, + {"sparse_infill_density", "15%"}, + {"sparse_infill_smooth_factor", smoothing}, + {"fill_multiline", multiline}, + {"infill_direction", 45}, + {"sparse_infill_rotate_template", rotated ? "0,25,50" : ""}, + {"align_infill_direction_to_model", rotated}, + {"separated_infills", separated}, + {"top_shell_layers", 0}, + {"bottom_shell_layers", 0}, + {"top_shell_thickness", 0}, + {"bottom_shell_thickness", 0}, + {"layer_height", 0.2}, + {"initial_layer_print_height", 0.2}, + {"resolution", 0.012}}); + Print print; + Model model; + TriangleMesh mesh = make_cube(30, 24, 1); + if (separated) { + // Orca: Two disconnected bodies in one object must each use their own infill origin. + TriangleMesh second = make_cube(30, 24, 1); + second.translate(50, 0, 0); + mesh.merge(second); + } + Slic3r::Test::init_print({mesh}, print, model, config, nullptr, false); + if (rotated) { + model.objects.front()->instances.front()->set_rotation(Vec3d(0., 0., Geometry::deg2rad(23.))); + print.apply(model, config); + } + print.process(); + + const Layer &layer = *print.objects().front()->get_layer(4); + Polylines printed; + for (const LayerRegion *region : layer.regions()) + for (const ExtrusionEntity *entity : region->fills.flatten().entities) + if (entity->role() == erInternalInfill) + entity->collect_polylines(printed); + REQUIRE_FALSE(printed.empty()); + const AABBTreeLines::LinesDistancer printed_tree(to_lines(printed)); + + // Orca: Exclude perimeter connections: anchoring and extrusion can trim those differently. + const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr), + shrink(to_polygons(layer.lslices), scale_(3.))); + REQUIRE_FALSE(anchors.empty()); + double max_distance = 0.; + for (const Polyline &path : anchors) + for (const Point &point : path.equally_spaced_points(scale_(0.25))) + max_distance = std::max(max_distance, printed_tree.distance_from_lines(point)); + // Orca: Allow only the configured simplification tolerance; infill-scale offsets + // would hide anchors that no longer coincide with printed lines. + CHECK(unscale(max_distance) <= config.opt_float("resolution")); +} diff --git a/tests/fff_print/test_printobject.cpp b/tests/fff_print/test_printobject.cpp index fb7fe2c1fd..a373a1ad39 100644 --- a/tests/fff_print/test_printobject.cpp +++ b/tests/fff_print/test_printobject.cpp @@ -4,11 +4,18 @@ #include "libslic3r/Print.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/GCodeReader.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/AABBTreeLines.hpp" #include "test_helpers.hpp" +#include #include +#include #include +#include +#include +#include using namespace Slic3r; using namespace Slic3r::Test; @@ -130,3 +137,438 @@ TEST_CASE("Initial layer height is honored", "[PrintObject]") REQUIRE_THAT(*layer_zs.begin(), Catch::Matchers::WithinAbs(0.3, 1e-4)); REQUIRE_THAT(*std::next(layer_zs.begin()), Catch::Matchers::WithinAbs(0.5, 1e-4)); } + +static TriangleMesh internal_bridge_step() +{ + // Orca: The smaller tower leaves a shoulder whose solid skin needs internal bridges + // over the sparse infill in the base, without relying on an external model file. + TriangleMesh mesh = make_cube(30, 24, 3); + TriangleMesh tower = make_cube(14, 10, 1); + tower.translate(8, 7, 3); + mesh.merge(tower); + return mesh; +} + +static DynamicPrintConfig internal_bridge_config(const std::string &pattern, int multiline) +{ + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"sparse_infill_pattern", pattern}, + {"fill_multiline", multiline}, + {"sparse_infill_density", "15%"}, + {"sparse_infill_smooth_factor", "100%"}, + {"infill_direction", 45}, + {"internal_bridge_angle", 0}, + {"thick_internal_bridges", true}, + {"top_shell_layers", 3}, + {"bottom_shell_layers", 2}, + {"top_shell_thickness", 0}, + {"bottom_shell_thickness", 0}, + {"layer_height", 0.2}, + {"initial_layer_print_height", 0.2}}); + return config; +} + +TEST_CASE("Internal bridge angles follow the lower infill layer and model rotation", "[PrintObject][InternalBridge][Regression]") +{ + const std::string pattern = GENERATE("hilbertcurve", "octagramspiral"); + // Orca: Cover both a central line (odd counts) and offset pairs (even counts). + const int multiline = GENERATE(1, 2, 3); + CAPTURE(multiline); + const double rotation = GENERATE(23., -123.); + const std::vector cycle{10., 30., 70.}; + auto config = internal_bridge_config(pattern, multiline); + config.set_deserialize_strict({{"sparse_infill_rotate_template", "10,30,70"}, + {"align_infill_direction_to_model", true}, + {"separated_infills", false}}); + Print print; + Model model; + init_print({internal_bridge_step()}, print, model, config, nullptr, false); + model.objects.front()->instances.front()->set_rotation(Vec3d(0., 0., Geometry::deg2rad(rotation))); + print.apply(model, config); + print.process(); + const PrintObject &object = *print.objects().front(); + size_t bridges = 0; + for (size_t i = 1; i < object.layer_count(); ++i) { + // Orca: The support is one layer below the bridge. Check the template and model + // rotation together, including normalization when the resulting angle is negative. + double expected = std::fmod(cycle[(i - 1) % cycle.size()] + 90. + rotation, 180.); + if (expected < 0.) expected += 180.; + for (const LayerRegion *region : object.get_layer(i)->regions()) + for (const Surface *surface : region->fill_surfaces.filter_by_type(stInternalBridge)) { + CAPTURE(pattern, rotation, i); + CHECK_THAT(Geometry::rad2deg(surface->bridge_angle), Catch::Matchers::WithinAbs(expected, 0.001)); + ++bridges; + } + } + REQUIRE(bridges > 0); +} + +TEST_CASE("Turning infill does not replace the anchors of another region", "[PrintObject][InternalBridge][Regression]") +{ + // Orca: Keep the right-hand region fixed while changing the left-hand pattern in the + // same object. Its bridge areas must be independent of a previous candidate's anchors. + const int multiline = GENERATE(1, 2, 3); + CAPTURE(multiline); + auto right_bridges = [multiline](const std::string &left_pattern) { + auto config = internal_bridge_config(left_pattern, multiline); + Print print; + Model model; + init_print({internal_bridge_step()}, print, model, config, nullptr, false); + TriangleMesh right = internal_bridge_step(); + right.translate(50, 0, 0); + ModelVolume *volume = model.objects.front()->add_volume(std::move(right)); + volume->config.set_key_value("sparse_infill_pattern", new ConfigOptionEnum(ipRectilinear)); + volume->config.set_key_value("infill_direction", new ConfigOptionFloat(17.)); + print.apply(model, config); + print.process(); + std::map result; + const PrintObject &object = *print.objects().front(); + for (size_t i = 0; i < object.layer_count(); ++i) + for (const LayerRegion *region : object.get_layer(i)->regions()) + if (region->region().config().infill_direction == 17.) + polygons_append(result[i], to_polygons(region->fill_surfaces.filter_by_type(stInternalBridge))); + return result; + }; + const auto baseline = right_bridges("rectilinear"); + const auto actual = right_bridges(GENERATE("hilbertcurve", "octagramspiral")); + REQUIRE(actual.size() == baseline.size()); + double total_area = 0.; + for (const auto &[layer, expected] : baseline) { + CAPTURE(layer); + const auto &polys = actual.at(layer); + CHECK(area(diff(expected, polys)) < scaled(1.) * scaled(1.) * 1e-6); + CHECK(area(diff(polys, expected)) < scaled(1.) * scaled(1.) * 1e-6); + total_area += area(expected); + } + REQUIRE(total_area > 0.); +} + +TEST_CASE("Rounded internal bridges end on printed support", "[PrintObject][InternalBridge][Regression]") +{ + const std::string pattern = GENERATE("hilbertcurve", "octagramspiral"); + const bool separated = GENERATE(false, true); + CAPTURE(pattern, separated); + auto config = internal_bridge_config(pattern, 1); + config.set_deserialize_strict({{"infill_wall_overlap", "0%"}, {"separated_infills", separated}}); + TriangleMesh mesh = internal_bridge_step(); + if (separated) { + TriangleMesh second = internal_bridge_step(); + second.translate(50, 0, 0); + mesh.merge(second); + } + Print print; + Model model; + init_print({mesh}, print, model, config, nullptr, false); + print.process(); + + // Orca: Check final extrusion endpoints after polygon cleanup and fill generation. + // A correct bridge angle and correct sparse anchors alone do not guarantee contact. + const PrintObject &object = *print.objects().front(); + size_t checked = 0; + for (size_t i = 1; i < object.layer_count(); ++i) { + Polygons support; + Polylines walls; + for (const LayerRegion *region : object.get_layer(i - 1)->regions()) { + region->perimeters.polygons_covered_by_width(support, 0.f); + region->fills.polygons_covered_by_width(support, 0.f); + region->perimeters.collect_polylines(walls); + } + REQUIRE_FALSE(support.empty()); + const AABBTreeLines::LinesDistancer support_tree(to_lines(union_(support))); + const AABBTreeLines::LinesDistancer wall_tree(to_lines(walls)); + for (const LayerRegion *region : object.get_layer(i)->regions()) + for (const ExtrusionEntity *entity : region->fills.flatten().entities) { + if (entity->role() != erInternalBridgeInfill) + continue; + const auto *path = dynamic_cast(entity); + REQUIRE(path != nullptr); + for (const Line &line : path->polyline.to_polyline().lines()) { + // Orca: Sample span ends, excluding short connectors and wall overlap. + if (line.length() < scale_(std::max(0.7, 3. * path->width))) + continue; + for (const Point &point : {line.a, line.b}) { + if (wall_tree.distance_from_lines(point) <= scale_(0.5)) + continue; + CAPTURE(i, point.x(), point.y()); + const double gap = unscale(support_tree.distance_from_lines(point)) - 0.5 * path->width; + CHECK(gap <= 0.1); + ++checked; + } + } + } + } + REQUIRE(checked > 0); +} + +TEST_CASE("Enabling separated infill recomputes body origins", "[PrintObject][InternalBridge][Regression]") +{ + const std::string pattern = GENERATE("hilbertcurve", "octagramspiral", "archimedeanchords"); + CAPTURE(pattern); + auto footprint = [&](bool reslice) { + auto config = internal_bridge_config(pattern, 2); + config.set_deserialize_strict({{"separated_infills", !reslice}}); + TriangleMesh mesh = internal_bridge_step(); + TriangleMesh second = internal_bridge_step(); + second.translate(50, 0, 0); + mesh.merge(second); + Print print; + Model model; + init_print({mesh}, print, model, config, nullptr, false); + print.process(); + if (reslice) { + // Orca: Enabling centering after a completed slice must rebuild the body + // origins now shared by bridge preparation and printed infill. + config.set_deserialize_strict({{"separated_infills", true}}); + print.apply(model, config); + print.process(); + } + Polygons result; + for (const LayerRegion *region : print.objects().front()->get_layer(4)->regions()) + region->fills.polygons_covered_by_width(result, 0.f); + return union_(result); + }; + const Polygons fresh = footprint(false); + const Polygons resliced = footprint(true); + REQUIRE_FALSE(fresh.empty()); + CHECK(area(diff(fresh, resliced)) < scaled(1.) * scaled(1.) * 1e-6); + CHECK(area(diff(resliced, fresh)) < scaled(1.) * scaled(1.) * 1e-6); +} + +TEST_CASE("Surface centering survives changes to separated infill settings", "[PrintObject][SurfaceInfill][Regression]") +{ + const std::string pattern = GENERATE("archimedeanchords", "octagramspiral"); + const std::string initial_center = GENERATE("each_surface", "each_model", "each_assembly"); + const std::string final_center = GENERATE("each_surface", "each_model", "each_assembly"); + const bool separated = GENERATE(false, true); + const std::string top_order = GENERATE("default", "outward", "inward"); + const std::string bottom_order = top_order == "outward" ? "inward" : top_order == "inward" ? "outward" : "default"; + const std::string density = GENERATE("80%", "100%"); + const bool change_center = initial_center != final_center; + CAPTURE(pattern, initial_center, final_center, separated, top_order, bottom_order, density); + + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"top_surface_pattern", pattern}, + {"bottom_surface_pattern", pattern}, + {"top_surface_fill_order", top_order}, + {"bottom_surface_fill_order", bottom_order}, + {"top_surface_density", density}, + {"bottom_surface_density", density}, + {"center_of_surface_pattern", initial_center}, + {"separated_infills", change_center ? separated : !separated}, + {"sparse_infill_pattern", "rectilinear"}, + {"sparse_infill_density", "15%"}, + {"top_shell_layers", 2}, + {"bottom_shell_layers", 2}, + {"top_shell_thickness", 0}, + {"bottom_shell_thickness", 0}, + {"layer_height", 0.2}, + {"initial_layer_print_height", 0.2}}); + + // Orca: Two disconnected bodies exercise per-body centering. The offset tower also + // makes each-surface and each-model centering differ on the top surfaces. + TriangleMesh mesh = make_cube(30, 24, 2); + TriangleMesh tower = make_cube(12, 10, 1); + tower.translate(4, 3, 2); + mesh.merge(tower); + TriangleMesh second = mesh; + second.translate(50, 0, 0); + mesh.merge(second); + + // Orca: Equal footprints can hide reordered or reversed paths. Retain their point + // sequences and ordering protection to cover the directional surface behavior too. + struct SurfaceFillSnapshot { + std::map> paths; + bool protected_order = true; + }; + auto surface_fills = [](const Print &print) { + std::map, SurfaceFillSnapshot> result; + const PrintObject &object = *print.objects().front(); + for (size_t i = 0; i < object.layer_count(); ++i) { + auto collect = [&](const auto &self, const ExtrusionEntity &entity, bool no_sort) -> void { + if (const auto *collection = dynamic_cast(&entity)) { + for (const ExtrusionEntity *child : collection->entities) + self(self, *child, no_sort || collection->no_sort); + } else if (entity.role() == erTopSolidInfill || entity.role() == erBottomSurface) { + const auto *path = dynamic_cast(&entity); + REQUIRE(path != nullptr); + auto &snapshot = result[{i, entity.role()}]; + // Orca: The centered test model has one body on either side of X=0. + // Their traversal order may vary; preserve path order within each body. + Points points = path->polyline.to_polyline().points; + REQUIRE_FALSE(points.empty()); + snapshot.paths[points.front().x() > 0].push_back(std::move(points)); + snapshot.protected_order &= no_sort && !path->can_reverse(); + } + }; + for (const LayerRegion *region : object.get_layer(i)->regions()) + collect(collect, region->fills, false); + } + return result; + }; + + Print print; + Model model; + init_print({mesh}, print, model, config, nullptr, false); + print.process(); + const auto initial = surface_fills(print); + config.set_deserialize_strict({{"center_of_surface_pattern", final_center}, {"separated_infills", separated}}); + print.apply(model, config); + // Orca: Preparation owns the body origins, and its invalidation must also force + // regeneration of top/bottom extrusion paths, even when sparse infill is unchanged. + CHECK_FALSE(print.objects().front()->is_step_done(posPrepareInfill)); + CHECK_FALSE(print.objects().front()->is_step_done(posInfill)); + print.process(); + const auto resliced = surface_fills(print); + + Print fresh_print; + Model fresh_model; + init_print({mesh}, fresh_print, fresh_model, config, nullptr, false); + fresh_print.process(); + const auto fresh = surface_fills(fresh_print); + REQUIRE_FALSE(fresh.empty()); + REQUIRE(resliced.size() == fresh.size()); + std::set roles; + bool changed_paths = false; + for (const auto &entry : fresh) { + CAPTURE(entry.first.first, entry.first.second); + REQUIRE_FALSE(entry.second.paths.empty()); + roles.insert(entry.first.second); + REQUIRE(resliced.count(entry.first) == 1); + REQUIRE(initial.count(entry.first) == 1); + const auto &actual = resliced.at(entry.first); + const auto &expected = entry.second; + const auto &before = initial.at(entry.first); + CHECK((actual.paths == expected.paths)); + if (!change_center) + CHECK((actual.paths == before.paths)); + if (top_order != "default") { + CHECK(expected.protected_order); + CHECK(actual.protected_order); + CHECK(before.protected_order); + } + changed_paths |= expected.paths != before.paths; + } + CHECK(roles.count(erTopSolidInfill) == 1); + CHECK(roles.count(erBottomSurface) == 1); + // Orca: Guard against a vacuous comparison: changing surface centering must change + // the printed pattern, while toggling separated sparse infill must leave it alone. + CHECK(changed_paths == change_center); +} + +TEST_CASE("Separated infill keeps fragmented and nested bodies independent", "[PrintObject][SurfaceInfill][Regression]") +{ + constexpr size_t grid_size = 8; + TriangleMesh mesh; + auto add_box = [&](double x, double y, double width, double depth) { + TriangleMesh box = make_cube(width, depth, 0.6); + box.translate(x, y, 0); + mesh.merge(box); + }; + // Orca: Many small islands exercise spatial pruning and the tree's original + // island indices. A pillar inside a frame also overlaps its bounding box, + // but must remain a separate body because it lies entirely inside the hole. + for (size_t x = 0; x < grid_size; ++ x) + for (size_t y = 0; y < grid_size; ++ y) + add_box(6 * x, 6 * y, 3, 3); + add_box(54, 0, 20, 4); + add_box(54, 16, 20, 4); + add_box(54, 0, 4, 20); + add_box(70, 0, 4, 20); + add_box(62, 8, 4, 4); + + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"separated_infills", true}, + {"center_of_surface_pattern", "each_surface"}, + {"layer_height", 0.2}, + {"initial_layer_print_height", 0.2}, + {"elefant_foot_compensation", 0}, + {"wall_loops", 1}}); + Print print; + Model model; + init_print({mesh}, print, model, config, nullptr, false); + // Orca: Prepare body bounds through the public pipeline, then inspect the object read-only. + print.process(); + const PrintObject &object = *print.objects().front(); + REQUIRE(object.layer_count() > 1); + for (const Layer *layer : object.layers()) { + REQUIRE(layer->lslices.size() == grid_size * grid_size + 2); + REQUIRE(layer->lslices_separated_component_bboxes.size() == layer->lslices.size()); + size_t holes = 0; + for (size_t i = 0; i < layer->lslices.size(); ++ i) { + const BoundingBox &body = layer->lslices_separated_component_bboxes[i]; + const BoundingBox &island = layer->lslices_bboxes[i]; + CHECK(body.min == island.min); + CHECK(body.max == island.max); + holes += layer->lslices[i].holes.size(); + } + CHECK(holes == 1); + } +} + +TEST_CASE("Body centering survives islands merging and splitting between layers", "[PrintObject][SurfaceInfill][Regression]") +{ + const bool separated = GENERATE(false, true); + CAPTURE(separated); + // Orca: Four posts join through horizontal then vertical rails, creating a + // cycle of overlaps before splitting into four islands again. This exercises + // redundant connections and indexing either adjacent layer. A fifth post + // stays separate at every height. + TriangleMesh mesh; + for (int x : {0, 8}) + for (int y : {0, 8}) { + TriangleMesh post = make_cube(4, 4, 1); + post.translate(x, y, 0); + mesh.merge(post); + } + for (int y : {0, 8}) { + TriangleMesh rail = make_cube(12, 4, 0.2); + rail.translate(0, y, 0.2); + mesh.merge(rail); + } + for (int x : {0, 8}) { + TriangleMesh rail = make_cube(4, 12, 0.2); + rail.translate(x, 0, 0.4); + mesh.merge(rail); + } + TriangleMesh isolated = make_cube(4, 4, 1); + isolated.translate(20, 0, 0); + mesh.merge(isolated); + + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"separated_infills", separated}, + {"center_of_surface_pattern", separated ? "each_surface" : "each_model"}, + {"layer_height", 0.2}, + {"initial_layer_print_height", 0.2}, + {"elefant_foot_compensation", 0}, + {"wall_loops", 1}}); + Print print; + Model model; + init_print({mesh}, print, model, config, nullptr, false); + // Orca: Prepare body bounds through the public pipeline, then inspect the object read-only. + print.process(); + const PrintObject &object = *print.objects().front(); + REQUIRE(object.layer_count() == 5); + REQUIRE(object.get_layer(0)->lslices.size() == 5); + REQUIRE(object.get_layer(1)->lslices.size() == 3); + REQUIRE(object.get_layer(2)->lslices.size() == 3); + REQUIRE(object.get_layer(4)->lslices.size() == 5); + + BoundingBox isolated_bbox = object.get_layer(0)->lslices_bboxes.front(); + for (const BoundingBox &bbox : object.get_layer(0)->lslices_bboxes) + if (bbox.min.x() > isolated_bbox.min.x()) + isolated_bbox = bbox; + BoundingBox connected_bbox; + for (const Layer *layer : object.layers()) + for (const BoundingBox &bbox : layer->lslices_bboxes) + if (bbox.min.x() < isolated_bbox.min.x()) + connected_bbox.merge(bbox); + for (const Layer *layer : object.layers()) { + REQUIRE(layer->lslices_separated_component_bboxes.size() == layer->lslices.size()); + for (size_t i = 0; i < layer->lslices.size(); ++ i) { + const BoundingBox &expected = layer->lslices_bboxes[i].min.x() < isolated_bbox.min.x() ? connected_bbox : isolated_bbox; + const BoundingBox &actual = layer->lslices_separated_component_bboxes[i]; + CHECK(actual.min == expected.min); + CHECK(actual.max == expected.max); + } + } +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 5c10ab1496..2f859f46fe 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(${_TEST_NAME}_tests test_preset_setting_id.cpp test_preset_diff.cpp test_vendor_cache.cpp + test_preset_options.cpp test_elephant_foot_compensation.cpp test_fill_corner_smoothing.cpp test_filament_mixer.cpp diff --git a/tests/libslic3r/test_arrange.cpp b/tests/libslic3r/test_arrange.cpp index a9fb51e352..3906cba8ba 100644 --- a/tests/libslic3r/test_arrange.cpp +++ b/tests/libslic3r/test_arrange.cpp @@ -4,6 +4,8 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" using namespace Slic3r; using namespace Slic3r::arrangement; @@ -24,11 +26,13 @@ ArrangePolygon make_square(coord_t side) return ap; } -ArrangePolygons squares(int n, double side_mm) +ArrangePolygons squares(int n, double side_mm, double height_mm = 0.) { ArrangePolygons items; - for (int i = 0; i < n; ++i) + for (int i = 0; i < n; ++i) { items.emplace_back(make_square(scaled(side_mm))); + items.back().height = height_mm; + } return items; } @@ -82,6 +86,38 @@ void require_no_overlap(const ArrangePolygons &items) REQUIRE(disjoint(placed_shapes(items))); } +// The sequential-print floor is chosen by comparing object height against the nozzle, +// so the two are defined together and every expectation is derived from them. +constexpr double NOZZLE_HEIGHT_MM = 2.5; +constexpr double CLEARANCE_MM = 30.; +constexpr double NOZZLE_FLOOR_MM = MAX_OUTER_NOZZLE_DIAMETER / 2.; + +ArrangeParams seq_print_params(coord_t min_dist) +{ + ArrangeParams p = quiet_params(min_dist); + p.is_seq_print = true; + p.clearance_radius = float(CLEARANCE_MM); + p.nozzle_height = float(NOZZLE_HEIGHT_MM); + p.object_skirt_offset = 0.f; + return p; +} + +// update_selected_items_inflation reads the bed out of the config to cap inflation. +DynamicPrintConfig bed_config() +{ + DynamicPrintConfig c; + c.set_key_value("printable_area", new ConfigOptionPoints{{0, 0}, {200, 0}, {200, 200}, {0, 200}}); + return c; +} + +ArrangePolygons squares_of_heights(const std::vector &heights_mm) +{ + ArrangePolygons items; + for (double height_mm : heights_mm) + items.push_back(squares(1, 20., height_mm).front()); + return items; +} + } // namespace // Prove the overlap check the other tests rely on actually detects overlap. @@ -222,3 +258,61 @@ TEST_CASE("Arrange aligns the pile to a custom center", "[Arrange]") REQUIRE(ap.bed_idx == 0); require_no_overlap(items); } + +TEST_CASE("Sequential print floors the object distance by object height", "[Arrange]") +{ + // The only place sequential-print clearance is enforced. The arrange menu offers + // no floor of its own, so a stored 0 has to be raised here or not at all. + struct Case + { + std::string description; + std::vector heights; + double skirt_offset_mm; + double expected_floor_mm; + }; + + auto c = GENERATE(values({ + {"objects taller than the nozzle need the full clearance", {NOZZLE_HEIGHT_MM * 2, NOZZLE_HEIGHT_MM * 2}, 0., CLEARANCE_MM}, + {"an object exactly at the nozzle height counts as tall", {NOZZLE_HEIGHT_MM, NOZZLE_HEIGHT_MM}, 0., CLEARANCE_MM}, + {"one tall object among short ones is enough", {NOZZLE_HEIGHT_MM / 2, NOZZLE_HEIGHT_MM * 2}, 0., CLEARANCE_MM}, + {"objects the nozzle clears keep only the nozzle-width floor", {NOZZLE_HEIGHT_MM / 2, NOZZLE_HEIGHT_MM / 2}, 0., NOZZLE_FLOOR_MM}, + {"a wide skirt raises the floor for short objects", {NOZZLE_HEIGHT_MM / 2, NOZZLE_HEIGHT_MM / 2}, 3., 6.}, + })); + + DYNAMIC_SECTION(c.description) + { + ArrangePolygons items = squares_of_heights(c.heights); + DynamicPrintConfig cfg = bed_config(); + ArrangeParams p = seq_print_params(0); + p.object_skirt_offset = float(c.skirt_offset_mm); + + update_selected_items_inflation(items, &cfg, p); + + CHECK(p.min_obj_distance >= scaled(c.expected_floor_mm)); + CHECK(p.min_obj_distance <= scaled(c.expected_floor_mm + 0.01)); + // Half each, so a pair ends up a full min_obj_distance apart. + CHECK(items.front().inflation == p.min_obj_distance / 2); + } +} + +TEST_CASE("Sequential print keeps an object distance already above the floor", "[Arrange]") +{ + const coord_t stored = scaled(CLEARANCE_MM * 2); + ArrangePolygons items = squares_of_heights({NOZZLE_HEIGHT_MM * 2, NOZZLE_HEIGHT_MM * 2}); + DynamicPrintConfig cfg = bed_config(); + ArrangeParams p = seq_print_params(stored); + + update_selected_items_inflation(items, &cfg, p); + CHECK(p.min_obj_distance == stored); +} + +TEST_CASE("Layered printing does not floor the object distance", "[Arrange]") +{ + ArrangePolygons items = squares_of_heights({NOZZLE_HEIGHT_MM * 2, NOZZLE_HEIGHT_MM * 2}); + DynamicPrintConfig cfg = bed_config(); + ArrangeParams p = seq_print_params(0); + p.is_seq_print = false; + + update_selected_items_inflation(items, &cfg, p); + CHECK(p.min_obj_distance == 0); +} diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index bd147b5881..208bbc6cf0 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -15,6 +15,8 @@ #include #include +#include + using namespace Slic3r; SCENARIO("Generic config validation performs as expected.", "[Config]") { @@ -488,6 +490,59 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[ CHECK(reloaded.option("slicing_pipeline_plugin")->values == refs); } +TEST_CASE("save_to_json writes the same document to a stream as to a file", "[Config]") { + DynamicPrintConfig config; + config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); + config.set_key_value("wall_loops", new ConfigOptionInt(3)); + config.set_key_value("filament_type", new ConfigOptionStrings({ "PLA", "PETG" })); + config.set_key_value("machine_start_gcode", new ConfigOptionString("G28\nG1 Z5")); + + ScopedTemporaryFile tmp(".json"); + config.save_to_json(tmp.string(), "test_preset", "User", "1.0.0.0"); + std::string file_contents; + { + boost::nowide::ifstream ifs(tmp.string()); + file_contents.assign(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); + } + // The file format: one tab per nesting level and a trailing newline. + REQUIRE_FALSE(file_contents.empty()); + CHECK(file_contents.rfind("{\n\t\"", 0) == 0); + CHECK(file_contents.back() == '\n'); + + std::ostringstream strict, replaced; + config.save_to_json(strict, "test_preset", "User", "1.0.0.0"); + config.save_to_json(replaced, "test_preset", "User", "1.0.0.0", true); + CHECK(strict.str() == file_contents); + CHECK(replaced.str() == file_contents); + CHECK(nlohmann::json::parse(strict.str())["machine_start_gcode"] == "G28\nG1 Z5"); +} + +TEST_CASE("save_to_json replaces invalid UTF-8 in a stream only when asked", "[Config]") { + DynamicPrintConfig config; + config.set_key_value("machine_start_gcode", new ConfigOptionString("G28 ; \xff")); + + std::ostringstream strict, replaced; + CHECK_THROWS_AS(config.save_to_json(strict, "test_preset", "User", "1.0.0.0"), nlohmann::json::type_error); + REQUIRE_NOTHROW(config.save_to_json(replaced, "test_preset", "User", "1.0.0.0", true)); + CHECK(nlohmann::json::parse(replaced.str())["machine_start_gcode"] == "G28 ; \xEF\xBF\xBD"); +} + +TEST_CASE("save_to_json leaves an existing file untouched when the config cannot be serialized", "[Config]") { + DynamicPrintConfig config; + config.set_key_value("machine_start_gcode", new ConfigOptionString("G28 ; \xff")); + + ScopedTemporaryFile tmp(".json"); + { + boost::nowide::ofstream ofs(tmp.string()); + ofs << "previous"; + } + CHECK_THROWS_AS(config.save_to_json(tmp.string(), "test_preset", "User", "1.0.0.0"), nlohmann::json::type_error); + + boost::nowide::ifstream ifs(tmp.string()); + const std::string contents((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + CHECK(contents == "previous"); +} + TEST_CASE("plugin capability references survive string-map serialization", "[Config][plugins]") { const std::vector refs = { "master_plugin;;header-stamp", @@ -1091,3 +1146,128 @@ TEST_CASE("get_filament_type treats empty vector options as absent", "[Config][F REQUIRE(displayed == "Sup.PLA"); } } + +namespace { + +// min_object_distance reads exactly these three options. +DynamicPrintConfig spacing_config(PrinterTechnology tech, PrintSequence seq, double clearance_radius) +{ + DynamicPrintConfig c; + c.set_key_value("printer_technology", new ConfigOptionEnum(tech)); + c.set_key_value("print_sequence", new ConfigOptionEnum(seq)); + c.set_key_value("extruder_clearance_radius", new ConfigOptionFloat(clearance_radius)); + return c; +} + +} // namespace + +TEST_CASE("min_object_distance floors object spacing per print sequence", "[Config]") +{ + struct Case + { + std::string description; + PrinterTechnology tech; + PrintSequence sequence; + double clearance_radius; + double expected; + }; + + auto c = GENERATE(values({ + {"sequential FFF takes a clearance radius above the floor", ptFFF, PrintSequence::ByObject, 12., 12.}, + {"sequential FFF holds the floor at the radius", ptFFF, PrintSequence::ByObject, 6., 6.}, + {"sequential FFF holds the floor below the radius", ptFFF, PrintSequence::ByObject, 4., 6.}, + {"layered FFF ignores the clearance radius", ptFFF, PrintSequence::ByLayer, 12., 6.}, + {"SLA is a flat 6mm", ptSLA, PrintSequence::ByObject, 12., 6.}, + {"SLA ignores the print sequence too", ptSLA, PrintSequence::ByLayer, 12., 6.}, + })); + + DYNAMIC_SECTION(c.description) + { + CHECK_THAT(min_object_distance(spacing_config(c.tech, c.sequence, c.clearance_radius)), + Catch::Matchers::WithinAbs(c.expected, 1e-9)); + } +} + +TEST_CASE("min_object_distance yields no floor when an FFF config lacks the options", "[Config]") +{ + // Missing options yield 0 rather than an error, so a caller gets no floor at all. + SECTION("no clearance radius") { + DynamicPrintConfig c; + c.set_key_value("printer_technology", new ConfigOptionEnum(ptFFF)); + c.set_key_value("print_sequence", new ConfigOptionEnum(PrintSequence::ByObject)); + CHECK_THAT(min_object_distance(c), Catch::Matchers::WithinAbs(0., 1e-9)); + } + + SECTION("no print sequence") { + DynamicPrintConfig c; + c.set_key_value("printer_technology", new ConfigOptionEnum(ptFFF)); + c.set_key_value("extruder_clearance_radius", new ConfigOptionFloat(12.)); + CHECK_THAT(min_object_distance(c), Catch::Matchers::WithinAbs(0., 1e-9)); + } + + SECTION("nothing at all") { + CHECK_THAT(min_object_distance(DynamicPrintConfig{}), Catch::Matchers::WithinAbs(0., 1e-9)); + } + + SECTION("an unset printer technology is treated as FFF") { + DynamicPrintConfig c; + c.set_key_value("print_sequence", new ConfigOptionEnum(PrintSequence::ByObject)); + c.set_key_value("extruder_clearance_radius", new ConfigOptionFloat(12.)); + CHECK_THAT(min_object_distance(c), Catch::Matchers::WithinAbs(12., 1e-9)); + } +} + +TEST_CASE("Static print configs compare, order and hash by their option values", "[Config]") +{ + // PrintObjectConfig comes from PRINT_CONFIG_CLASS_DEFINE; PrintConfig combines MachineEnvelopeConfig + // and GCodeConfig through PRINT_CONFIG_CLASS_DERIVED_DEFINE. Both generate hash(), operator==, + // operator< and the option registration from the same option list. The hash inequalities use fixed + // inputs, so they are deterministic; they check that hash() covers the changed option. + SECTION("default-constructed configs are equal and find their options by key") + { + PrintObjectConfig a, b; + REQUIRE(a == b); + REQUIRE(a.hash() == b.hash()); + REQUIRE_FALSE(a < b); + REQUIRE_FALSE(b < a); + REQUIRE(a.optptr("layer_height") == &a.layer_height); + REQUIRE(a.optptr("brim_object_gap") == &a.brim_object_gap); + } + + SECTION("one differing option makes the configs unequal and orders them") + { + PrintObjectConfig a, b; + b.layer_height.value = a.layer_height.value + 0.05; + REQUIRE(a != b); + REQUIRE(a.hash() != b.hash()); + REQUIRE(a < b); + REQUIRE_FALSE(b < a); + } + + SECTION("ordering is decided by the first option in declaration order that differs") + { + PrintObjectConfig a, b; + a.brim_object_gap.value = b.brim_object_gap.value + 1.0; // declared first + a.layer_height.value = b.layer_height.value - 0.05; // declared later, points the other way + REQUIRE(b < a); + REQUIRE_FALSE(a < b); + } + + SECTION("a derived config sees differences in its parents and in its own options") + { + PrintConfig a, b; + REQUIRE(a == b); + REQUIRE(a.hash() == b.hash()); + + b.gcode_flavor.value = b.gcode_flavor.value == gcfMarlinLegacy ? gcfKlipper : gcfMarlinLegacy; // GCodeConfig parent + REQUIRE(a != b); + REQUIRE(a.hash() != b.hash()); + + PrintConfig c, d; + d.skirt_distance.value = c.skirt_distance.value + 1.0; // PrintConfig's own list + REQUIRE(c != d); + REQUIRE(c.hash() != d.hash()); + REQUIRE(c.optptr("skirt_distance") == &c.skirt_distance); + REQUIRE(c.optptr("gcode_flavor") == &c.gcode_flavor); + } +} diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp index 2469789d5b..d8e09539bb 100644 --- a/tests/libslic3r/test_config_variant_expansion.cpp +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -484,6 +484,34 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); REQUIRE(config.option("filament_self_index")->values == std::vector({1, 2})); } + + SECTION("a variant option shorter than the filament slots keeps its first value instead of zero") { + DynamicPrintConfig config; + config.option("extruder_type", true)->values = {etDirectDrive, etDirectDrive}; + config.option("nozzle_volume_type", true)->values = {nvtStandard, nvtHighFlow}; + config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", + "Direct Drive Standard,Direct Drive High Flow"}; + make_filament_arrays(config); + config.option("filament_map", true)->values = {1, 2}; + // no loaded preset carries the key, so only its single registered default is present + config.option("filament_cooling_before_tower", true)->values = {10.}; + // only the first filament's two variant columns were loaded + config.option("filament_ramming_volumetric_speed", true)->values = {-1., -2.}; + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys, + "filament_self_index", "filament_extruder_variant"); + + // filament 2 resolves to column 3 (its extruder's High Flow column), past the end of both vectors + REQUIRE_THAT(config.option("filament_cooling_before_tower")->values, + Catch::Matchers::Approx(std::vector({10., 10.}))); + REQUIRE_THAT(config.option("filament_ramming_volumetric_speed")->values, + Catch::Matchers::Approx(std::vector({-1., -1.}))); + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); + } } // update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 6844ecb6ac..9a11f49faa 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -1,4 +1,6 @@ +#ifndef NOMINMAX #define NOMINMAX +#endif #include #include "test_utils.hpp" diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index ecdede7053..73d244cf42 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -987,6 +987,193 @@ TEST_CASE("Resolution terminates when no vendor manifest exists", "[Preset][Bund CHECK(error == "Preset was not found in the loaded bundle"); } +TEST_CASE("Manifest-backed resolution reuses the vendor tree it already loaded", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path process_dir = dir.path() / "Acme" / "process"; + fs::create_directories(process_dir); + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","process_list":[)" + << R"({"name":"fdm_process_common","sub_path":"process/base.json"},)" + << R"({"name":"Acme First","sub_path":"process/first.json"},)" + << R"({"name":"Acme Second","sub_path":"process/second.json"}]})"; + auto write_base = [&](double travel_speed) { + std::ofstream((process_dir / "base.json").string()) + << R"({"type":"process","name":"fdm_process_common","from":"system",)" + << R"("instantiation":"false","travel_speed":[")" << travel_speed << R"("]})"; + }; + auto write_child = [&](const std::string &file, const std::string &name) { + std::ofstream((process_dir / file).string()) + << R"({"type":"process","name":")" << name << R"(","from":"system",)" + << R"("instantiation":"true","inherits":"fdm_process_common"})"; + }; + write_base(111.0); + write_child("first.json", "Acme First"); + write_child("second.json", "Acme Second"); + + auto travel_speed = [&](PresetBundle &bundle, const std::string &file) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common"; + std::string error; + REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, (process_dir / file).string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + return raw.option("travel_speed")->values.front(); + }; + + PresetBundle bundle; + CHECK_THAT(travel_speed(bundle, "first.json"), Catch::Matchers::WithinAbs(111.0, 1e-6)); + + // Only a reload would see this change. + write_base(222.0); + CHECK_THAT(travel_speed(bundle, "second.json"), Catch::Matchers::WithinAbs(111.0, 1e-6)); + + PresetBundle fresh; + CHECK_THAT(travel_speed(fresh, "second.json"), Catch::Matchers::WithinAbs(222.0, 1e-6)); +} + +TEST_CASE("Manifest-backed resolution does not keep a vendor tree that failed to load", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path child_file = dir.path() / "Acme" / "process" / "child.json"; + auto write_manifest = [&](const std::string &leading_entry) { + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","process_list":[)" << leading_entry + << R"({"name":"Acme Process","sub_path":"process/child.json"}]})"; + }; + write_manifest("123,"); + fs::create_directories(child_file.parent_path()); + std::ofstream(child_file.string()) + << R"({"type":"process","name":"Acme Process","from":"system",)" + << R"("instantiation":"true","layer_height":"0.2"})"; + + PresetBundle bundle; + auto resolve = [&](std::string &error) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common"; + return bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, child_file.string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error); + }; + + std::string error; + CHECK_FALSE(resolve(error)); + CHECK_FALSE(error.empty()); + + write_manifest(""); + error.clear(); + CHECK(resolve(error)); + CHECK(error.empty()); +} + +TEST_CASE("Manifest-backed resolution reuses the library base for type-probed files", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path library_pet = dir.path() / PresetBundle::ORCA_FILAMENT_LIBRARY / "filament" / "pet.json"; + const fs::path filament_dir = dir.path() / "Acme" / "filament"; + + std::ofstream((dir.path() / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json")).string()) + << R"({"version":"1.0.0","name":"OrcaFilamentLibrary","filament_list":[)" + << R"({"name":"fdm_filament_pet","sub_path":"filament/pet.json","filament_id":"GFL99"}]})"; + fs::create_directories(library_pet.parent_path()); + auto write_library_pet = [&](double density) { + std::ofstream(library_pet.string()) + << R"({"type":"filament","name":"fdm_filament_pet","from":"system",)" + << R"("filament_id":"GFL99","instantiation":"false",)" + << R"("filament_type":["PETG"],"filament_density":[")" << density << R"("]})"; + }; + write_library_pet(1.27); + + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","filament_list":[)" + << R"({"name":"Acme PETG","sub_path":"filament/petg.json","filament_id":"GFA00"},)" + << R"({"name":"Acme PETG Matte","sub_path":"filament/petg_matte.json","filament_id":"GFA01"}]})"; + fs::create_directories(filament_dir); + auto write_child = [&](const std::string &file, const std::string &name, const std::string &filament_id) { + std::ofstream((filament_dir / file).string()) + << R"({"type":"filament","name":")" << name << R"(","from":"system",)" + << R"("filament_id":")" << filament_id << R"(","instantiation":"true","inherits":"fdm_filament_pet"})"; + }; + write_child("petg.json", "Acme PETG", "GFA00"); + write_child("petg_matte.json", "Acme PETG Matte", "GFA01"); + + auto density = [](const DynamicPrintConfig &config) { + return config.option("filament_density")->values.front(); + }; + + PresetBundle bundle; + DynamicPrintConfig first; + first.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_pet"; + std::string error; + REQUIRE(bundle.resolve_preset_config(first, Preset::TYPE_FILAMENT, (filament_dir / "petg.json").string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK_THAT(density(first), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + // Only a reload would see this change. + write_library_pet(1.5); + + DynamicPrintConfig second; + Preset::Type type = Preset::TYPE_INVALID; + REQUIRE(bundle.resolve_preset_config_type(second, type, (filament_dir / "petg_matte.json").string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK(type == Preset::TYPE_FILAMENT); + CHECK_THAT(density(second), Catch::Matchers::WithinAbs(1.27, 1e-6)); +} + +TEST_CASE("Manifest-backed resolution shares the library between vendors under one root", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path library_dir = dir.path() / PresetBundle::ORCA_FILAMENT_LIBRARY / "filament"; + + std::ofstream((dir.path() / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json")).string()) + << R"({"version":"1.0.0","name":"OrcaFilamentLibrary","filament_list":[)" + << R"({"name":"fdm_filament_pet","sub_path":"filament/pet.json","filament_id":"GFL99"},)" + << R"({"name":"Generic PETG","sub_path":"filament/generic_petg.json","filament_id":"GFL98"}]})"; + fs::create_directories(library_dir); + auto write_library_pet = [&](double density) { + std::ofstream((library_dir / "pet.json").string()) + << R"({"type":"filament","name":"fdm_filament_pet","from":"system",)" + << R"("filament_id":"GFL99","instantiation":"false",)" + << R"("filament_type":["PETG"],"filament_density":[")" << density << R"("]})"; + }; + write_library_pet(1.27); + std::ofstream((library_dir / "generic_petg.json").string()) + << R"({"type":"filament","name":"Generic PETG","from":"system",)" + << R"("filament_id":"GFL98","instantiation":"true","inherits":"fdm_filament_pet"})"; + + auto write_vendor = [&](const std::string &vendor, const std::string &filament_id) { + const fs::path filament_dir = dir.path() / vendor / "filament"; + fs::create_directories(filament_dir); + std::ofstream((dir.path() / (vendor + ".json")).string()) + << R"({"version":"1.0.0","name":")" << vendor << R"(","filament_list":[)" + << R"({"name":")" << vendor << R"( PETG","sub_path":"filament/petg.json","filament_id":")" << filament_id << R"("}]})"; + std::ofstream((filament_dir / "petg.json").string()) + << R"({"type":"filament","name":")" << vendor << R"( PETG","from":"system",)" + << R"("filament_id":")" << filament_id << R"(","instantiation":"true","inherits":"fdm_filament_pet"})"; + return filament_dir / "petg.json"; + }; + const fs::path acme_petg = write_vendor("Acme", "GFA00"); + const fs::path beta_petg = write_vendor("Beta", "GFB00"); + + auto density = [&](PresetBundle &bundle, const fs::path &file) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_pet"; + std::string error; + REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_FILAMENT, file.string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + return raw.option("filament_density")->values.front(); + }; + + PresetBundle bundle; + CHECK_THAT(density(bundle, acme_petg), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + // Only a reload would see this change. + write_library_pet(1.5); + CHECK_THAT(density(bundle, beta_petg), Catch::Matchers::WithinAbs(1.27, 1e-6)); + CHECK_THAT(density(bundle, library_dir / "generic_petg.json"), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + PresetBundle fresh; + CHECK_THAT(density(fresh, beta_petg), Catch::Matchers::WithinAbs(1.5, 1e-6)); +} + // Orca: a filament in the Orca Filament Library that names its compatible printers has to hide the generic // library filament sharing its alias, the same way a vendor owned filament does. Otherwise both are compatible // with that printer and the plater combo box lists the shared alias twice. diff --git a/tests/libslic3r/test_preset_options.cpp b/tests/libslic3r/test_preset_options.cpp new file mode 100644 index 0000000000..1763f5fbc7 --- /dev/null +++ b/tests/libslic3r/test_preset_options.cpp @@ -0,0 +1,70 @@ +// Regression test for the "option in def + UI but missing from preset key list" +// crash class. +// +// The print preset's DynamicPrintConfig is seeded with only the keys returned by +// Preset::print_options() (PresetBundle.cpp). A field added to PrintRegionConfig +// or PrintObjectConfig and registered via print_config_def plus a TabPrint +// optgroup, but left out of print_options(), still gets its control built; on tab +// activation reload_config -> get_config_value dispatches to opt_bool/opt_int on a +// DynamicPrintConfig with no entry for the key, and the accessor null-derefs the +// result of option(key). +// +// The invariant asserted here is the inverse: every key declared on +// PrintRegionConfig and PrintObjectConfig appears in Preset::print_options() or +// Preset::filament_options(), the two preset key lists that seed a print preset's +// DynamicConfig. + +#include + +#include "libslic3r/Preset.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include + +using namespace Slic3r; + +namespace { + +// Deprecated keys renamed in handle_legacy() (ironing_direction -> +// ironing_angle, wall_infill_order -> wall_sequence); neither is in a +// preset list. Register new options in a preset list, not here. +const std::set kDeprecatedRegionFields = { + "ironing_direction", + "wall_infill_order", +}; + +void check_keys_are_in_a_preset(const t_config_option_keys& keys, const std::string& class_name) +{ + REQUIRE_FALSE(keys.empty()); + const auto& print_options = Preset::print_options(); + const auto& filament_options = Preset::filament_options(); + const std::set in_print(print_options.begin(), print_options.end()); + const std::set in_filament(filament_options.begin(), filament_options.end()); + for (const std::string& key : keys) { + DYNAMIC_SECTION(class_name << "::" << key) + { + INFO("'" << key << "' on " << class_name + << " is missing from " + "Preset::print_options()/filament_options(); add it to " + "s_Preset_print_options (or s_Preset_filament_options) in Preset.cpp."); + const bool registered = in_print.count(key) || in_filament.count(key) || kDeprecatedRegionFields.count(key); + REQUIRE(registered); + } + } +} + +} // namespace + +// Bodies are laid out like the rest of the test suite rather than collapsed +// onto the brace line. +// clang-format off +TEST_CASE("Every PrintRegionConfig field is registered in a preset key list", "[Preset][Config]") +{ + check_keys_are_in_a_preset(PrintRegionConfig::defaults().keys(), "PrintRegionConfig"); +} + +TEST_CASE("Every PrintObjectConfig field is registered in a preset key list", "[Preset][Config]") +{ + check_keys_are_in_a_preset(PrintObjectConfig::defaults().keys(), "PrintObjectConfig"); +} +// clang-format on diff --git a/tests/libslic3r/test_utils.cpp b/tests/libslic3r/test_utils.cpp index 484438127c..7880b783f1 100644 --- a/tests/libslic3r/test_utils.cpp +++ b/tests/libslic3r/test_utils.cpp @@ -4,6 +4,8 @@ #include "test_utils.hpp" +#include + #include #include #include @@ -88,3 +90,65 @@ TEST_CASE("copy_file reports the OS error when the destination cannot be written REQUIRE(std::all_of(code.begin(), code.end(), [](unsigned char c) { return std::isdigit(c) != 0; })); #endif // _WIN32 } + +TEST_CASE("A resolved input path still names the same file after the working directory changes", "[utils]") { + ScopedTemporaryFile model(".3mf"); + { std::ofstream out(model.string()); out << "3mf"; } + const std::string name = model.path().filename().string(); + + // Resolve the bare name from the directory holding the file, then move away from it. The guard + // restores the directory the test started in, wherever this leaves it. + ScopedWorkingDirectory cwd(model.path().parent_path()); + const std::string resolved = resolve_cli_input_path(name); + boost::filesystem::current_path(boost::filesystem::path(TEST_DATA_DIR)); + + REQUIRE(boost::filesystem::exists(resolved)); + REQUIRE(boost::filesystem::equivalent(resolved, model.path())); + // Control: the bare name finds nothing from here, so resolving it this late would have failed. + REQUIRE_FALSE(boost::filesystem::exists(name)); +} + +TEST_CASE("resolve_cli_input_path completes a relative path against the working directory", "[utils]") { + ScopedWorkingDirectory cwd(boost::filesystem::temp_directory_path()); + // Read back rather than reusing temp_directory_path(): changing to it resolves any symlink. + const boost::filesystem::path here = boost::filesystem::current_path(); + + SECTION("a bare name") { + REQUIRE(resolve_cli_input_path("model.3mf") == (here / "model.3mf").make_preferred().string()); + } + SECTION("a ./ prefix is dropped") { + REQUIRE(resolve_cli_input_path("./model.3mf") == (here / "model.3mf").make_preferred().string()); + } + SECTION("a ../ traversal is collapsed") { + REQUIRE(resolve_cli_input_path("../model.3mf") == (here.parent_path() / "model.3mf").make_preferred().string()); + } +} + +TEST_CASE("resolve_cli_input_path leaves inputs that must not be completed unchanged", "[utils]") { + SECTION("an absolute path") { + const boost::filesystem::path absolute = (boost::filesystem::temp_directory_path() / "model.3mf").make_preferred(); + REQUIRE(resolve_cli_input_path(absolute.string()) == absolute.string()); + } +#ifdef _WIN32 + // Every absolute form Windows accepts opens today, so each must come back byte for byte: + // normalizing them would rewrite the forward slashes and rebuild the \\?\ and UNC prefixes. + SECTION("an absolute Windows path of any form") { + for (const std::string absolute : {R"(C:\models\model.3mf)", + R"(C:/models/model.3mf)", + R"(\\server\share\model.3mf)", + R"(\\?\C:\models\model.3mf)"}) + REQUIRE(resolve_cli_input_path(absolute) == absolute); + } +#endif + // These are downloaded rather than opened, and completing one would produce a path, not a URL. + SECTION("a custom open protocol URL") { + for (const std::string url : {"orcaslicer://open/?file=https://example.com/model.3mf", + "prusaslicer://open/?file=https://example.com/model.3mf", + "bambustudio://open/?file=https://example.com/model.3mf", + "cura://open/?file=https://example.com/model.3mf"}) + REQUIRE(resolve_cli_input_path(url) == url); + } + SECTION("an empty argument") { + REQUIRE(resolve_cli_input_path("").empty()); + } +} diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index e3fbbe8fab..0b04e6ad11 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -176,4 +176,22 @@ inline void write_debug_stream([[maybe_unused]] const std::string &name, [[maybe #endif } +// Changes the working directory and restores the previous one on scope exit, including when an +// assertion throws. It is process wide state shared with every other test. +class ScopedWorkingDirectory +{ +public: + explicit ScopedWorkingDirectory(const boost::filesystem::path &dir) + : m_previous(boost::filesystem::current_path()) + { + boost::filesystem::current_path(dir); + } + ~ScopedWorkingDirectory() { boost::system::error_code ec; boost::filesystem::current_path(m_previous, ec); } + ScopedWorkingDirectory(const ScopedWorkingDirectory &) = delete; + ScopedWorkingDirectory &operator=(const ScopedWorkingDirectory &) = delete; + +private: + boost::filesystem::path m_previous; +}; + #endif // SLIC3R_TEST_UTILS