diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index ed106ec65e..24317b0add 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -413,6 +413,14 @@ jobs: GH_TOKEN: ${{ github.token }} run: | api="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches" + # The save step reports success even when its tar failed, so keep + # the older entries unless the new one is listed. + if ! curl -sSf -H "Authorization: Bearer $GH_TOKEN" \ + "$api?ref=$GITHUB_REF&key=$CCACHE_ENTRY" \ + | jq -e --arg entry "$CCACHE_ENTRY" 'any(.actions_caches[]; .key == $entry)' > /dev/null; then + echo "$CCACHE_ENTRY was not saved; keeping the older entries." + exit 0 + fi 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" \ diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 85458615b4..2ea72d0773 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -798,6 +798,13 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | + # The save step reports success even when its tar failed, so keep + # the older entries unless the new one is listed. + if ! gh cache list --ref "$GITHUB_REF" --key "$CCACHE_ENTRY" --json key \ + | jq -e --arg entry "$CCACHE_ENTRY" 'any(.[]; .key == $entry)' > /dev/null; then + echo "$CCACHE_ENTRY was not saved; keeping the older entries." + exit 0 + fi 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' \ diff --git a/.github/workflows/parity_nightly.yml b/.github/workflows/parity_nightly.yml index 79f5c9b514..44937fdad5 100644 --- a/.github/workflows/parity_nightly.yml +++ b/.github/workflows/parity_nightly.yml @@ -5,8 +5,9 @@ # 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. +# sources checked out at the commit that build was made from; a manual run can +# name another branch, or pin one build by its run id. Nothing here gates a +# build or a PR. name: Parity Nightly on: @@ -20,9 +21,13 @@ on: required: false default: "main" build_branch: - description: "branch whose latest successful build_all artifact to test" + description: "branch whose newest successful build_all artifact to test (a PR build is the PR merged into its base; sources are checked out at the PR head)" required: false default: "main" + build_run_id: + description: "build_all run id to test instead of build_branch's newest (same PR caveat)" + required: false + default: "" fixtures: description: "harness fixture ids, space-separated (empty = all)" required: false @@ -50,14 +55,53 @@ jobs: env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} + BRANCH: ${{ inputs.build_branch || 'main' }} + RUN_ID: ${{ inputs.build_run_id }} + SCHEDULED: ${{ github.event_name == 'schedule' }} 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" + if [ -n "$RUN_ID" ]; then + [[ $RUN_ID =~ ^[0-9]+$ ]] || { echo "build_run_id must be a numeric run id, got '$RUN_ID'" >&2; exit 1; } + # a pinned build is read directly, not through a search; it must come + # from this repository, because the later jobs check out its commit here + found=$(gh api "repos/$GH_REPO/actions/runs/$RUN_ID" --jq \ + 'select(.path == ".github/workflows/build_all.yml" and .conclusion == "success" + and .head_repository.full_name == env.GH_REPO) + | "\(.id) \(.head_sha) \(.created_at)"') + [ -n "$found" ] || { echo "run $RUN_ID is not a successful build_all run of $GH_REPO" >&2; exit 1; } + else + # GitHub serves filtered run listings (branch=, status=, head_sha=, ...) + # from a search index that has returned weeks-old results, while the + # unfiltered listing stays current, so list unfiltered and filter here. + # The repository check keeps out fork PRs whose branch has the same + # name. A feature branch is normally built only for its PR, and a PR + # build compiles the PR merged into its base rather than head_sha, so + # a build of the branch itself (push or dispatch) is preferred when + # the same page has one. + pick='([.workflow_runs[] | select(.head_branch == env.BRANCH and .conclusion == "success" + and .head_repository.full_name == env.GH_REPO)] + | map(select(.event != "pull_request"))[0] // .[0]) + | select(.) | "\(.id) \(.head_sha) \(.created_at)"' + # a page of 100 runs spans about a day and a half; a manual run may + # target a branch that last built weeks ago + pages=3 + if [ "$SCHEDULED" != true ]; then pages=20; fi + found="" + for page in $(seq "$pages"); do + found=$(gh api "repos/$GH_REPO/actions/workflows/build_all.yml/runs?per_page=100&page=$page" --jq "$pick") + if [ -n "$found" ]; then break; fi + done + [ -n "$found" ] || { echo "no successful $BRANCH build among the last $((pages * 100)) build_all runs; pass build_run_id to test an older one" >&2; exit 1; } + fi + read -r run_id head_sha created <<< "$found" + # the nightly fails rather than report on a stale build + if [ "$SCHEDULED" = true ] && [ $(( $(date +%s) - $(date -d "$created" +%s) )) -gt 172800 ]; then + echo "newest $BRANCH build $run_id is from $created, over 48 hours old" >&2 + exit 1 + fi + printf 'run_id=%s\nhead_sha=%s\n' "$run_id" "$head_sha" >> "$GITHUB_OUTPUT" cat "$GITHUB_OUTPUT" + echo "Testing build [$run_id](https://github.com/$GH_REPO/actions/runs/$run_id) of \`$head_sha\`, built $created" >> "$GITHUB_STEP_SUMMARY" effect: name: Override sweep effect stage (shard ${{ matrix.shard }}) diff --git a/resources/profiles/FlyingBear/machine/Ghost7/FlyingBear Ghost7 0.4 nozzle.json b/resources/profiles/FlyingBear/machine/Ghost7/FlyingBear Ghost7 0.4 nozzle.json index fd14f77ebf..9f195a4eca 100644 --- a/resources/profiles/FlyingBear/machine/Ghost7/FlyingBear Ghost7 0.4 nozzle.json +++ b/resources/profiles/FlyingBear/machine/Ghost7/FlyingBear Ghost7 0.4 nozzle.json @@ -198,6 +198,6 @@ "0.4" ], "z_hop_types": [ - "Normal Lift" + "Auto Lift" ] } diff --git a/resources/profiles/InfiMech/machine/EX+APS/InfiMech EX+APS 0.4 nozzle.json b/resources/profiles/InfiMech/machine/EX+APS/InfiMech EX+APS 0.4 nozzle.json index a77186c2fd..9fb5343da9 100644 --- a/resources/profiles/InfiMech/machine/EX+APS/InfiMech EX+APS 0.4 nozzle.json +++ b/resources/profiles/InfiMech/machine/EX+APS/InfiMech EX+APS 0.4 nozzle.json @@ -198,6 +198,6 @@ "0.4" ], "z_hop_types": [ - "Normal Lift" + "Auto Lift" ] } diff --git a/resources/profiles/InfiMech/machine/EX/InfiMech EX 0.4 nozzle.json b/resources/profiles/InfiMech/machine/EX/InfiMech EX 0.4 nozzle.json index a018e3f8a3..ec9bbc6d00 100644 --- a/resources/profiles/InfiMech/machine/EX/InfiMech EX 0.4 nozzle.json +++ b/resources/profiles/InfiMech/machine/EX/InfiMech EX 0.4 nozzle.json @@ -198,6 +198,6 @@ "0.4" ], "z_hop_types": [ - "Normal Lift" + "Auto Lift" ] } diff --git a/resources/profiles/Prusa.json b/resources/profiles/Prusa.json index dc69bbe57a..e16e2d3be3 100644 --- a/resources/profiles/Prusa.json +++ b/resources/profiles/Prusa.json @@ -1,6 +1,6 @@ { "name": "Prusa", - "version": "02.04.00.07", + "version": "02.04.00.08", "force_update": "0", "description": "Prusa configurations", "machine_model_list": [ @@ -20,6 +20,10 @@ "name": "Prusa CORE One L HF", "sub_path": "machine/Prusa CORE One L HF.json" }, + { + "name": "Prusa CORE One MMU3", + "sub_path": "machine/Prusa CORE One MMU3.json" + }, { "name": "Prusa MINI", "sub_path": "machine/Prusa MINI.json" @@ -702,6 +706,26 @@ "name": "0.25mm Speed @MINIIS", "sub_path": "process/0.25mm Speed @MINIIS.json" }, + { + "name": "0.15mm BALANCED @COREONE0.4 + MMU3", + "sub_path": "process/0.15mm BALANCED @COREONE0.4 + MMU3.json" + }, + { + "name": "0.20mm BALANCED @COREONE0.4 + MMU3", + "sub_path": "process/0.20mm BALANCED @COREONE0.4 + MMU3.json" + }, + { + "name": "0.25mm BALANCED @COREONE0.4 + MMU3", + "sub_path": "process/0.25mm BALANCED @COREONE0.4 + MMU3.json" + }, + { + "name": "0.25mm SPEED @COREONE0.4 + MMU3", + "sub_path": "process/0.25mm SPEED @COREONE0.4 + MMU3.json" + }, + { + "name": "0.25mm STRUCTURAL @COREONE0.4 + MMU3", + "sub_path": "process/0.25mm STRUCTURAL @COREONE0.4 + MMU3.json" + }, { "name": "0.05mm DETAIL @MK4S 0.25", "sub_path": "process/0.05mm DETAIL @MK4S 0.25.json" @@ -1114,6 +1138,14 @@ "name": "0.05mm DETAIL @CORE One L 0.25", "sub_path": "process/0.05mm DETAIL @CORE One L 0.25.json" }, + { + "name": "0.07mm DETAIL @COREONE0.25 + MMU3", + "sub_path": "process/0.07mm DETAIL @COREONE0.25 + MMU3.json" + }, + { + "name": "0.12mm SPEED @COREONE0.25 + MMU3", + "sub_path": "process/0.12mm SPEED @COREONE0.25 + MMU3.json" + }, { "name": "0.12mm STRUCTURAL @CORE One 0.25", "sub_path": "process/0.12mm STRUCTURAL @CORE One 0.25.json" @@ -1134,6 +1166,10 @@ "name": "0.15mm STRUCTURAL @MK4S 0.25", "sub_path": "process/0.15mm STRUCTURAL @MK4S 0.25.json" }, + { + "name": "0.12mm STRUCTURAL @COREONE0.3 + MMU3", + "sub_path": "process/0.12mm STRUCTURAL @COREONE0.3 + MMU3.json" + }, { "name": "0.16mm SPEED @MK4S 0.3", "sub_path": "process/0.16mm SPEED @MK4S 0.3.json" @@ -1158,6 +1194,10 @@ "name": "0.10mm FAST DETAIL @CORE One L 0.4", "sub_path": "process/0.10mm FAST DETAIL @CORE One L 0.4.json" }, + { + "name": "0.15mm SPEED @COREONE0.4 + MMU3", + "sub_path": "process/0.15mm SPEED @COREONE0.4 + MMU3.json" + }, { "name": "0.15mm SPEED @CORE One HF 0.4", "sub_path": "process/0.15mm SPEED @CORE One HF 0.4.json" @@ -1166,6 +1206,22 @@ "name": "0.15mm SPEED @CORE One L HF 0.4", "sub_path": "process/0.15mm SPEED @CORE One L HF 0.4.json" }, + { + "name": "0.15mm STRUCTURAL @COREONE0.6 + MMU3", + "sub_path": "process/0.15mm STRUCTURAL @COREONE0.6 + MMU3.json" + }, + { + "name": "0.20mm SOLUBLE FULL @COREONE0.4 + MMU3", + "sub_path": "process/0.20mm SOLUBLE FULL @COREONE0.4 + MMU3.json" + }, + { + "name": "0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3", + "sub_path": "process/0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3.json" + }, + { + "name": "0.20mm SPEED @COREONE0.4 + MMU3", + "sub_path": "process/0.20mm SPEED @COREONE0.4 + MMU3.json" + }, { "name": "0.20mm SPEED @CORE One HF 0.5", "sub_path": "process/0.20mm SPEED @CORE One HF 0.5.json" @@ -1198,6 +1254,10 @@ "name": "0.25mm SPEED @MK4S HF0.5", "sub_path": "process/0.25mm SPEED @MK4S HF0.5.json" }, + { + "name": "0.20mm SPEED @COREONE0.6 + MMU3", + "sub_path": "process/0.20mm SPEED @COREONE0.6 + MMU3.json" + }, { "name": "0.20mm SPEED @CORE One HF 0.6", "sub_path": "process/0.20mm SPEED @CORE One HF 0.6.json" @@ -1222,6 +1282,10 @@ "name": "0.15mm STRUCTURAL @CORE One L 0.4", "sub_path": "process/0.15mm STRUCTURAL @CORE One L 0.4.json" }, + { + "name": "0.20mm STRUCTURAL @COREONE0.4 + MMU3", + "sub_path": "process/0.20mm STRUCTURAL @COREONE0.4 + MMU3.json" + }, { "name": "0.25mm STRUCTURAL @CORE One HF 0.4", "sub_path": "process/0.25mm STRUCTURAL @CORE One HF 0.4.json" @@ -1230,6 +1294,10 @@ "name": "0.25mm STRUCTURAL @CORE One L HF 0.4", "sub_path": "process/0.25mm STRUCTURAL @CORE One L HF 0.4.json" }, + { + "name": "0.25mm SPEED @COREONE0.6 + MMU3", + "sub_path": "process/0.25mm SPEED @COREONE0.6 + MMU3.json" + }, { "name": "0.25mm SPEED @CORE One HF 0.6", "sub_path": "process/0.25mm SPEED @CORE One HF 0.6.json" @@ -1262,6 +1330,10 @@ "name": "0.30mm STRUCTURAL @CORE One L HF 0.8", "sub_path": "process/0.30mm STRUCTURAL @CORE One L HF 0.8.json" }, + { + "name": "0.32mm SPEED @COREONE0.6 + MMU3", + "sub_path": "process/0.32mm SPEED @COREONE0.6 + MMU3.json" + }, { "name": "0.32mm SPEED @CORE One HF 0.6", "sub_path": "process/0.32mm SPEED @CORE One HF 0.6.json" @@ -1318,6 +1390,18 @@ "name": "0.55mm SPEED @CORE One L HF 0.8", "sub_path": "process/0.55mm SPEED @CORE One L HF 0.8.json" }, + { + "name": "0.05mm DETAIL @COREONE0.25 + MMU3", + "sub_path": "process/0.05mm DETAIL @COREONE0.25 + MMU3.json" + }, + { + "name": "0.12mm STRUCTURAL @COREONE0.25 + MMU3", + "sub_path": "process/0.12mm STRUCTURAL @COREONE0.25 + MMU3.json" + }, + { + "name": "0.15mm SPEED @COREONE0.25 + MMU3", + "sub_path": "process/0.15mm SPEED @COREONE0.25 + MMU3.json" + }, { "name": "0.15mm STRUCTURAL @CORE One 0.25", "sub_path": "process/0.15mm STRUCTURAL @CORE One 0.25.json" @@ -1338,6 +1422,10 @@ "name": "0.20mm SPEED @MK4S 0.3", "sub_path": "process/0.20mm SPEED @MK4S 0.3.json" }, + { + "name": "0.16mm STRUCTURAL @COREONE0.3 + MMU3", + "sub_path": "process/0.16mm STRUCTURAL @COREONE0.3 + MMU3.json" + }, { "name": "0.20mm STRUCTURAL @CORE One 0.3", "sub_path": "process/0.20mm STRUCTURAL @CORE One 0.3.json" @@ -1346,6 +1434,10 @@ "name": "0.20mm STRUCTURAL @CORE One L 0.3", "sub_path": "process/0.20mm STRUCTURAL @CORE One L 0.3.json" }, + { + "name": "0.10mm FAST DETAIL @COREONE0.4 + MMU3", + "sub_path": "process/0.10mm FAST DETAIL @COREONE0.4 + MMU3.json" + }, { "name": "0.25mm STRUCTURAL @CORE One 0.5", "sub_path": "process/0.25mm STRUCTURAL @CORE One 0.5.json" @@ -1370,6 +1462,30 @@ "name": "0.32mm SPEED @MK4S HF0.5", "sub_path": "process/0.32mm SPEED @MK4S HF0.5.json" }, + { + "name": "0.20mm STRUCTURAL @COREONE0.6 + MMU3", + "sub_path": "process/0.20mm STRUCTURAL @COREONE0.6 + MMU3.json" + }, + { + "name": "0.15mm STRUCTURAL @COREONE0.4 + MMU3", + "sub_path": "process/0.15mm STRUCTURAL @COREONE0.4 + MMU3.json" + }, + { + "name": "0.25mm STRUCTURAL @COREONE0.6 + MMU3", + "sub_path": "process/0.25mm STRUCTURAL @COREONE0.6 + MMU3.json" + }, + { + "name": "0.32mm STRUCTURAL @COREONE0.6 + MMU3", + "sub_path": "process/0.32mm STRUCTURAL @COREONE0.6 + MMU3.json" + }, + { + "name": "0.15mm STRUCTURAL @COREONE0.25 + MMU3", + "sub_path": "process/0.15mm STRUCTURAL @COREONE0.25 + MMU3.json" + }, + { + "name": "0.16mm SPEED @COREONE0.3 + MMU3", + "sub_path": "process/0.16mm SPEED @COREONE0.3 + MMU3.json" + }, { "name": "0.20mm SPEED @CORE One 0.3", "sub_path": "process/0.20mm SPEED @CORE One 0.3.json" @@ -1378,6 +1494,10 @@ "name": "0.20mm SPEED @CORE One L 0.3", "sub_path": "process/0.20mm SPEED @CORE One L 0.3.json" }, + { + "name": "0.20mm STRUCTURAL @COREONE0.3 + MMU3", + "sub_path": "process/0.20mm STRUCTURAL @COREONE0.3 + MMU3.json" + }, { "name": "0.32mm STRUCTURAL @CORE One HF 0.5", "sub_path": "process/0.32mm STRUCTURAL @CORE One HF 0.5.json" @@ -1393,6 +1513,10 @@ { "name": "0.32mm SPEED @CORE One L HF 0.5", "sub_path": "process/0.32mm SPEED @CORE One L HF 0.5.json" + }, + { + "name": "0.20mm SPEED @COREONE0.3 + MMU3", + "sub_path": "process/0.20mm SPEED @COREONE0.3 + MMU3.json" } ], "filament_list": [ @@ -2032,6 +2156,10 @@ "name": "Generic ABS @Prusa CORE One HF 0.8", "sub_path": "filament/Generic ABS @Prusa CORE One HF 0.8.json" }, + { + "name": "Generic ABS @Prusa CORE One MMU3", + "sub_path": "filament/Generic ABS @Prusa CORE One MMU3.json" + }, { "name": "Generic ABS @Prusa MK4S 0.6", "sub_path": "filament/Generic ABS @Prusa MK4S 0.6.json" @@ -2112,6 +2240,10 @@ "name": "Generic ASA @Prusa CORE One HF 0.8", "sub_path": "filament/Generic ASA @Prusa CORE One HF 0.8.json" }, + { + "name": "Generic ASA @Prusa CORE One MMU3", + "sub_path": "filament/Generic ASA @Prusa CORE One MMU3.json" + }, { "name": "Generic ASA @Prusa MK4S 0.6", "sub_path": "filament/Generic ASA @Prusa MK4S 0.6.json" @@ -2160,6 +2292,10 @@ "name": "Prusament ASA @CORE One HF 0.8", "sub_path": "filament/Prusament ASA @CORE One HF 0.8.json" }, + { + "name": "Prusament ASA @CORE One MMU3", + "sub_path": "filament/Prusament ASA @CORE One MMU3.json" + }, { "name": "Prusament ASA @MK4S 0.6", "sub_path": "filament/Prusament ASA @MK4S 0.6.json" @@ -2192,6 +2328,10 @@ "name": "Generic TPU @Prusa CORE One 0.8", "sub_path": "filament/Generic TPU @Prusa CORE One 0.8.json" }, + { + "name": "Generic TPU @Prusa CORE One MMU3", + "sub_path": "filament/Generic TPU @Prusa CORE One MMU3.json" + }, { "name": "Generic PA @Prusa", "sub_path": "filament/Generic PA @Prusa.json" @@ -2240,6 +2380,10 @@ "name": "Prusament PA-CF @CORE One 0.8", "sub_path": "filament/Prusament PA-CF @CORE One 0.8.json" }, + { + "name": "Prusament PA-CF @CORE One MMU3", + "sub_path": "filament/Prusament PA-CF @CORE One MMU3.json" + }, { "name": "Prusament PA11 Carbon Fiber @MK4S 0.6", "sub_path": "filament/Prusament PA11 Carbon Fiber @MK4S 0.6.json" @@ -2300,6 +2444,10 @@ "name": "Prusament PC Blend @CORE One HF 0.8", "sub_path": "filament/Prusament PC Blend @CORE One HF 0.8.json" }, + { + "name": "Prusament PC Blend @CORE One MMU3", + "sub_path": "filament/Prusament PC Blend @CORE One MMU3.json" + }, { "name": "Prusament PC Blend @MK4S 0.6", "sub_path": "filament/Prusament PC Blend @MK4S 0.6.json" @@ -2332,6 +2480,10 @@ "name": "Prusament PC-CF @CORE One 0.8", "sub_path": "filament/Prusament PC-CF @CORE One 0.8.json" }, + { + "name": "Prusament PC-CF @CORE One MMU3", + "sub_path": "filament/Prusament PC-CF @CORE One MMU3.json" + }, { "name": "Prusament PC Blend Carbon Fiber @MK4S 0.6", "sub_path": "filament/Prusament PC Blend Carbon Fiber @MK4S 0.6.json" @@ -2380,6 +2532,10 @@ "name": "Generic PETG @Prusa CORE One HF 0.8", "sub_path": "filament/Generic PETG @Prusa CORE One HF 0.8.json" }, + { + "name": "Generic PETG @Prusa CORE One MMU3", + "sub_path": "filament/Generic PETG @Prusa CORE One MMU3.json" + }, { "name": "Generic PETG @Prusa MK4S 0.6", "sub_path": "filament/Generic PETG @Prusa MK4S 0.6.json" @@ -2460,6 +2616,10 @@ "name": "Prusament PETG @CORE One HF 0.8", "sub_path": "filament/Prusament PETG @CORE One HF 0.8.json" }, + { + "name": "Prusament PETG @CORE One MMU3", + "sub_path": "filament/Prusament PETG @CORE One MMU3.json" + }, { "name": "Prusament PETG @MK4S 0.6", "sub_path": "filament/Prusament PETG @MK4S 0.6.json" @@ -2516,6 +2676,10 @@ "name": "Generic PLA @Prusa CORE One HF 0.8", "sub_path": "filament/Generic PLA @Prusa CORE One HF 0.8.json" }, + { + "name": "Generic PLA @Prusa CORE One MMU3", + "sub_path": "filament/Generic PLA @Prusa CORE One MMU3.json" + }, { "name": "Generic PLA @Prusa MK4S 0.6", "sub_path": "filament/Generic PLA @Prusa MK4S 0.6.json" @@ -2584,6 +2748,10 @@ "name": "Generic PLA Silk @Prusa CORE One 0.8", "sub_path": "filament/Generic PLA Silk @Prusa CORE One 0.8.json" }, + { + "name": "Generic PLA Silk @Prusa CORE One MMU3", + "sub_path": "filament/Generic PLA Silk @Prusa CORE One MMU3.json" + }, { "name": "Generic PLA-CF @Prusa", "sub_path": "filament/Generic PLA-CF @Prusa.json" @@ -2628,6 +2796,10 @@ "name": "Prusament PLA @CORE One HF 0.8", "sub_path": "filament/Prusament PLA @CORE One HF 0.8.json" }, + { + "name": "Prusament PLA @CORE One MMU3", + "sub_path": "filament/Prusament PLA @CORE One MMU3.json" + }, { "name": "Prusament PLA @MK4S 0.6", "sub_path": "filament/Prusament PLA @MK4S 0.6.json" @@ -2668,6 +2840,10 @@ "name": "Prusament rPLA @CORE One 0.8", "sub_path": "filament/Prusament rPLA @CORE One 0.8.json" }, + { + "name": "Prusament rPLA @CORE One MMU3", + "sub_path": "filament/Prusament rPLA @CORE One MMU3.json" + }, { "name": "Prusament rPLA @MK4S 0.6", "sub_path": "filament/Prusament rPLA @MK4S 0.6.json" @@ -2740,6 +2916,10 @@ "name": "Prusament PVB @CORE One 0.8", "sub_path": "filament/Prusament PVB @CORE One 0.8.json" }, + { + "name": "Prusament PVB @CORE One MMU3", + "sub_path": "filament/Prusament PVB @CORE One MMU3.json" + }, { "name": "Prusament PVB @MK4S 0.6", "sub_path": "filament/Prusament PVB @MK4S 0.6.json" @@ -3018,6 +3198,10 @@ "name": "fdm_machine_common_xl_5t", "sub_path": "machine/fdm_machine_common_xl_5t.json" }, + { + "name": "Prusa CORE One MMU3 0.4 nozzle", + "sub_path": "machine/Prusa CORE One MMU3 0.4 nozzle.json" + }, { "name": "Prusa CORE One 0.5 nozzle", "sub_path": "machine/Prusa CORE One 0.5 nozzle.json" @@ -3090,6 +3274,18 @@ "name": "Prusa XL 5T 0.8 nozzle", "sub_path": "machine/Prusa XL 5T 0.8 nozzle.json" }, + { + "name": "Prusa CORE One MMU3 0.25 nozzle", + "sub_path": "machine/Prusa CORE One MMU3 0.25 nozzle.json" + }, + { + "name": "Prusa CORE One MMU3 0.3 nozzle", + "sub_path": "machine/Prusa CORE One MMU3 0.3 nozzle.json" + }, + { + "name": "Prusa CORE One MMU3 0.6 nozzle", + "sub_path": "machine/Prusa CORE One MMU3 0.6 nozzle.json" + }, { "name": "Prusa MK4S HF0.5 nozzle", "sub_path": "machine/Prusa MK4S HF0.5 nozzle.json" diff --git a/resources/profiles/Prusa/Prusa CORE One MMU3_cover.png b/resources/profiles/Prusa/Prusa CORE One MMU3_cover.png new file mode 100644 index 0000000000..be841d654b Binary files /dev/null and b/resources/profiles/Prusa/Prusa CORE One MMU3_cover.png differ diff --git a/resources/profiles/Prusa/filament/Generic ABS @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic ABS @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..b909758269 --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic ABS @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic ABS @Prusa CORE One MMU3", + "inherits": "Generic ABS @Prusa CORE One", + "from": "system", + "setting_id": "TM5fcvhbo1lgzsU6", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n\nM142 S40 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Generic ASA @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic ASA @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..4461d30750 --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic ASA @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic ASA @Prusa CORE One MMU3", + "inherits": "Generic ASA @Prusa CORE One", + "from": "system", + "setting_id": "0uAHoCupgHmVYVEx", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n\nM142 S40 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Generic PETG @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic PETG @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..c026e6d770 --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic PETG @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic PETG @Prusa CORE One MMU3", + "inherits": "Generic PETG @Prusa CORE One", + "from": "system", + "setting_id": "JutuB6ct5A1l2Biz", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Generic PLA @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic PLA @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..32498fb8a9 --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic PLA @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic PLA @Prusa CORE One MMU3", + "inherits": "Generic PLA @Prusa CORE One", + "from": "system", + "setting_id": "haNaA1KFlYeqtfIg", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.5" + ], + "filament_cooling_initial_speed": [ + "10" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "29" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.03{elsif nozzle_diameter[filament_extruder_id]==0.5}0.022{elsif nozzle_diameter[filament_extruder_id]==0.6}0.019{elsif nozzle_diameter[filament_extruder_id]==0.8}0.013{elsif nozzle_diameter[filament_extruder_id]==0.25}0.1{elsif nozzle_diameter[filament_extruder_id]==0.3}0.07{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "40" + ], + "filament_unloading_speed_start": [ + "30" + ], + "idle_temperature": [ + "170" + ] +} diff --git a/resources/profiles/Prusa/filament/Generic PLA Silk @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic PLA Silk @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..d45bd3cd90 --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic PLA Silk @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic PLA Silk @Prusa CORE One MMU3", + "inherits": "Generic PLA Silk @Prusa CORE One", + "from": "system", + "setting_id": "MMarDGtK4f7u78gA", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.5" + ], + "filament_cooling_initial_speed": [ + "10" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "29" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[0]==0.4}0.03{elsif nozzle_diameter[0]==0.5}0.022{elsif nozzle_diameter[0]==0.6}0.018{elsif nozzle_diameter[0]==0.8}0.012{elsif nozzle_diameter[0]==0.25}0.12{elsif nozzle_diameter[0]==0.3}0.075{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "40" + ], + "filament_unloading_speed_start": [ + "30" + ], + "idle_temperature": [ + "170" + ] +} diff --git a/resources/profiles/Prusa/filament/Generic TPU @Prusa CORE One MMU3.json b/resources/profiles/Prusa/filament/Generic TPU @Prusa CORE One MMU3.json new file mode 100644 index 0000000000..7aa75a058f --- /dev/null +++ b/resources/profiles/Prusa/filament/Generic TPU @Prusa CORE One MMU3.json @@ -0,0 +1,53 @@ +{ + "type": "filament", + "name": "Generic TPU @Prusa CORE One MMU3", + "inherits": "Generic TPU @Prusa CORE One", + "from": "system", + "setting_id": "pYEUfg7uqu2LGw2n", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.4" + ], + "filament_cooling_initial_speed": [ + "2.2" + ], + "filament_cooling_moves": [ + "4" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "28" + ], + "filament_loading_speed_start": [ + "3" + ], + "filament_ramming_parameters": [ + "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" + ], + "filament_stamping_distance": [ + "0" + ], + "filament_stamping_loading_speed": [ + "0" + ], + "filament_start_gcode": [ + "M900 K0 ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "90" + ], + "filament_unloading_speed_start": [ + "100" + ], + "idle_temperature": [ + "150" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament ASA @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament ASA @CORE One MMU3.json new file mode 100644 index 0000000000..6f415ba213 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament ASA @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament ASA @CORE One MMU3", + "inherits": "Prusament ASA @CORE One", + "from": "system", + "setting_id": "pESQ8XminojyIGyw", + "filament_id": "OFmFwUWM", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.02{elsif nozzle_diameter[filament_extruder_id]==0.5}0.018{elsif nozzle_diameter[filament_extruder_id]==0.6}0.012{elsif nozzle_diameter[filament_extruder_id]==0.8}0.01{elsif nozzle_diameter[filament_extruder_id]==0.25}0.09{elsif nozzle_diameter[filament_extruder_id]==0.3}0.065{else}0{endif} ; Filament gcode\n\nM142 S40 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PA-CF @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PA-CF @CORE One MMU3.json new file mode 100644 index 0000000000..cfdc1c5412 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PA-CF @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament PA-CF @CORE One MMU3", + "inherits": "Prusament PA-CF @CORE One", + "from": "system", + "setting_id": "DsvJLtFStnmOCm1m", + "filament_id": "OF54SvEe", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n\nM142 S45 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PC Blend @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PC Blend @CORE One MMU3.json new file mode 100644 index 0000000000..ac1e31b8a5 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PC Blend @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament PC Blend @CORE One MMU3", + "inherits": "Prusament PC Blend @CORE One", + "from": "system", + "setting_id": "VjMN8MwtU3BG5cA9", + "filament_id": "OFvDbkFq", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n\nM142 S45 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "150" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PC-CF @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PC-CF @CORE One MMU3.json new file mode 100644 index 0000000000..fc73ddf3b4 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PC-CF @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament PC-CF @CORE One MMU3", + "inherits": "Prusament PC-CF @CORE One", + "from": "system", + "setting_id": "hTcuccoINAUJ4PfR", + "filament_id": "OFjz0a3m", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.05{elsif nozzle_diameter[filament_extruder_id]==0.5}0.035{elsif nozzle_diameter[filament_extruder_id]==0.6}0.025{elsif nozzle_diameter[filament_extruder_id]==0.8}0.016{elsif nozzle_diameter[filament_extruder_id]==0.25}0.14{elsif nozzle_diameter[filament_extruder_id]==0.3}0.09{else}0{endif} ; Filament gcode\n\nM142 S45 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "150" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PETG @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PETG @CORE One MMU3.json new file mode 100644 index 0000000000..70d19cc0d1 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PETG @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament PETG @CORE One MMU3", + "inherits": "Prusament PETG @CORE One", + "from": "system", + "setting_id": "ypQNV38FrfRu9QSd", + "filament_id": "OFkR8E2c", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "2.5" + ], + "filament_cooling_initial_speed": [ + "5" + ], + "filament_cooling_moves": [ + "3" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "26.5" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.053{elsif nozzle_diameter[filament_extruder_id]==0.5}0.042{elsif nozzle_diameter[filament_extruder_id]==0.6}0.032{elsif nozzle_diameter[filament_extruder_id]==0.8}0.018{elsif nozzle_diameter[filament_extruder_id]==0.25}0.18{elsif nozzle_diameter[filament_extruder_id]==0.3}0.1{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "60" + ], + "filament_unloading_speed_start": [ + "60" + ], + "idle_temperature": [ + "130" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PLA @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PLA @CORE One MMU3.json new file mode 100644 index 0000000000..df0112ed8f --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PLA @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament PLA @CORE One MMU3", + "inherits": "Prusament PLA @CORE One", + "from": "system", + "setting_id": "HSqoQhcjRqpny2g2", + "filament_id": "OFnO4wnf", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.5" + ], + "filament_cooling_initial_speed": [ + "10" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "29" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "40" + ], + "filament_unloading_speed_start": [ + "30" + ], + "idle_temperature": [ + "170" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament PVB @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament PVB @CORE One MMU3.json new file mode 100644 index 0000000000..992b9ebd58 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament PVB @CORE One MMU3.json @@ -0,0 +1,57 @@ +{ + "type": "filament", + "name": "Prusament PVB @CORE One MMU3", + "inherits": "Prusament PVB @CORE One", + "from": "system", + "setting_id": "IXUO4uCgUY1p5jIo", + "filament_id": "OFh7mvPO", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.5" + ], + "filament_cooling_initial_speed": [ + "10" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_minimal_purge_on_wipe_tower": [ + "15" + ], + "filament_ramming_parameters": [ + "250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "29" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "40" + ], + "filament_unloading_speed_start": [ + "30" + ], + "idle_temperature": [ + "170" + ] +} diff --git a/resources/profiles/Prusa/filament/Prusament rPLA @CORE One MMU3.json b/resources/profiles/Prusa/filament/Prusament rPLA @CORE One MMU3.json new file mode 100644 index 0000000000..282449b194 --- /dev/null +++ b/resources/profiles/Prusa/filament/Prusament rPLA @CORE One MMU3.json @@ -0,0 +1,54 @@ +{ + "type": "filament", + "name": "Prusament rPLA @CORE One MMU3", + "inherits": "Prusament rPLA @CORE One", + "from": "system", + "setting_id": "n0gSFPBaa7PQ1Y1x", + "filament_id": "OFWRITFw", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle", + "Prusa CORE One MMU3 0.25 nozzle", + "Prusa CORE One MMU3 0.3 nozzle", + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filament_cooling_final_speed": [ + "3.5" + ], + "filament_cooling_initial_speed": [ + "10" + ], + "filament_cooling_moves": [ + "2" + ], + "filament_extruder_variant": [ + "Direct Drive Standard" + ], + "filament_loading_speed": [ + "10" + ], + "filament_loading_speed_start": [ + "50" + ], + "filament_ramming_parameters": [ + "250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419" + ], + "filament_stamping_distance": [ + "45" + ], + "filament_stamping_loading_speed": [ + "29" + ], + "filament_start_gcode": [ + "M572 S{if nozzle_diameter[filament_extruder_id]==0.4}0.036{elsif nozzle_diameter[filament_extruder_id]==0.5}0.025{elsif nozzle_diameter[filament_extruder_id]==0.6}0.022{elsif nozzle_diameter[filament_extruder_id]==0.8}0.014{elsif nozzle_diameter[filament_extruder_id]==0.25}0.12{elsif nozzle_diameter[filament_extruder_id]==0.3}0.08{else}0{endif} ; Filament gcode\n\nM142 S36 ; set heatbreak target temp" + ], + "filament_unloading_speed": [ + "40" + ], + "filament_unloading_speed_start": [ + "30" + ], + "idle_temperature": [ + "170" + ] +} diff --git a/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.25 nozzle.json b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.25 nozzle.json new file mode 100644 index 0000000000..2a862a9a73 --- /dev/null +++ b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.25 nozzle.json @@ -0,0 +1,18 @@ +{ + "type": "machine", + "name": "Prusa CORE One MMU3 0.25 nozzle", + "inherits": "Prusa CORE One MMU3 0.4 nozzle", + "from": "system", + "setting_id": "8VtAsgGqtUUBmaxH", + "instantiation": "true", + "default_print_profile": "0.12mm SPEED @COREONE0.25 + MMU3", + "max_layer_height": "0.15", + "min_layer_height": "0.05", + "nozzle_diameter": [ + "0.25" + ], + "printer_model": "Prusa CORE One MMU3", + "printer_variant": "0.25", + "retraction_length": "0.8", + "z_hop": "0.15" +} diff --git a/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.3 nozzle.json b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.3 nozzle.json new file mode 100644 index 0000000000..3396a41a8d --- /dev/null +++ b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.3 nozzle.json @@ -0,0 +1,18 @@ +{ + "type": "machine", + "name": "Prusa CORE One MMU3 0.3 nozzle", + "inherits": "Prusa CORE One MMU3 0.4 nozzle", + "from": "system", + "setting_id": "EiPXZjRoFcw95gWA", + "instantiation": "true", + "default_print_profile": "0.16mm STRUCTURAL @COREONE0.3 + MMU3", + "max_layer_height": "0.22", + "min_layer_height": "0.05", + "nozzle_diameter": [ + "0.3" + ], + "printer_model": "Prusa CORE One MMU3", + "printer_variant": "0.3", + "retraction_length": "0.7", + "z_hop": "0.15" +} diff --git a/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.4 nozzle.json b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.4 nozzle.json new file mode 100644 index 0000000000..83a6a95ca5 --- /dev/null +++ b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.4 nozzle.json @@ -0,0 +1,40 @@ +{ + "type": "machine", + "name": "Prusa CORE One MMU3 0.4 nozzle", + "inherits": "Prusa CORE One 0.4 nozzle", + "from": "system", + "setting_id": "7NzvROXEkMpOMzJ5", + "instantiation": "true", + "before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\nG92 E0.0\n;[layer_z]", + "change_filament_gcode": "", + "cooling_tube_length": "20", + "cooling_tube_retraction": "45", + "default_filament_profile": "Prusament PLA @CORE One MMU3", + "default_print_profile": "0.20mm SPEED @COREONE0.4 + MMU3", + "extra_loading_move": "-52", + "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\n", + "machine_end_gcode": "{if layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+1, max_print_height)} F720 ; Move print head up{endif}\nM140 S0 ; turn off heatbed\nM141 S0 ; disable chamber control\nM107 ; turn off fan\nG1 X242 Y211 F10200 ; park\n{if layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+50, max_print_height)} F720 ; Move bed down{endif}\nM702 ; unload the current filament\nM104 S0 ; turn off temperature\nG4 ; wait\nM572 S0 ; reset PA\nM84 X Y E ; disable motors\n; max_layer_z = [max_layer_z]", + "machine_load_filament_time": "10.9", + "machine_max_speed_z": [ + "12", + "40" + ], + "machine_start_gcode": "M17 ; enable steppers\nM862.1 P[nozzle_diameter] A{(printer_notes=~/.*ABRASIVE_NOZZLE.*/ ? 1 : 0)} F{(printer_notes=~/.*HF_NOZZLE.*/ ? 1 : 0)} ; nozzle check\nM862.3 P \"COREONE\" ; printer model check\nM862.5 P2 ; g-code level check\nM862.6 P \"Input shaper\" ; FW feature check\nM862.6 P \"MMU3\" ; FW feature check\nM115 U6.4.0+11974\n\n; setup MMU\nM708 A0x0b X5 ; extra load distance\nM708 A0x0d X140 ; unload feedrate\nM708 A0x11 X140 ; load feedrate\nM708 A0x14 X50 ; slow feedrate\nM708 A0x1e X12 ; Pulley current to ~200mA\nM708 A0x12 X90 ; selector feedrate mm/s\nM708 A0x13 X400 ; idler feedrate deg/s\nM708 A0x0f X400 ; selector acceleration mm/s2\n\nM555 X{(min(print_bed_max[0], first_layer_print_min[0] + 32) - 32)} Y{(max(0, first_layer_print_min[1]) - 4)} W{((min(print_bed_max[0], max(first_layer_print_min[0] + 32, first_layer_print_max[0])))) - ((min(print_bed_max[0], first_layer_print_min[0] + 32) - 32))} H{((first_layer_print_max[1])) - ((max(0, first_layer_print_min[1]) - 4))} ; define print area\n\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\n\n{if chamber_temperature[initial_tool] > 35} \n; we need to preheat the chamber\nM140 S115 ; set bed temp for chamber heating\n{else} \n; just set the selected bed temp otherwise\nM140 S[first_layer_bed_temperature] ; set bed temp\n{endif}\nM109 R{((filament_notes[initial_tool]=~/.*HT_MBL10.*/) ? (first_layer_temperature[initial_tool] - 10) : (filament_type[initial_tool] == \"PC\" or filament_type[initial_tool] == \"PA\") ? (first_layer_temperature[initial_tool] - 25) : (filament_type[initial_tool] == \"FLEX\") ? 210 : (filament_type[initial_tool]=~/.*PET.*/) ? 175 : 170)} ; wait for temp\n\nM84 E ; turn off E motor\n\nG28 ; home all without mesh bed level\n\n; -- Chamber Temperature Control --\n{if chamber_temperature[initial_tool] > 35} ; if we need to heat the chamber\n; min chamber temp section\nM104 T{initial_tool} S{if idle_temperature[initial_tool] == 0}100{else}{idle_temperature[initial_tool]}{endif} ; set idle temp\nG1 Z10 F720 ; set bed position\nG1 X242 Y-9 F4800 ; set print head position\nM191 S{chamber_temperature[initial_tool]} ; wait for minimal chamber temp\nM141 S0 ; set nominal chamber temp\nM107\nM140 S[first_layer_bed_temperature] ; set bed temp\n\n{else}\nM141 S{if chamber_temperature[initial_tool] == 0}20{else}{chamber_temperature[initial_tool]}{endif} ; set nominal chamber temp\n{endif}\n\n{if first_layer_bed_temperature[initial_tool]<=60}M106 S70{endif}\nG0 Z40 F10000\nM104 T{initial_tool} S{if idle_temperature[initial_tool] == 0}100{else}{idle_temperature[initial_tool]}{endif}\nM190 R[first_layer_bed_temperature] ; wait for bed temp\nM107\n\nG29 G ; absorb heat\n\nM109 R{((filament_notes[0]=~/.*HT_MBL10.*/) ? (first_layer_temperature[0] - 10) : (filament_type[0] == \"PC\" or filament_type[0] == \"PA\") ? (first_layer_temperature[0] - 25) : (filament_type[0] == \"FLEX\") ? 210 : 170)} ; wait for MBL temp\n\nM302 S160 ; lower cold extrusion limit to 160C\n\n{if filament_type[initial_tool]==\"FLEX\"}\nG1 E-4 F2400 ; retraction\n{else}\nG1 E-2 F2400 ; retraction\n{endif}\n\nM84 E ; turn off E motor\n\n; --- Mesh Bed Leveling (MBL) ---\n\nG29 P9 X208 Y-2.5 W32 H4 ; limited MBL\nM84 E ; turn off E motor\n\nG29 P1 ; invalidate MBL and probe print area\nG29 P1 X150 Y0 W100 H20 C ; probe near purge area\nG29 P3.2 ; MBL interpolation\nG29 P3.13 ; MBL extrapolation outside probe area\nG29 A ; activate MBL\n\n; prepare for purge\nM104 S{first_layer_temperature[initial_tool]}\nG0 X249 Y-2.5 Z15 F4800 ; move away and ready for the purge\nM109 S{first_layer_temperature[initial_tool]}\n\nG92 E0\nM569 S0 E ; set spreadcycle mode for extruder\n\nM591 S0 ; disable stuck detection\n\nT[initial_tool]\nG1 E{parking_pos_retraction + extra_loading_move - 15} F1000 ; load to the nozzle\n\n;\n; Extrude purge line\n;\nG92 E0 ; reset extruder position\nG1 E{(filament_type[initial_tool] == \"FLEX\" ? 4 : 2)} F2400 ; deretraction after the initial one before nozzle cleaning\nG0 E5 X235 Z0.2 F500 ; purge\nG0 X145 E36 F500 ; purge\nG0 X135 E4 F500 ; purge\nG0 X125 E4 F650 ; purge\nG0 X122 Z0.05 F8000 ; wipe, move close to the bed\nG0 X119 Z0.2 F8000 ; wipe, move quickly away from the bed\n\nM591 R ; restore stuck detection\n\nG92 E0\nM221 S100 ; set flow to 100%", + "machine_unload_filament_time": "8.9", + "nozzle_diameter": [ + "0.4" + ], + "nozzle_height": "4", + "parking_pos_retraction": "84", + "printer_extruder_id": [ + "1" + ], + "printer_extruder_variant": [ + "Direct Drive Standard" + ], + "printer_model": "Prusa CORE One MMU3", + "printer_notes": "Don't remove the following keywords! These keywords are used in the \"compatible printer\" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_MODEL_COREONEMMU3\nPRINTER_MODEL_MK4\nCOREMMU\nRAMMING_EXTRA\nPG\nNO_TEMPLATES\nThe MK4 marker enables WipeTower2 cold ramming and filament monitoring control for the CORE One MMU3.", + "printer_variant": "0.4", + "single_extruder_multi_material": "1", + "wipe_tower_type": "type2" +} diff --git a/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.6 nozzle.json b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.6 nozzle.json new file mode 100644 index 0000000000..11de2e2eac --- /dev/null +++ b/resources/profiles/Prusa/machine/Prusa CORE One MMU3 0.6 nozzle.json @@ -0,0 +1,19 @@ +{ + "type": "machine", + "name": "Prusa CORE One MMU3 0.6 nozzle", + "inherits": "Prusa CORE One MMU3 0.4 nozzle", + "from": "system", + "setting_id": "rFDxiYXLDZVYHqe8", + "instantiation": "true", + "default_filament_profile": "Prusament PLA @CORE One MMU3", + "default_print_profile": "0.20mm SPEED @COREONE0.6 + MMU3", + "max_layer_height": "0.40", + "min_layer_height": "0.15", + "nozzle_diameter": [ + "0.6" + ], + "printer_model": "Prusa CORE One MMU3", + "printer_variant": "0.6", + "retraction_length": "0.7", + "wipe": "0" +} diff --git a/resources/profiles/Prusa/machine/Prusa CORE One MMU3.json b/resources/profiles/Prusa/machine/Prusa CORE One MMU3.json new file mode 100644 index 0000000000..a0e766e818 --- /dev/null +++ b/resources/profiles/Prusa/machine/Prusa CORE One MMU3.json @@ -0,0 +1,12 @@ +{ + "type": "machine_model", + "name": "Prusa CORE One MMU3", + "bed_model": "coreone_bed.stl", + "bed_texture": "coreone.svg", + "default_materials": "Generic ABS @Prusa CORE One MMU3;Generic ASA @Prusa CORE One MMU3;Generic PETG @Prusa CORE One MMU3;Generic PLA @Prusa CORE One MMU3;Generic PLA Silk @Prusa CORE One MMU3;Generic TPU @Prusa CORE One MMU3", + "family": "Prusa", + "hotend_model": "", + "machine_tech": "FFF", + "model_id": "Prusa_CORE_One_MMU3", + "nozzle_diameter": "0.25;0.3;0.4;0.6" +} diff --git a/resources/profiles/Prusa/process/0.05mm DETAIL @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.05mm DETAIL @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..123a51ea99 --- /dev/null +++ b/resources/profiles/Prusa/process/0.05mm DETAIL @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.05mm DETAIL @COREONE0.25 + MMU3", + "inherits": "0.05mm DETAIL @CORE One 0.25", + "from": "system", + "setting_id": "GZ9p7JXzPU3Widdg", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.07mm DETAIL @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.07mm DETAIL @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..97b0973c5c --- /dev/null +++ b/resources/profiles/Prusa/process/0.07mm DETAIL @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.07mm DETAIL @COREONE0.25 + MMU3", + "inherits": "0.07mm DETAIL @CORE One 0.25", + "from": "system", + "setting_id": "9ffM9tc7XMSNHnGB", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.10mm FAST DETAIL @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.10mm FAST DETAIL @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..2e7b0c7edb --- /dev/null +++ b/resources/profiles/Prusa/process/0.10mm FAST DETAIL @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.10mm FAST DETAIL @COREONE0.4 + MMU3", + "inherits": "0.10mm FAST DETAIL @CORE One 0.4", + "from": "system", + "setting_id": "5l28K1bE8lfuaXF1", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.12mm SPEED @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.12mm SPEED @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..e2d4ba94ca --- /dev/null +++ b/resources/profiles/Prusa/process/0.12mm SPEED @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.12mm SPEED @COREONE0.25 + MMU3", + "inherits": "0.12mm SPEED @CORE One 0.25", + "from": "system", + "setting_id": "YjbMEvZ8ALzK2qHf", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..551f6248f5 --- /dev/null +++ b/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.12mm STRUCTURAL @COREONE0.25 + MMU3", + "inherits": "0.12mm STRUCTURAL @CORE One 0.25", + "from": "system", + "setting_id": "g8Bp93DCAwTo5G0s", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.3 + MMU3.json b/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.3 + MMU3.json new file mode 100644 index 0000000000..25efb09730 --- /dev/null +++ b/resources/profiles/Prusa/process/0.12mm STRUCTURAL @COREONE0.3 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.12mm STRUCTURAL @COREONE0.3 + MMU3", + "inherits": "0.12mm STRUCTURAL @CORE One 0.3", + "from": "system", + "setting_id": "XmhM0qI0mXLbBMwv", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.3 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.15mm BALANCED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.15mm BALANCED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..dd86f29945 --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm BALANCED @COREONE0.4 + MMU3.json @@ -0,0 +1,70 @@ +{ + "type": "process", + "name": "0.15mm BALANCED @COREONE0.4 + MMU3", + "inherits": "0.20mm Standard @MK4", + "from": "system", + "setting_id": "gU24xfFrPejGfV1D", + "instantiation": "true", + "bridge_acceleration": "1500", + "brim_type": "no_brim", + "default_acceleration": "3000", + "elefant_foot_compensation": "0.2", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "initial_layer_acceleration": "500", + "initial_layer_infill_speed": "100", + "initial_layer_line_width": "0.5", + "inner_wall_line_width": "0.45", + "internal_solid_infill_line_width": "0.45", + "line_width": "0.45", + "outer_wall_line_width": "0.45", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "raft_first_layer_expansion": "3", + "resolution": "0", + "seam_gap": "15%", + "sparse_infill_line_width": "0.45", + "sparse_infill_pattern": "grid", + "support_base_pattern_spacing": "2", + "support_bottom_interface_spacing": "0.2", + "support_interface_bottom_layers": "0", + "support_interface_spacing": "0.2", + "support_interface_top_layers": "3", + "support_line_width": "0.4", + "support_speed": "120", + "top_surface_acceleration": "2000", + "top_surface_line_width": "0.42", + "travel_acceleration": "7000", + "travel_speed": "350", + "wall_loops": "2", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "bridge_speed": "45", + "bottom_shell_layers": "4", + "inner_wall_acceleration": "3000", + "inner_wall_speed": "150", + "internal_bridge_speed": "45", + "internal_solid_infill_acceleration": "6000", + "internal_solid_infill_speed": "150", + "layer_height": "0.15", + "outer_wall_acceleration": "2500", + "overhang_2_4_speed": "25", + "overhang_3_4_speed": "50", + "overhang_4_4_speed": "80%", + "small_perimeter_speed": "170", + "sparse_infill_acceleration": "5000", + "sparse_infill_speed": "150", + "support_bottom_z_distance": "0.17", + "support_interface_speed": "50", + "support_threshold_angle": "35", + "support_top_z_distance": "0.17", + "top_shell_layers": "6", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ] +} diff --git a/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..287579505b --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.15mm SPEED @COREONE0.25 + MMU3", + "inherits": "0.15mm SPEED @CORE One 0.25", + "from": "system", + "setting_id": "mYSeGZQ0MykDNtXP", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..d9b2e4b903 --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm SPEED @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.15mm SPEED @COREONE0.4 + MMU3", + "inherits": "0.15mm SPEED @CORE One 0.4", + "from": "system", + "setting_id": "CdehEFlvnacxhU2H", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.25 + MMU3.json b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.25 + MMU3.json new file mode 100644 index 0000000000..b9082c332c --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.25 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.15mm STRUCTURAL @COREONE0.25 + MMU3", + "inherits": "0.15mm STRUCTURAL @CORE One 0.25", + "from": "system", + "setting_id": "sUWaBUUy033BXEzk", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.25 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..dd1e847ee3 --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.15mm STRUCTURAL @COREONE0.4 + MMU3", + "inherits": "0.15mm STRUCTURAL @CORE One 0.4", + "from": "system", + "setting_id": "kM9rtB6iLqcDNZZU", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..6e7adf72c0 --- /dev/null +++ b/resources/profiles/Prusa/process/0.15mm STRUCTURAL @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.15mm STRUCTURAL @COREONE0.6 + MMU3", + "inherits": "0.15mm STRUCTURAL @CORE One 0.6", + "from": "system", + "setting_id": "PTOVGYLNah28dKRr", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.16mm SPEED @COREONE0.3 + MMU3.json b/resources/profiles/Prusa/process/0.16mm SPEED @COREONE0.3 + MMU3.json new file mode 100644 index 0000000000..30c6902ee7 --- /dev/null +++ b/resources/profiles/Prusa/process/0.16mm SPEED @COREONE0.3 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.16mm SPEED @COREONE0.3 + MMU3", + "inherits": "0.16mm SPEED @CORE One 0.3", + "from": "system", + "setting_id": "ByXT7JeCV0SWiSnt", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.3 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.16mm STRUCTURAL @COREONE0.3 + MMU3.json b/resources/profiles/Prusa/process/0.16mm STRUCTURAL @COREONE0.3 + MMU3.json new file mode 100644 index 0000000000..946d22c3a5 --- /dev/null +++ b/resources/profiles/Prusa/process/0.16mm STRUCTURAL @COREONE0.3 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.16mm STRUCTURAL @COREONE0.3 + MMU3", + "inherits": "0.16mm STRUCTURAL @CORE One 0.3", + "from": "system", + "setting_id": "3XaKemYMM9AMLAIE", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.3 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.20mm BALANCED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.20mm BALANCED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..eba4b17a0e --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm BALANCED @COREONE0.4 + MMU3.json @@ -0,0 +1,66 @@ +{ + "type": "process", + "name": "0.20mm BALANCED @COREONE0.4 + MMU3", + "inherits": "0.20mm Standard @MK4", + "from": "system", + "setting_id": "ZhSjwXMEOWyeWIDd", + "instantiation": "true", + "bridge_acceleration": "1500", + "brim_type": "no_brim", + "default_acceleration": "3000", + "elefant_foot_compensation": "0.2", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "initial_layer_acceleration": "500", + "initial_layer_infill_speed": "100", + "initial_layer_line_width": "0.5", + "inner_wall_line_width": "0.45", + "internal_solid_infill_line_width": "0.45", + "line_width": "0.45", + "outer_wall_line_width": "0.45", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "raft_first_layer_expansion": "3", + "resolution": "0", + "seam_gap": "15%", + "sparse_infill_line_width": "0.45", + "sparse_infill_pattern": "grid", + "support_base_pattern_spacing": "2", + "support_bottom_interface_spacing": "0.2", + "support_interface_bottom_layers": "0", + "support_interface_spacing": "0.2", + "support_interface_top_layers": "3", + "support_line_width": "0.4", + "support_speed": "120", + "top_surface_acceleration": "2000", + "top_surface_line_width": "0.42", + "travel_acceleration": "7000", + "travel_speed": "350", + "wall_loops": "2", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "bridge_speed": "50", + "inner_wall_acceleration": "3000", + "inner_wall_speed": "150", + "internal_solid_infill_acceleration": "6000", + "internal_solid_infill_speed": "150", + "outer_wall_acceleration": "2500", + "overhang_2_4_speed": "25", + "overhang_3_4_speed": "50", + "overhang_4_4_speed": "80%", + "small_perimeter_speed": "170", + "sparse_infill_acceleration": "5000", + "sparse_infill_speed": "150", + "support_interface_speed": "60", + "support_style": "snug", + "support_threshold_angle": "35", + "top_shell_layers": "5", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ] +} diff --git a/resources/profiles/Prusa/process/0.20mm SOLUBLE FULL @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.20mm SOLUBLE FULL @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..88860432a2 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm SOLUBLE FULL @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm SOLUBLE FULL @COREONE0.4 + MMU3", + "inherits": "0.20mm SOLUBLE FULL @CORE One 0.4", + "from": "system", + "setting_id": "F4GvezeeqYGgETx5", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..c74ace1163 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm SOLUBLE INTERFACE @COREONE0.4 + MMU3", + "inherits": "0.20mm SOLUBLE INTERFACE @CORE One 0.4", + "from": "system", + "setting_id": "hrh6ZS8cMrp6Fput", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.3 + MMU3.json b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.3 + MMU3.json new file mode 100644 index 0000000000..660170520b --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.3 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm SPEED @COREONE0.3 + MMU3", + "inherits": "0.20mm SPEED @CORE One 0.3", + "from": "system", + "setting_id": "a8MnMQB4PuzlCBkv", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.3 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..66ac63af73 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm SPEED @COREONE0.4 + MMU3", + "inherits": "0.20mm SPEED @CORE One 0.4", + "from": "system", + "setting_id": "gFegVTUTnp12IurN", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..fa6fcb0a90 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm SPEED @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm SPEED @COREONE0.6 + MMU3", + "inherits": "0.20mm SPEED @CORE One 0.6", + "from": "system", + "setting_id": "b5DuLDBbPOrc3QUB", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.3 + MMU3.json b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.3 + MMU3.json new file mode 100644 index 0000000000..d1fe101fb0 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.3 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm STRUCTURAL @COREONE0.3 + MMU3", + "inherits": "0.20mm STRUCTURAL @CORE One 0.3", + "from": "system", + "setting_id": "3GXAe0t8hNMloSsA", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.3 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..cdb41c0ac6 --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.4 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm STRUCTURAL @COREONE0.4 + MMU3", + "inherits": "0.20mm STRUCTURAL @CORE One 0.4", + "from": "system", + "setting_id": "elSJkPZlFi4sWveY", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..1a5cad323a --- /dev/null +++ b/resources/profiles/Prusa/process/0.20mm STRUCTURAL @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.20mm STRUCTURAL @COREONE0.6 + MMU3", + "inherits": "0.20mm STRUCTURAL @CORE One 0.6", + "from": "system", + "setting_id": "uvQ25wBWKEFdqMVl", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.25mm BALANCED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.25mm BALANCED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..4a3343351a --- /dev/null +++ b/resources/profiles/Prusa/process/0.25mm BALANCED @COREONE0.4 + MMU3.json @@ -0,0 +1,68 @@ +{ + "type": "process", + "name": "0.25mm BALANCED @COREONE0.4 + MMU3", + "inherits": "0.20mm Standard @MK4", + "from": "system", + "setting_id": "cEccdq6KND9xa5HS", + "instantiation": "true", + "bridge_acceleration": "1500", + "brim_type": "no_brim", + "default_acceleration": "3000", + "elefant_foot_compensation": "0.2", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "initial_layer_acceleration": "500", + "initial_layer_infill_speed": "100", + "initial_layer_line_width": "0.5", + "inner_wall_line_width": "0.45", + "internal_solid_infill_line_width": "0.45", + "line_width": "0.45", + "outer_wall_line_width": "0.45", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "raft_first_layer_expansion": "3", + "resolution": "0", + "seam_gap": "15%", + "sparse_infill_line_width": "0.45", + "sparse_infill_pattern": "grid", + "support_base_pattern_spacing": "2", + "support_bottom_interface_spacing": "0.2", + "support_interface_bottom_layers": "0", + "support_interface_spacing": "0.2", + "support_interface_top_layers": "3", + "support_line_width": "0.4", + "support_speed": "120", + "top_surface_acceleration": "2000", + "top_surface_line_width": "0.42", + "travel_acceleration": "7000", + "travel_speed": "350", + "wall_loops": "2", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "bridge_speed": "50", + "inner_wall_acceleration": "3000", + "inner_wall_speed": "150", + "internal_solid_infill_acceleration": "5000", + "internal_solid_infill_speed": "140", + "layer_height": "0.25", + "outer_wall_acceleration": "2500", + "overhang_2_4_speed": "15", + "overhang_3_4_speed": "25", + "overhang_4_4_speed": "70%", + "small_perimeter_speed": "150", + "sparse_infill_acceleration": "5000", + "sparse_infill_speed": "150", + "support_base_pattern": "rectilinear", + "support_bottom_z_distance": "0.25", + "support_style": "snug", + "support_threshold_angle": "35", + "support_top_z_distance": "0.25", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ] +} diff --git a/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..869de56781 --- /dev/null +++ b/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.4 + MMU3.json @@ -0,0 +1,65 @@ +{ + "type": "process", + "name": "0.25mm SPEED @COREONE0.4 + MMU3", + "inherits": "0.20mm Standard @MK4", + "from": "system", + "setting_id": "oVLlJtwPINArAb0Y", + "instantiation": "true", + "bridge_acceleration": "1500", + "brim_type": "no_brim", + "default_acceleration": "3000", + "elefant_foot_compensation": "0.2", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "initial_layer_acceleration": "500", + "initial_layer_infill_speed": "100", + "initial_layer_line_width": "0.5", + "inner_wall_line_width": "0.45", + "internal_solid_infill_line_width": "0.45", + "line_width": "0.45", + "outer_wall_line_width": "0.45", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "raft_first_layer_expansion": "3", + "resolution": "0", + "seam_gap": "15%", + "sparse_infill_line_width": "0.45", + "sparse_infill_pattern": "grid", + "support_base_pattern_spacing": "2", + "support_bottom_interface_spacing": "0.2", + "support_interface_bottom_layers": "0", + "support_interface_spacing": "0.2", + "support_interface_top_layers": "3", + "support_line_width": "0.4", + "support_speed": "120", + "top_surface_acceleration": "2000", + "top_surface_line_width": "0.42", + "travel_acceleration": "7000", + "travel_speed": "350", + "wall_loops": "2", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "bridge_speed": "50", + "inner_wall_acceleration": "6000", + "internal_solid_infill_acceleration": "5000", + "layer_height": "0.25", + "overhang_2_4_speed": "15", + "overhang_3_4_speed": "25", + "overhang_4_4_speed": "70%", + "small_perimeter_speed": "170", + "sparse_infill_acceleration": "7000", + "support_base_pattern": "rectilinear", + "support_bottom_z_distance": "0.25", + "support_interface_speed": "60", + "support_style": "snug", + "support_threshold_angle": "35", + "support_top_z_distance": "0.25", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ] +} diff --git a/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..ebfaa5bad5 --- /dev/null +++ b/resources/profiles/Prusa/process/0.25mm SPEED @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.25mm SPEED @COREONE0.6 + MMU3", + "inherits": "0.25mm SPEED @CORE One 0.6", + "from": "system", + "setting_id": "CRAV58WCDIn2bZdK", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.4 + MMU3.json b/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.4 + MMU3.json new file mode 100644 index 0000000000..87aa0f0c52 --- /dev/null +++ b/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.4 + MMU3.json @@ -0,0 +1,72 @@ +{ + "type": "process", + "name": "0.25mm STRUCTURAL @COREONE0.4 + MMU3", + "inherits": "0.20mm Standard @MK4", + "from": "system", + "setting_id": "lBGry26X4a9IihAD", + "instantiation": "true", + "bridge_acceleration": "1500", + "brim_type": "no_brim", + "default_acceleration": "3000", + "elefant_foot_compensation": "0.2", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "initial_layer_acceleration": "500", + "initial_layer_infill_speed": "100", + "initial_layer_line_width": "0.5", + "inner_wall_line_width": "0.45", + "internal_solid_infill_line_width": "0.45", + "line_width": "0.45", + "outer_wall_line_width": "0.45", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "raft_first_layer_expansion": "3", + "resolution": "0", + "seam_gap": "15%", + "sparse_infill_line_width": "0.45", + "sparse_infill_pattern": "grid", + "support_base_pattern_spacing": "2", + "support_bottom_interface_spacing": "0.2", + "support_interface_bottom_layers": "0", + "support_interface_spacing": "0.2", + "support_interface_top_layers": "3", + "support_line_width": "0.4", + "support_speed": "120", + "top_surface_acceleration": "2000", + "top_surface_line_width": "0.42", + "travel_acceleration": "7000", + "travel_speed": "350", + "wall_loops": "2", + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "bridge_speed": "50", + "gap_infill_speed": "60", + "inner_wall_acceleration": "2500", + "inner_wall_speed": "70", + "internal_solid_infill_acceleration": "4000", + "internal_solid_infill_speed": "140", + "layer_height": "0.25", + "outer_wall_acceleration": "1500", + "outer_wall_speed": "50", + "overhang_1_4_speed": "50%", + "overhang_2_4_speed": "15", + "overhang_3_4_speed": "45", + "overhang_4_4_speed": "90%", + "small_perimeter_speed": "50", + "sparse_infill_acceleration": "6000", + "sparse_infill_speed": "120", + "support_base_pattern": "rectilinear", + "support_bottom_z_distance": "0.25", + "support_style": "snug", + "support_threshold_angle": "40", + "support_top_z_distance": "0.25", + "top_surface_speed": "80", + "compatible_printers": [ + "Prusa CORE One MMU3 0.4 nozzle" + ] +} diff --git a/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..3ade06d990 --- /dev/null +++ b/resources/profiles/Prusa/process/0.25mm STRUCTURAL @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.25mm STRUCTURAL @COREONE0.6 + MMU3", + "inherits": "0.25mm STRUCTURAL @CORE One 0.6", + "from": "system", + "setting_id": "95m4AA4YRwcBt63r", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/profiles/Prusa/process/0.32mm SPEED @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.32mm SPEED @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..1f39814702 --- /dev/null +++ b/resources/profiles/Prusa/process/0.32mm SPEED @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.32mm SPEED @COREONE0.6 + MMU3", + "inherits": "0.32mm SPEED @CORE One 0.6", + "from": "system", + "setting_id": "nqrmLvULVOKW9bSm", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%", + "compatible_printers_condition": "" +} diff --git a/resources/profiles/Prusa/process/0.32mm STRUCTURAL @COREONE0.6 + MMU3.json b/resources/profiles/Prusa/process/0.32mm STRUCTURAL @COREONE0.6 + MMU3.json new file mode 100644 index 0000000000..8d69f5c4bc --- /dev/null +++ b/resources/profiles/Prusa/process/0.32mm STRUCTURAL @COREONE0.6 + MMU3.json @@ -0,0 +1,23 @@ +{ + "type": "process", + "name": "0.32mm STRUCTURAL @COREONE0.6 + MMU3", + "inherits": "0.32mm STRUCTURAL @CORE One 0.6", + "from": "system", + "setting_id": "rYhCYIq1vvY40IYf", + "instantiation": "true", + "compatible_printers": [ + "Prusa CORE One MMU3 0.6 nozzle" + ], + "compatible_printers_condition": "", + "filename_format": "{input_filename_base}_{layer_height}mm_{filament_type[0]}-{filament_type[1]}-{filament_type[2]}-{filament_type[3]}-{filament_type[4]}_COREONE_MMU3_{print_time}.gcode", + "prime_tower_brim_width": "2", + "print_extruder_id": [ + "1" + ], + "print_extruder_variant": [ + "Direct Drive Standard" + ], + "wipe_tower_cone_angle": "25", + "wipe_tower_extra_flow": "250%", + "wipe_tower_extra_spacing": "110%" +} diff --git a/resources/shaders/110/ssao.fs b/resources/shaders/110/ssao.fs index 5aada05883..db266daba1 100644 --- a/resources/shaders/110/ssao.fs +++ b/resources/shaders/110/ssao.fs @@ -7,18 +7,38 @@ uniform sampler2D color_texture; uniform sampler2D depth_texture; -uniform sampler2D normal_texture; uniform vec2 inv_tex_size; -uniform float z_near; uniform float z_far; uniform bool is_outline; +// The pass has no normal target to read, so the surface normal is reconstructed from the depth +// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z +// expressed in view space, which is what tells a top surface from a wall. +uniform mat4 inv_projection_matrix; +uniform vec3 up_view; varying vec2 tex_coord; -float linearize_depth(float depth) +// Position of the given pixel in view space. Valid under both an orthographic and a perspective +// camera, unlike the depth linearization it replaces. +vec3 view_pos(vec2 uv) { - float z = depth * 2.0 - 1.0; - return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); + vec2 c = clamp(uv, vec2(0.0), vec2(1.0)); + float d = texture2D(depth_texture, c).r; + vec4 ndc = vec4(c * 2.0 - 1.0, d * 2.0 - 1.0, 1.0); + vec4 view = inv_projection_matrix * ndc; + return view.xyz / view.w; +} + +// Surface normal at the given pixel, from the forward differences of the reconstructed view +// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the +// normal only weights the occlusion, nothing is shaded with it. +vec3 view_normal(vec2 uv, vec3 p) +{ + vec3 px = view_pos(uv + vec2(inv_tex_size.x, 0.0)); + vec3 py = view_pos(uv + vec2(0.0, inv_tex_size.y)); + vec3 n = cross(px - p, py - p); + float len = length(n); + return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0); } void main() @@ -28,16 +48,21 @@ void main() return; } vec3 base = texture2D(color_texture, tex_coord).rgb; - float depth_center = linearize_depth(texture2D(depth_texture, tex_coord).r); - // Sample normal at current fragment (range: -1 to 1) - vec3 normal_center = texture2D(normal_texture, tex_coord).rgb * 2.0 - 1.0; + // Nothing was drawn here: occluding the background would only darken the gradient, and its + // reconstructed normal is degenerate anyway. + if (texture2D(depth_texture, tex_coord).r >= 0.9999) { + gl_FragColor = vec4(base, 1.0); + return; + } + + vec3 center_pos = view_pos(tex_coord); + float depth_center = -center_pos.z; + vec3 normal_center = view_normal(tex_coord, center_pos); // Calculate how much the surface faces upward - // up_factor = 1.0 for surfaces pointing straight up (0,0,1) - // up_factor = 0.0 for surfaces pointing down or sideways - float up_factor = max(0.0, normal_center.z); // Assuming Z is up axis - // Alternative: if Y is up, use normal_center.y + // up_factor = 1.0 for surfaces pointing straight up, 0.0 for walls and downward faces + float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0); // Adaptive sampling radius float radius = mix(2.0, 4.0, depth_center / z_far); @@ -52,39 +77,38 @@ void main() offsets[6] = vec2( 0.0, -1.0); offsets[7] = vec2( 0.707,-0.707); + // Occlusion is a slope, not a depth difference: how far a neighbour rises out of the + // centre's tangent plane over how far away it is. Unlike a raw difference, that sine is + // free of camera distance and zoom, so a crease reads the same from any view. + const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface + const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease + + const float SAMPLE_COUNT = 8.0; float occlusion = 0.0; - int valid_samples = 0; for (int i = 0; i < 8; ++i) { vec2 uv = tex_coord + offsets[i] * inv_tex_size * radius; - uv = clamp(uv, vec2(0.001), vec2(0.999)); - float sample_depth = linearize_depth(texture2D(depth_texture, uv).r); - float depth_diff = max(0.0, depth_center - sample_depth); - - float threshold = 0.015 * (0.5 + depth_center / z_far); - float contribution = smoothstep(0.001, threshold, depth_diff); + vec3 delta = view_pos(uv) - center_pos; + float dist = length(delta); + float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0; + float contribution = smoothstep(SLOPE_MIN, SLOPE_MAX, rise); float diagonal_weight = 1.0 - abs(offsets[i].x * offsets[i].y) * 0.5; occlusion += contribution * diagonal_weight; - valid_samples++; } - if (valid_samples > 0) - occlusion /= float(valid_samples); + occlusion /= SAMPLE_COUNT; // flatter/top-like surfaces get less darkening float ao_intensity = 0.55; float ambient_occlusion = 1.0 - occlusion * ao_intensity; - // Different min values for top vs bottom surfaces + // Different min values for top vs bottom surfaces. The boost that used to follow lifted a + // top surface back to within 2% of unoccluded once up_factor became a real normal rather + // than a colour, which is where the AO went; the floors alone shape the effect now. float ao_min = mix(0.45, 0.70, up_factor); // Bottom: 0.45, Top: 0.70 ambient_occlusion = clamp(ambient_occlusion, ao_min, 1.0); - // Boost brightness on top surfaces (optional) - float brightness_boost = 1.0 + up_factor * 0.15; // 15% extra brightness on top - ambient_occlusion = pow(ambient_occlusion, 2.2) * brightness_boost; - ambient_occlusion = clamp(ambient_occlusion, 0.45, 1.05); - gl_FragColor = vec4(base * ambient_occlusion, 1.0); -} \ No newline at end of file +} diff --git a/resources/shaders/140/ssao.fs b/resources/shaders/140/ssao.fs index b3b6df6877..28b561ba2c 100644 --- a/resources/shaders/140/ssao.fs +++ b/resources/shaders/140/ssao.fs @@ -1,24 +1,45 @@ #version 140 /** - * SSAO Shader - GLSL 140 version with sharp depth threshold + * SSAO Shader - GLSL 140 version with a slope-based occlusion test * Only darkens valleys/concave areas, ignores smooth variations */ uniform sampler2D color_texture; uniform sampler2D depth_texture; -uniform sampler2D normal_texture; -uniform float z_near; +uniform vec2 inv_tex_size; uniform float z_far; uniform bool is_outline; +// The pass has no normal target to read, so the surface normal is reconstructed from the depth +// buffer. inv_projection_matrix unprojects a pixel back into view space and up_view is world +Z +// expressed in view space, which is what tells a top surface from a wall. +uniform mat4 inv_projection_matrix; +uniform vec3 up_view; in vec2 tex_coord; out vec4 frag_color; -float linearize_depth(float depth) +// Position of the given pixel in view space. Valid under both an orthographic and a perspective +// camera, unlike the depth linearization it replaces. +vec3 view_pos(ivec2 pixel) { - float z = depth * 2.0 - 1.0; - return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); + ivec2 p = clamp(pixel, ivec2(0), textureSize(depth_texture, 0) - 1); + float d = texelFetch(depth_texture, p, 0).r; + vec4 ndc = vec4((vec2(p) + 0.5) * inv_tex_size * 2.0 - 1.0, d * 2.0 - 1.0, 1.0); + vec4 view = inv_projection_matrix * ndc; + return view.xyz / view.w; +} + +// Surface normal at the given pixel, from the forward differences of the reconstructed view +// position. It rings by a pixel across a depth discontinuity, which is acceptable here: the +// normal only weights the occlusion, nothing is shaded with it. +vec3 view_normal(ivec2 pixel, vec3 p) +{ + vec3 px = view_pos(pixel + ivec2(1, 0)); + vec3 py = view_pos(pixel + ivec2(0, 1)); + vec3 n = cross(px - p, py - p); + float len = length(n); + return (len > 1e-8) ? n / len : vec3(0.0, 0.0, 1.0); } void main() @@ -28,81 +49,72 @@ void main() return; } ivec2 pixel = ivec2(gl_FragCoord.xy); - float center_depth = linearize_depth(texelFetch(depth_texture, pixel, 0).r); - - // Sample normal buffer (stored as RGB in 0-1 range, convert to -1 to 1) - vec3 normal_center = texelFetch(normal_texture, pixel, 0).rgb * 2.0 - 1.0; - normal_center = normalize(normal_center); - + vec3 color = texture(color_texture, tex_coord).rgb; + + // Nothing was drawn here: occluding the background would only darken the gradient, and its + // reconstructed normal is degenerate anyway. + if (texelFetch(depth_texture, pixel, 0).r >= 0.9999) { + frag_color = vec4(color, 1.0); + return; + } + + vec3 center_pos = view_pos(pixel); + float center_depth = -center_pos.z; + vec3 normal_center = view_normal(pixel, center_pos); + // Calculate upward-facing factor (Z-up coordinate system) - float up_factor = clamp(normal_center.z * 1.5, 0.0, 1.0); - + float up_factor = clamp(dot(normal_center, up_view), 0.0, 1.0); + // Adaptive radius in pixel space int radius = int(mix(2.0, 4.0, center_depth / z_far)); // Optimized sampling pattern - const ivec2 offsets[12] = ivec2[]( + const int SAMPLE_COUNT = 12; + const ivec2 offsets[SAMPLE_COUNT] = ivec2[]( ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1), ivec2(1, 1), ivec2(-1, 1), ivec2(1, -1), ivec2(-1, -1), ivec2(2, 0), ivec2(-2, 0), ivec2(0, 2), ivec2(0, -2) ); - float occlusion = 0.0; - int valid_samples = 0; + // Occlusion is a slope, not a depth difference: the sine of the angle a neighbour subtends + // above the centre's tangent plane. A raw difference depends on camera distance and zoom, + // so no fixed thresholds suit both a 0.2 mm layer step and a 5 mm overhang. + const float SLOPE_MIN = 0.08; // ~5 degrees, above the depth-buffer noise of a flat surface + const float SLOPE_MAX = 0.60; // ~37 degrees, a full crease - for (int i = 0; i < 12; i++) { + float occlusion = 0.0; + + for (int i = 0; i < SAMPLE_COUNT; i++) { + // No edge rejection: view_pos clamps, giving a near-zero delta and no occlusion. + // Rejecting one side only would bias the denominator against the other. ivec2 sample_pixel = pixel + offsets[i] * radius; - if (sample_pixel.x < 0 || sample_pixel.y < 0) - continue; - - float sample_depth = linearize_depth(texelFetch(depth_texture, sample_pixel, 0).r); - - // Sample normal at neighbor - vec3 normal_sample = texelFetch(normal_texture, sample_pixel, 0).rgb * 2.0 - 1.0; - - // Depth difference (positive if neighbor is closer to camera) - float depth_diff = center_depth - sample_depth; - - // Sharp depth threshold === - // Minimum depth difference to consider occlusion (ignores small variations) - float threshold_min = 0.008; // Higher = only deep valleys get darkened - float threshold_max = 0.04; // Transition range for full occlusion + vec3 delta = view_pos(sample_pixel) - center_pos; + float dist = length(delta); + // How far the neighbour rises towards the viewer out of the centre's tangent plane. A + // flat surface gives ~0 whatever its orientation, so this also subsumes the separate + // planar test the normals were compared for. + float rise = (dist > 1e-6) ? dot(delta, normal_center) / dist : 0.0; float contribution = 0.0; - if (depth_diff > threshold_min) { + if (rise > SLOPE_MIN) { // Abrupt mapping with power curve - contribution = (depth_diff - threshold_min) / (threshold_max - threshold_min); + contribution = (rise - SLOPE_MIN) / (SLOPE_MAX - SLOPE_MIN); contribution = clamp(contribution, 0.0, 1.0); contribution = pow(contribution, 2.0); // Steeper curve for sharper transition } - // Reduce occlusion on planar surfaces (similar normals) - float normal_similarity = dot(normal_center, normal_sample); - float planar_factor = smoothstep(0.75, 0.95, normal_similarity); - contribution *= (1.0 - planar_factor * 0.6); - occlusion += contribution; - valid_samples++; } - if (valid_samples > 0) { - // Calculate ambient occlusion factor with higher base intensity - float ao_factor = 1.0 - (occlusion / float(valid_samples)) * 0.6; - - // Keep bright areas clean (higher minimum for upward-facing surfaces) - float ao_min = mix(0.55, 0.85, up_factor); - ao_factor = clamp(ao_factor, ao_min, 1.0); - - // Slight brightness boost for upward-facing surfaces - float brightness_boost = 1.0 + up_factor * 0.15; - ao_factor = ao_factor * brightness_boost; - - occlusion = ao_factor; - } else { - occlusion = 1.0; - } + // Calculate ambient occlusion factor with higher base intensity + float ao_factor = 1.0 - (occlusion / float(SAMPLE_COUNT)) * 0.6; + + // Keep bright areas clean (higher minimum for upward-facing surfaces). The old 0.85 floor + // and 1.15 boost were set when up_factor came from the colour buffer and read ~0; with a + // real normal they capped a top surface at 2% darkening, which hid the AO entirely. + float ao_min = mix(0.45, 0.70, up_factor); + occlusion = clamp(ao_factor, ao_min, 1.0); - vec3 color = texture(color_texture, tex_coord).rgb; frag_color = vec4(color * occlusion, 1.0); -} \ No newline at end of file +} diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 0c9bd0b1ee..57be0595cb 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #if defined(__linux__) || defined(__LINUX__) #include @@ -1614,6 +1615,10 @@ int CLI::run(int argc, char **argv) ConfigOptionBool* allow_rotations_option = m_config.option("allow_rotations"); if (allow_rotations_option) allow_rotations = allow_rotations_option->value; + // Only an explicit --align-to-y-axis overrides the printer-structure default. + std::optional align_to_y_axis; + if (m_given_option_keys.count("align_to_y_axis") > 0) + align_to_y_axis = m_config.opt_bool("align_to_y_axis"); ConfigOptionBool* skip_modified_gcodes_option = m_config.option("skip_modified_gcodes"); if (skip_modified_gcodes_option) @@ -5298,7 +5303,9 @@ int CLI::run(int argc, char **argv) arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT; arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT; } - if (auto printer_structure_opt = m_print_config.option>("printer_structure")) { + if (align_to_y_axis.has_value()) { + arrange_cfg.align_to_y_axis = *align_to_y_axis; + } else if (auto printer_structure_opt = m_print_config.option>("printer_structure")) { arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3); } @@ -5748,7 +5755,9 @@ int CLI::run(int argc, char **argv) arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT; arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT; } - if (auto printer_structure_opt = m_print_config.option>("printer_structure")) { + if (align_to_y_axis.has_value()) { + arrange_cfg.align_to_y_axis = *align_to_y_axis; + } else if (auto printer_structure_opt = m_print_config.option>("printer_structure")) { arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3); } @@ -7943,6 +7952,8 @@ bool CLI::setup(int argc, char **argv) for (std::string &input_file : m_input_files) input_file = resolve_cli_input_path(input_file); + m_given_option_keys.insert(opt_order.begin(), opt_order.end()); + // Parse actions and transform options. for (auto const &opt_key : opt_order) { if (cli_actions_config_def.has(opt_key)) diff --git a/src/OrcaSlicer.hpp b/src/OrcaSlicer.hpp index e6aada821f..380607e95f 100644 --- a/src/OrcaSlicer.hpp +++ b/src/OrcaSlicer.hpp @@ -1,6 +1,8 @@ #ifndef SLIC3R_HPP #define SLIC3R_HPP +#include + #include "libslic3r/Config.hpp" #include "libslic3r/Model.hpp" @@ -113,6 +115,8 @@ private: std::vector m_input_files; std::vector m_actions; std::vector m_transforms; + // Options the user typed; setup() fills the CLI's own options with defaults afterwards. + std::set m_given_option_keys; std::vector m_models; bool setup(int argc, char **argv); diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index c8a2c4984d..cdcd9b4dec 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -224,6 +224,12 @@ void AppConfig::set_defaults() set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99)))); } + // ORCA: view type the G-code preview opens with. "auto" keeps the automatic choice (Filament for + // multi material prints, Line Type for single material ones), "last" restores the view type the user + // picked last, any other value is a fixed view type name, see GCodeViewer::view_type_to_config_name(). + if (get("preview_default_view_type").empty()) + set("preview_default_view_type", "auto"); + if (get("filaments_area_preferred_count").empty()) set("filaments_area_preferred_count", "10"); @@ -303,6 +309,9 @@ void AppConfig::set_defaults() if (get(SETTING_OPENGL_REALISTIC_PHONG).empty()) set_bool(SETTING_OPENGL_REALISTIC_PHONG, true); + if (get(SETTING_OPENGL_REALISTIC_PREVIEW).empty()) + set_bool(SETTING_OPENGL_REALISTIC_PREVIEW, false); + if (get(SETTING_OPENGL_SHADING_MODEL).empty()) set(SETTING_OPENGL_SHADING_MODEL, "gouraud"); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 24a4c2069b..2eac9f5fc6 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -42,6 +42,7 @@ using namespace nlohmann; #define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows" #define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao" #define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals" +#define SETTING_OPENGL_REALISTIC_PREVIEW "opengl_realistic_preview" #define SETTING_PLUGIN_PAGES_VISIBLE_COUNT "plugin_pages_visible_count" #define PLUGIN_PAGES_VISIBLE_COUNT_MIN 1 diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3e8a6478c6..1a1d1bcbff 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -8,6 +8,7 @@ #include "I18N.hpp" #include "GCode.hpp" #include "Exception.hpp" +#include "LifecycleEvents.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry/ConvexHull.hpp" @@ -2476,6 +2477,15 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_writer.set_is_bbl_machine(print->is_BBL_printer()); print->set_started(psGCodeExport); + { + LifecycleEventContext ctx; + ctx.name = std::to_string(print->model().id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = path; + ctx.cancellation_check = [print]() { return print->canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx); + } + // check if any custom gcode contains keywords used by the gcode processor to // produce time estimation and gcode toolpaths std::vector> validation_res = DoExport::validate_custom_gcode(*print); @@ -2511,12 +2521,23 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu m_processor.set_print(print); GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); if (! file.is_open()) { - BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl; + std::string err_msg = std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"; + BOOST_LOG_TRIVIAL(error) << err_msg << std::endl; if (!fs::exists(folder)) { //fs::create_directory(folder); - BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl; + std::string add_err_msg = "the parent path " + folder.string() +" is not there!!!"; + BOOST_LOG_TRIVIAL(error) << add_err_msg << std::endl; + err_msg += add_err_msg; } - throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + { + LifecycleEventContext ctx; + ctx.name = std::to_string(print->model().id().id); + ctx.code = LifecycleEvtCode::Error; + ctx.msg = std::string(path) + "\n" + err_msg; + ctx.cancellation_check = [print]() { return print->canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } + throw Slic3r::RuntimeError(err_msg); } try { @@ -2527,11 +2548,19 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu boost::nowide::remove(path_tmp.c_str()); throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } - } catch (std::exception & /* ex */) { + } catch (std::exception &ex) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. // Close and remove the file. file.close(); boost::nowide::remove(path_tmp.c_str()); + { + LifecycleEventContext ctx; + ctx.name = std::to_string(print->model().id().id); + ctx.code = LifecycleEvtCode::Error; + ctx.msg = std::string(path) + "\n" + ex.what(); + ctx.cancellation_check = [print]() { return print->canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } throw; } file.close(); @@ -2637,6 +2666,14 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu std::error_code ret = rename_file(path_tmp, path); if (ret) { + { + LifecycleEventContext ctx; + ctx.name = std::to_string(print->model().id().id); + ctx.code = LifecycleEvtCode::Error; + ctx.msg = std::string(path) + "\nFailed to rename the output G-code file: " + ret.message(); + ctx.cancellation_check = [print]() { return print->canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } throw Slic3r::RuntimeError( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' + "Is " + path_tmp + " locked?" + '\n'); @@ -2647,7 +2684,16 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); print->set_done(psGCodeExport); - + + { + LifecycleEventContext ctx; + ctx.name = std::to_string(print->model().id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = path; + ctx.cancellation_check = [print]() { return print->canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } + // Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT / // M486), which is driven by exclude_object for every printer if(result != nullptr) diff --git a/src/libslic3r/LifecycleEvents.hpp b/src/libslic3r/LifecycleEvents.hpp new file mode 100644 index 0000000000..f3e6f32392 --- /dev/null +++ b/src/libslic3r/LifecycleEvents.hpp @@ -0,0 +1,249 @@ +#pragma once + +// LifecycleEvents.hpp +// -------------------- +// Application lifecycle events (project, slicing, plate editing, preset, printer connection, and +// job activity) that other subsystems -- chiefly the plugin layer above libslic3r -- may want to +// observe. Lives in libslic3r rather than the plugin layer because some events fire from inside +// the slicing engine itself; see fire_lifecycle_event() below. + +#include +#include +#include +#include +#include +#include + +namespace Slic3r +{ + enum class LifecycleEvent { + // Project (3mf) + NewProject, + ProjectOpened, + ProjectBeforeSave, + ProjectAfterSave, + ProjectClosed, + ProjectDirtyChanged, + + // Slicing pipeline + SliceStarted, + SliceGeometryFinished, + GCodeExportStarted, + GCodeExportFinished, + SlicingJobComplete, + + // Plate/model editing + ObjectAdded, + ObjectDeleted, + ObjectTransformed, + ObjectChanged, + ObjectRenamed, + PlateCreated, + PlateDeleted, + PlateSelected, + PlateRenamed, + + // Preset + PresetSelected, + PresetSaved, + + // Printer/device + PrintStateChanged, + DeviceOnline, + DeviceOffline, + DeviceDiscovered, + DeviceSelected, + UploadStarted, + UploadFinished, + + // Print/send jobs + PrintJobStarted, + PrintJobFinished, + SendJobStarted, + SendJobFinished, + }; + + // Scoped so callers must qualify (LifecycleEvtCode::Error, not ERROR) -- ERROR/OK collide with + // Windows macros (wingdi.h) as unqualified names. + enum class LifecycleEvtCode { Ok, Error, Warn }; + + struct LifecycleEventContext + { + // The primary subject identifier for the event. This is event-specific (for example, a + // project/output path, preset name, device id, or object name), must not contain status or + // prose, and may be empty when the event has no single subject. + std::string name; + + // Outcome of the operation represented by the event. For state-change and start events, + // Ok means that the event occurred; it does not imply that a future operation succeeded. + LifecycleEvtCode code = LifecycleEvtCode::Ok; + + // Optional human-readable detail or diagnostic text. It is not a stable parsing contract; + // machine-readable data should be represented by a dedicated field or event instead. + std::string msg; + + // Stable subject/object identifier, when the source model provides one. + std::string id; + + // Previous value for rename and other before/after events. + std::string previous_name; + + // Device identifier for printer and job events. + std::string device_id; + + // Job identifier when the originating queue/task provides one. + std::string job_id; + + // Source subsystem or operation detail, suitable for filtering but not guaranteed to be + // exhaustive across versions. + std::string source; + + // Plate, object, or volume index when the source uses an index rather than a stable id. + int index = -1; + + // Aggregate project dirty state for ProjectDirtyChanged. + bool dirty = false; + + // Optional host-side cancellation probe. Background slicing and G-code export events set + // this to the originating Print's cancellation state so dispatch can stop before calling + // the next capability. It is intentionally not exposed through the Python payload API. + std::function cancellation_check; + }; + + inline std::string lifecycle_event_to_string(LifecycleEvent event) + { + switch (event) { + case LifecycleEvent::NewProject: return "NewProject"; + case LifecycleEvent::ProjectOpened: return "ProjectOpened"; + case LifecycleEvent::ProjectBeforeSave: return "ProjectBeforeSave"; + case LifecycleEvent::ProjectAfterSave: return "ProjectAfterSave"; + case LifecycleEvent::ProjectClosed: return "ProjectClosed"; + case LifecycleEvent::ProjectDirtyChanged: return "ProjectDirtyChanged"; + + case LifecycleEvent::SliceStarted: return "SliceStarted"; + case LifecycleEvent::SliceGeometryFinished: return "SliceGeometryFinished"; + case LifecycleEvent::GCodeExportStarted: return "GCodeExportStarted"; + case LifecycleEvent::GCodeExportFinished: return "GCodeExportFinished"; + case LifecycleEvent::SlicingJobComplete: return "SlicingJobComplete"; + + case LifecycleEvent::ObjectAdded: return "ObjectAdded"; + case LifecycleEvent::ObjectDeleted: return "ObjectDeleted"; + case LifecycleEvent::ObjectTransformed: return "ObjectTransformed"; + case LifecycleEvent::ObjectChanged: return "ObjectChanged"; + case LifecycleEvent::ObjectRenamed: return "ObjectRenamed"; + case LifecycleEvent::PlateCreated: return "PlateCreated"; + case LifecycleEvent::PlateDeleted: return "PlateDeleted"; + case LifecycleEvent::PlateSelected: return "PlateSelected"; + case LifecycleEvent::PlateRenamed: return "PlateRenamed"; + + case LifecycleEvent::PresetSelected: return "PresetSelected"; + case LifecycleEvent::PresetSaved: return "PresetSaved"; + case LifecycleEvent::PrintStateChanged: return "PrintStateChanged"; + + case LifecycleEvent::DeviceOnline: return "DeviceOnline"; + case LifecycleEvent::DeviceOffline: return "DeviceOffline"; + case LifecycleEvent::DeviceDiscovered: return "DeviceDiscovered"; + case LifecycleEvent::DeviceSelected: return "DeviceSelected"; + + case LifecycleEvent::UploadStarted: return "UploadStarted"; + case LifecycleEvent::UploadFinished: return "UploadFinished"; + case LifecycleEvent::PrintJobStarted: return "PrintJobStarted"; + case LifecycleEvent::PrintJobFinished: return "PrintJobFinished"; + case LifecycleEvent::SendJobStarted: return "SendJobStarted"; + case LifecycleEvent::SendJobFinished: return "SendJobFinished"; + default: return "Unknown"; + } + } + + inline std::string lifecycle_evt_code_to_string(LifecycleEvtCode code) + { + switch (code) { + case LifecycleEvtCode::Ok: return "Ok"; + case LifecycleEvtCode::Error: return "Error"; + case LifecycleEvtCode::Warn: return "Warn"; + default: return "Unknown"; + } + } + + // Global cross-layer seam (mirrors ConfigBase::set_resolve_capability_fn): any libslic3r code can + // fire a lifecycle event without depending on the plugin layer above it, which installs the + // dispatcher here at startup. Not tied to Print/GCode specifically, since nothing here should + // require callers to hold a Print& just to report an event. + using LifecycleHookFn = std::function; + + namespace detail { + + struct LifecycleHookState + { + std::mutex mutex; + std::condition_variable cv; + LifecycleHookFn fn; + std::size_t active_dispatches = 0; + bool accepting = false; + }; + + inline LifecycleHookState& lifecycle_hook_state() + { + static LifecycleHookState state; + return state; + } + + class LifecycleDispatchGuard + { + public: + explicit LifecycleDispatchGuard(LifecycleHookState& state) : m_state(state) {} + + ~LifecycleDispatchGuard() + { + std::lock_guard lock(m_state.mutex); + --m_state.active_dispatches; + if (m_state.active_dispatches == 0) + m_state.cv.notify_all(); + } + + LifecycleDispatchGuard(const LifecycleDispatchGuard&) = delete; + LifecycleDispatchGuard& operator=(const LifecycleDispatchGuard&) = delete; + + private: + LifecycleHookState& m_state; + }; + + } // namespace detail + + // Installing a hook starts accepting dispatches. Passing an empty function stops accepting + // new dispatches, detaches the hook, and waits for callbacks already in progress to finish. + // This is used during plugin shutdown so plugin code cannot be unloaded while a lifecycle + // callback is still executing. The empty-function path must not be called from inside the + // lifecycle callback itself. + inline void set_lifecycle_hook_fn(LifecycleHookFn fn) + { + detail::LifecycleHookState& state = detail::lifecycle_hook_state(); + if (fn) { + std::lock_guard lock(state.mutex); + state.fn = std::move(fn); + state.accepting = true; + return; + } + + std::unique_lock lock(state.mutex); + state.accepting = false; + state.fn = nullptr; + state.cv.wait(lock, [&state] { return state.active_dispatches == 0; }); + } + + inline void fire_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) + { + detail::LifecycleHookState& state = detail::lifecycle_hook_state(); + LifecycleHookFn fn; + { + std::lock_guard lock(state.mutex); + if (!state.accepting || !state.fn) + return; + fn = state.fn; + ++state.active_dispatches; + } + + detail::LifecycleDispatchGuard guard(state); + fn(event, ctx); + } +} diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 1002f5be89..214ef229df 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -47,6 +47,7 @@ #include #include "libslic3r.h" +#include "LifecycleEvents.hpp" #include "Utils.hpp" #include "Time.hpp" #include "PlaceholderParser.hpp" @@ -2972,6 +2973,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det // 1) Find the preset with a new_name or create a new one, // initialize it with the edited config. auto it = this->find_preset_internal(new_name); + const bool preset_existed = (it != m_presets.end() && it->name == new_name); if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. Preset &preset = *it; @@ -3079,6 +3081,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det this->get_selected_preset().save(&(parent_preset->config)); else this->get_selected_preset().save(nullptr); + + { + LifecycleEventContext ctx; + ctx.name = new_name; + ctx.msg = preset_existed ? "overwrite" : "new"; + ctx.code = LifecycleEvtCode::Ok; + fire_lifecycle_event(LifecycleEvent::PresetSaved, ctx); + } } // A detached standalone preset for the Full Publish receiver: create a user preset holding diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 833b811535..5df69b7335 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -14,6 +14,7 @@ #include "Flow.hpp" #include "Geometry/ConvexHull.hpp" #include "I18N.hpp" +#include "LifecycleEvents.hpp" #include "ShortestPath.hpp" #include "Thread.hpp" #include "Time.hpp" @@ -2694,6 +2695,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) if (m_objects.empty()) return; + { + LifecycleEventContext ctx; + ctx.name = std::to_string(m_model.id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.cancellation_check = [this]() { return canceled(); }; + fire_lifecycle_event(LifecycleEvent::SliceStarted, ctx); + } + for (PrintObject *obj : m_objects) obj->clear_shared_object(); @@ -3312,6 +3321,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) } BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); + + { + LifecycleEventContext ctx; + ctx.name = std::to_string(m_model.id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.cancellation_check = [this]() { return canceled(); }; + fire_lifecycle_event(LifecycleEvent::SliceGeometryFinished, ctx); + } } // G-code export process, running at a background thread. @@ -4911,6 +4928,15 @@ void Print::set_gcode_file_invalidated() //BBS: add gcode file preload logic void Print::export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) { + { + LifecycleEventContext ctx; + ctx.name = std::to_string(m_model.id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = file; + ctx.cancellation_check = [this]() { return canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportStarted, ctx); + } + try { GCodeProcessor processor; GCodeProcessor::s_IsBBLPrinter = is_BBL_printer(); @@ -4930,13 +4956,30 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces *result = std::move(processor.extract_result()); result->filament_change_sequence = filament_seq_loaded; result->nozzle_change_sequence = nozzle_seq_loaded; - } catch (std::exception & /* ex */) { + } catch (std::exception &ex) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": found errors when process gcode file %1%") %file.c_str(); + { + LifecycleEventContext ctx; + ctx.name = std::to_string(m_model.id().id); + ctx.code = LifecycleEvtCode::Error; + ctx.msg = file + "\n" + ex.what(); + ctx.cancellation_check = [this]() { return canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } throw Slic3r::RuntimeError( std::string("Failed to process the G-code file ") + file + " from previous 3mf\n"); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": process the G-code file %1% successfully")%file.c_str(); + + { + LifecycleEventContext ctx; + ctx.name = std::to_string(m_model.id().id); + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = file; + ctx.cancellation_check = [this]() { return canceled(); }; + fire_lifecycle_event(LifecycleEvent::GCodeExportFinished, ctx); + } } std::tuple Print::object_skirt_offset(double margin_height) const diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 92e7067ae5..bf7a651dcc 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12347,6 +12347,12 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = L("If enabled, Arrange will allow rotation when placing objects."); def->set_default_value(new ConfigOptionBool(true)); + def = this->add("align_to_y_axis", coBool); + def->label = L("Align to Y axis when arranging"); + def->tooltip = L("If enabled, Arrange will turn each object so its long side runs along the Y axis before placing it. " + "When not given, it is on for i3 printers and off for the others, as in the GUI."); + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("avoid_extrusion_cali_region", coBool); def->label = L("Avoid extrusion calibrate region when arranging"); def->tooltip = L("If enabled, Arrange will avoid extrusion calibrate region when placing objects."); diff --git a/src/libvgcode/include/Viewer.hpp b/src/libvgcode/include/Viewer.hpp index 5141245d96..179105032d 100644 --- a/src/libvgcode/include/Viewer.hpp +++ b/src/libvgcode/include/Viewer.hpp @@ -60,6 +60,22 @@ public: // using the given camera matrices. // void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix); + // + // ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's + // shadow map. Depth only - the caller masks colour writes and owns the framebuffer. + // + void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position); + // + // ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit. + // intensity == 0, the default, turns the lookup off and restores the plain shading. + // + void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size); + // + // ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term, + // the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the + // caller decides which of the two it varies with the realistic view setting. + // + void set_tone(float exposure, float saturation); // // ************************************************************************ diff --git a/src/libvgcode/src/SegmentTemplate.cpp b/src/libvgcode/src/SegmentTemplate.cpp index 4e692d5b10..9d9872aa3b 100644 --- a/src/libvgcode/src/SegmentTemplate.cpp +++ b/src/libvgcode/src/SegmentTemplate.cpp @@ -15,7 +15,12 @@ namespace libvgcode { //| 2--0-------5--7 | //| \ | | / | //| 3-------4 | -static constexpr const std::array VERTEX_DATA = { +// The eight corners the vertex shader knows how to place. Each is sent once and +// referenced by INDEX_DATA below, so the post-transform cache can reuse it across +// the triangles that share it: the shader runs 8 times per segment instead of 24. +static constexpr const std::array VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 }; + +static constexpr const std::array INDEX_DATA = { 0, 1, 2, // front spike 0, 2, 3, // front spike 0, 3, 4, // right/bottom body @@ -31,7 +36,7 @@ void SegmentTemplate::init() if (m_vao_id != 0) return; - m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t); + m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t); int curr_vertex_array; glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array)); @@ -51,12 +56,22 @@ void SegmentTemplate::init() glsafe(glVertexAttribIPointer(0, 1, GL_UNSIGNED_BYTE, 0, (const void*)0)); #endif // ENABLE_OPENGL_ES + // The element buffer binding is part of the vao state, so it is left bound here + // and restored together with the vao. + glsafe(glGenBuffers(1, &m_ibo_id)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW)); + glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer)); glsafe(glBindVertexArray(curr_vertex_array)); } void SegmentTemplate::shutdown() { + if (m_ibo_id != 0) { + glsafe(glDeleteBuffers(1, &m_ibo_id)); + m_ibo_id = 0; + } if (m_vbo_id != 0) { glsafe(glDeleteBuffers(1, &m_vbo_id)); m_vbo_id = 0; @@ -71,14 +86,15 @@ void SegmentTemplate::shutdown() void SegmentTemplate::render(size_t count) { - if (m_vao_id == 0 || m_vbo_id == 0 || count == 0) + if (m_vao_id == 0 || m_vbo_id == 0 || m_ibo_id == 0 || count == 0) return; int curr_vertex_array; glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array)); glsafe(glBindVertexArray(m_vao_id)); - glsafe(glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast(VERTEX_DATA.size()), static_cast(count))); + glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast(INDEX_DATA.size()), GL_UNSIGNED_BYTE, + nullptr, static_cast(count))); glsafe(glBindVertexArray(curr_vertex_array)); } diff --git a/src/libvgcode/src/SegmentTemplate.hpp b/src/libvgcode/src/SegmentTemplate.hpp index f104eef5d2..117ed2b678 100644 --- a/src/libvgcode/src/SegmentTemplate.hpp +++ b/src/libvgcode/src/SegmentTemplate.hpp @@ -40,6 +40,7 @@ private: // unsigned int m_vao_id{ 0 }; unsigned int m_vbo_id{ 0 }; + unsigned int m_ibo_id{ 0 }; // // Size of the data sent to gpu, in bytes. // diff --git a/src/libvgcode/src/Shaders.hpp b/src/libvgcode/src/Shaders.hpp index 1450d1a6df..596b42df5c 100644 --- a/src/libvgcode/src/Shaders.hpp +++ b/src/libvgcode/src/Shaders.hpp @@ -16,7 +16,8 @@ static const char* Segments_Vertex_Shader = "#define FIX_TWISTING\n" "const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" "const float light_top_diffuse = 0.6 * 0.8;\n" -"const float light_top_specular = 0.6 * 0.125;\n" +// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen. +"const float light_top_specular = 0.6 * 0.25;\n" "const float light_top_shininess = 20.0;\n" "const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" "const float light_front_diffuse = 0.6 * 0.2;\n" @@ -30,8 +31,19 @@ static const char* Segments_Vertex_Shader = "uniform samplerBuffer height_width_angle_tex;\n" "uniform samplerBuffer color_tex;\n" "uniform usamplerBuffer segment_index_tex;\n" +// ORCA: 0 during the shadow caster pass - the bias below shifts eye_position but not +// world_position, so the caster would write a depth the receiver never looks up. +"uniform float bias_scale;\n" "in int vertex_id;\n" "out vec3 color;\n" +"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n" +"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n" +"// single lighting term this replaces, so shading is unchanged while shadows are off.\n" +"out vec3 color_direct;\n" +"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n" +"// needs its world position and, for the depth bias, its eye space normal.\n" +"out vec3 world_position;\n" +"out vec3 shadow_normal;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -40,11 +52,11 @@ static const char* Segments_Vertex_Shader = " float f = 1.0 / 255.0f;\n" " return f * vec3(r, g, b);\n" "}\n" -"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n" " float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" " float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" " float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" -" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +" return top_diffuse + front_diffuse + top_specular;\n" "}\n" "void main() {\n" " int id_a = int(texelFetch(segment_index_tex, gl_InstanceID).r);\n" @@ -135,19 +147,63 @@ static const char* Segments_Vertex_Shader = " }\n" " vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n" " // ORCA: Apply bias to z-position to avoid z-fighting\n" -" eye_position.z += bias;\n" +" eye_position.z += bias * bias_scale;\n" " vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n" " vec3 color_base = decode_color(texelFetch(color_tex, id).r);\n" -" color = color_base * lighting(eye_position, eye_normal);\n" +" color = color_base * (ambient + emission);\n" +" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n" +" world_position = pos;\n" +" shadow_normal = eye_normal;\n" " gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" "}\n"; static const char* Segments_Fragment_Shader = "#version 150\n" +"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n" +"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n" +"// so the toolpaths shade exactly as before whenever realistic view is off.\n" +"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"uniform sampler2D shadow_map;\n" +"uniform mat4 shadow_light_vp;\n" +"uniform float shadow_intensity;\n" +"uniform float shadow_map_texel;\n" +// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO +// post pass - only takes more light away, so the print reads dimmer and duller than the legend +// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually +// passes in each mode is decided in GLCanvas3D::_render_gcode, not here. +"uniform float exposure;\n" +"uniform float saturation;\n" +"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n" "in vec3 color;\n" +"in vec3 color_direct;\n" +"in vec3 world_position;\n" +"in vec3 shadow_normal;\n" "out vec4 fragment_color;\n" +"float shadow_shade() {\n" +" if (shadow_intensity <= 0.0)\n" +" return 1.0;\n" +" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n" +" vec3 proj = lp.xyz / lp.w;\n" +" proj = proj * 0.5 + 0.5;\n" +" if (proj.z > 1.0)\n" +" return 1.0;\n" +" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n" +" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n" +" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n" +" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n" +" float sum = 0.0;\n" +" for (int x = -2; x <= 2; ++x) {\n" +" for (int y = -2; y <= 2; ++y) {\n" +" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n" +" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n" +" }\n" +" }\n" +" return 1.0 - shadow_intensity * (sum / 25.0);\n" +"}\n" "void main() {\n" -" fragment_color = vec4(color, 1.0);\n" +" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n" +" c = mix(vec3(dot(c, LUMA)), c, saturation);\n" +" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n" "}\n"; static const char* Options_Vertex_Shader = diff --git a/src/libvgcode/src/ShadersES.hpp b/src/libvgcode/src/ShadersES.hpp index e688fa7b4c..4194b836e3 100644 --- a/src/libvgcode/src/ShadersES.hpp +++ b/src/libvgcode/src/ShadersES.hpp @@ -17,7 +17,8 @@ static const char* Segments_Vertex_Shader_ES = "#define FIX_TWISTING\n" "const vec3 light_top_dir = vec3(-0.4574957, 0.4574957, 0.7624929);\n" "const float light_top_diffuse = 0.6 * 0.8;\n" -"const float light_top_specular = 0.6 * 0.125;\n" +// ORCA: the specular was 0.6 * 0.125, too faint to give the filament any sheen. +"const float light_top_specular = 0.6 * 0.25;\n" "const float light_top_shininess = 20.0;\n" "const vec3 light_front_dir = vec3(0.6985074, 0.1397015, 0.6985074);\n" "const float light_front_diffuse = 0.6 * 0.3;\n" @@ -33,6 +34,14 @@ static const char* Segments_Vertex_Shader_ES = "uniform usampler2D segment_index_tex;\n" "in float vertex_id_float;\n" "out vec3 color;\n" +"// ORCA: realistic view - the light the shadow map is able to block, kept apart from the\n" +"// ambient and emissive terms in color, which a shadow does not occlude. Their sum is the\n" +"// single lighting term this replaces, so shading is unchanged while shadows are off.\n" +"out vec3 color_direct;\n" +"// ORCA: realistic view - the fragment shader looks the fragment up in the shadow map, which\n" +"// needs its world position and, for the depth bias, its eye space normal.\n" +"out vec3 world_position;\n" +"out vec3 shadow_normal;\n" "vec3 decode_color(float color) {\n" " int c = int(round(color));\n" " int r = (c >> 16) & 0xFF;\n" @@ -41,11 +50,11 @@ static const char* Segments_Vertex_Shader_ES = " float f = 1.0 / 255.0f;\n" " return f * vec3(r, g, b);\n" "}\n" -"float lighting(vec3 eye_position, vec3 eye_normal) {\n" +"float direct_lighting(vec3 eye_position, vec3 eye_normal) {\n" " float top_diffuse = light_top_diffuse * max(dot(eye_normal, light_top_dir), 0.0);\n" " float front_diffuse = light_front_diffuse * max(dot(eye_normal, light_front_dir), 0.0);\n" " float top_specular = light_top_specular * pow(max(dot(-normalize(eye_position), reflect(-light_top_dir, eye_normal)), 0.0), light_top_shininess);\n" -" return ambient + top_diffuse + front_diffuse + top_specular + emission;\n" +" return top_diffuse + front_diffuse + top_specular;\n" "}\n" "ivec2 tex_coord(sampler2D sampler, int id) {\n" " ivec2 tex_size = textureSize(sampler, 0);\n" @@ -143,17 +152,64 @@ static const char* Segments_Vertex_Shader_ES = " vec3 eye_position = (view_matrix * vec4(pos, 1.0)).xyz;\n" " vec3 eye_normal = (view_matrix * vec4(normalize(pos - endpoint_pos), 0.0)).xyz;\n" " vec3 color_base = decode_color(texelFetch(color_tex, tex_coord(color_tex, id), 0).r);\n" -" color = color_base * lighting(eye_position, eye_normal);\n" +" color = color_base * (ambient + emission);\n" +" color_direct = color_base * direct_lighting(eye_position, eye_normal);\n" +" world_position = pos;\n" +" shadow_normal = eye_normal;\n" " gl_Position = projection_matrix * vec4(eye_position, 1.0);\n" "}\n"; static const char* Segments_Fragment_Shader_ES = "#version 300 es\n" "precision highp float;\n" +"// ORCA: sampler2D defaults to lowp in an ES fragment shader, far too coarse to compare\n" +"// shadow map depths against.\n" +"precision highp sampler2D;\n" +"// ORCA: realistic view - object-on-object and self shadows, read from the same depth map the\n" +"// rest of the 3D scene samples. shadow_intensity == 0, the default, short-circuits the lookup,\n" +"// so the toolpaths shade exactly as before whenever realistic view is off.\n" +"const vec3 SHADOW_LIGHT_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);\n" +"uniform sampler2D shadow_map;\n" +"uniform mat4 shadow_light_vp;\n" +"uniform float shadow_intensity;\n" +"uniform float shadow_map_texel;\n" +// ORCA: the lighting term peaks near 0.9 and every later multiplier - the shadow, then the SSAO +// post pass - only takes more light away, so the print reads dimmer and duller than the legend +// colours. These pay that back. Both are 1.0 for an untouched image; what the caller actually +// passes in each mode is decided in GLCanvas3D::_render_gcode, not here. +"uniform float exposure;\n" +"uniform float saturation;\n" +"const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n" "in vec3 color;\n" +"in vec3 color_direct;\n" +"in vec3 world_position;\n" +"in vec3 shadow_normal;\n" "out vec4 fragment_color;\n" +"float shadow_shade() {\n" +" if (shadow_intensity <= 0.0)\n" +" return 1.0;\n" +" vec4 lp = shadow_light_vp * vec4(world_position, 1.0);\n" +" vec3 proj = lp.xyz / lp.w;\n" +" proj = proj * 0.5 + 0.5;\n" +" if (proj.z > 1.0)\n" +" return 1.0;\n" +" // Slope-scaled bias, as in gouraud.fs. An extrusion is only a handful of shadow-map texels\n" +" // wide, so grazing faces need the larger bias to keep self-shadow acne off the top surfaces.\n" +" float NdotL = dot(normalize(shadow_normal), SHADOW_LIGHT_DIR);\n" +" float bias = mix(0.0004, 0.004, clamp(1.0 - NdotL, 0.0, 1.0));\n" +" float sum = 0.0;\n" +" for (int x = -2; x <= 2; ++x) {\n" +" for (int y = -2; y <= 2; ++y) {\n" +" float closest = texture(shadow_map, proj.xy + vec2(float(x), float(y)) * shadow_map_texel).r;\n" +" sum += (proj.z - bias > closest) ? 1.0 : 0.0;\n" +" }\n" +" }\n" +" return 1.0 - shadow_intensity * (sum / 25.0);\n" +"}\n" "void main() {\n" -" fragment_color = vec4(color, 1.0);\n" +" vec3 c = (color + color_direct * shadow_shade()) * exposure;\n" +" c = mix(vec3(dot(c, LUMA)), c, saturation);\n" +" fragment_color = vec4(clamp(c, 0.0, 1.0), 1.0);\n" "}\n"; static const char* Options_Vertex_Shader_ES = diff --git a/src/libvgcode/src/Viewer.cpp b/src/libvgcode/src/Viewer.cpp index eb606598e9..61d7310508 100644 --- a/src/libvgcode/src/Viewer.cpp +++ b/src/libvgcode/src/Viewer.cpp @@ -42,6 +42,21 @@ void Viewer::render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix) m_impl->render(view_matrix, projection_matrix); } +void Viewer::render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position) +{ + m_impl->render_shadow_casters(view_matrix, projection_matrix, light_position); +} + +void Viewer::set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size) +{ + m_impl->set_shadow_map(texture_unit, light_view_projection, intensity, texel_size); +} + +void Viewer::set_tone(float exposure, float saturation) +{ + m_impl->set_tone(exposure, saturation); +} + EViewType Viewer::get_view_type() const { return m_impl->get_view_type(); diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index da1a601149..b1a7550b42 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -763,6 +763,14 @@ void ViewerImpl::init(const std::string& opengl_context_version) m_uni_segments_height_width_angle_tex_id = glGetUniformLocation(m_segments_shader_id, "height_width_angle_tex"); m_uni_segments_colors_tex_id = glGetUniformLocation(m_segments_shader_id, "color_tex"); m_uni_segments_segment_index_tex_id = glGetUniformLocation(m_segments_shader_id, "segment_index_tex"); + // ORCA: realistic view + m_uni_segments_shadow_map_id = glGetUniformLocation(m_segments_shader_id, "shadow_map"); + m_uni_segments_shadow_light_vp_id = glGetUniformLocation(m_segments_shader_id, "shadow_light_vp"); + m_uni_segments_shadow_intensity_id = glGetUniformLocation(m_segments_shader_id, "shadow_intensity"); + m_uni_segments_shadow_map_texel_id = glGetUniformLocation(m_segments_shader_id, "shadow_map_texel"); + m_uni_segments_exposure_id = glGetUniformLocation(m_segments_shader_id, "exposure"); + m_uni_segments_saturation_id = glGetUniformLocation(m_segments_shader_id, "saturation"); + m_uni_segments_bias_scale_id = glGetUniformLocation(m_segments_shader_id, "bias_scale"); glcheck(); assert(m_uni_segments_view_matrix_id != -1 && m_uni_segments_projection_matrix_id != -1 && @@ -875,6 +883,12 @@ void ViewerImpl::reset() m_travels_time = { 0.0f, 0.0f }; m_vertices.clear(); m_vertices_colors.clear(); + // swap rather than clear: these are sized by the print, and a reset means the memory + // should go back, not sit reserved until the next load + for (std::vector& times : m_layer_start_times) + std::vector().swap(times); + std::vector().swap(m_layer_first_vertex); + std::vector().swap(m_colors_scratch); m_valid_lines_bitset.clear(); #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS m_cog_marker.reset(); @@ -1048,6 +1062,37 @@ void ViewerImpl::load(GCodeInputData&& gcode_data) v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast(v.layer_id)); } + // Index of the first vertex of each layer, walked back to front so that a layer with no + // vertex of its own inherits the next layer's index and the array stays non-decreasing. + if (!m_layers.empty()) { + const uint32_t vertices_count = static_cast(m_vertices.size()); + m_layer_first_vertex.assign(m_layers.count(), vertices_count); + for (uint32_t i = vertices_count; i > 0; --i) { + const uint32_t layer_id = m_vertices[i - 1].layer_id; + if (layer_id < m_layer_first_vertex.size()) + m_layer_first_vertex[layer_id] = i - 1; + } + for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i) + m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]); + + // the running time at each layer's first vertex, summed in vertex order so that + // get_estimated_time_at() matches a full accumulation exactly + std::array running{}; + for (std::vector& times : m_layer_start_times) + times.assign(m_layer_first_vertex.size(), 0.0f); + size_t layer = 0; + for (size_t i = 0; i <= m_vertices.size(); ++i) { + for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) { + for (size_t j = 0; j < TIME_MODES_COUNT; ++j) + m_layer_start_times[j][layer] = running[j]; + } + if (i < m_vertices.size()) { + for (size_t j = 0; j < TIME_MODES_COUNT; ++j) + running[j] += m_vertices[i].times[j]; + } + } + } + if (!m_layers.empty()) m_layers.set_view_range(0, static_cast(m_layers.count()) - 1); @@ -1261,7 +1306,10 @@ void ViewerImpl::update_colors_texture() // Based on current settings and slider position, we might want to render some // vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache). - std::vector colors(m_vertices_colors.size()); + // Reused across calls: this runs on every slider tick, and the allocation alone is + // 4 bytes per vertex of the whole print each time. + std::vector& colors = m_colors_scratch; + colors.resize(m_vertices_colors.size()); assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size()); for (size_t i=0; i(m_settings.time_mode)]; }); + const size_t mode = static_cast(m_settings.time_mode); + if (mode >= TIME_MODES_COUNT || id >= m_vertices.size()) + return 0.0f; + size_t first = 0; + float time = 0.0f; + const size_t layer = static_cast(m_vertices[id].layer_id); + if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) { + first = m_layer_first_vertex[layer]; + time = m_layer_start_times[mode][layer]; + } + for (size_t i = first; i <= id; ++i) + time += m_vertices[i].times[mode]; + return time; } Color ViewerImpl::get_vertex_color(const PathVertex& v) const @@ -1722,6 +1810,10 @@ size_t ViewerImpl::get_used_cpu_memory() const ret += sizeof(m_extrusion_roles_colors); ret += sizeof(m_options_colors); ret += STDVEC_MEMSIZE(m_vertices, PathVertex); + for (const std::vector& times : m_layer_start_times) + ret += STDVEC_MEMSIZE(times, float); + ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t); + ret += STDVEC_MEMSIZE(m_colors_scratch, float); ret += m_valid_lines_bitset.size_in_bytes_cpu(); ret += m_height_range.size_in_bytes_cpu(); ret += m_width_range.size_in_bytes_cpu(); @@ -1787,7 +1879,11 @@ void ViewerImpl::update_view_full_range() const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)]; const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)]; + // every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop + // below would skip all of them anyway auto first_it = m_vertices.begin(); + if (layers_range[0] < m_layer_first_vertex.size()) + first_it += m_layer_first_vertex[layers_range[0]]; while (first_it != m_vertices.end() && (first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) { ++first_it; @@ -1994,6 +2090,15 @@ void ViewerImpl::render_segments(const Mat4x4& view_matrix, const Mat4x4& projec glsafe(glUniformMatrix4fv(m_uni_segments_view_matrix_id, 1, GL_FALSE, view_matrix.data())); glsafe(glUniformMatrix4fv(m_uni_segments_projection_matrix_id, 1, GL_FALSE, projection_matrix.data())); glsafe(glUniform3fv(m_uni_segments_camera_position_id, 1, camera_position.data())); + // ORCA: realistic view. The depth pass writes the map it would otherwise read, so it shades + // with the lookup off. + glsafe(glUniform1i(m_uni_segments_shadow_map_id, m_shadow_map_texture_unit)); + glsafe(glUniformMatrix4fv(m_uni_segments_shadow_light_vp_id, 1, GL_FALSE, m_shadow_light_vp.data())); + glsafe(glUniform1f(m_uni_segments_shadow_intensity_id, m_rendering_shadow_casters ? 0.0f : m_shadow_intensity)); + glsafe(glUniform1f(m_uni_segments_shadow_map_texel_id, m_shadow_map_texel)); + glsafe(glUniform1f(m_uni_segments_exposure_id, m_exposure)); + glsafe(glUniform1f(m_uni_segments_saturation_id, m_saturation)); + glsafe(glUniform1f(m_uni_segments_bias_scale_id, m_rendering_shadow_casters ? 0.0f : 1.0f)); glsafe(glDisable(GL_CULL_FACE)); diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index 4da312fc0e..c9d658beb1 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -71,6 +71,24 @@ public: // Render the toolpaths // void render(const Mat4x4& view_matrix, const Mat4x4& projection_matrix); + // + // ORCA: realistic view. Render the toolpaths as seen from the light, to fill the caller's + // shadow map. Only depth matters here, so the caller masks colour writes; light_position + // takes the place of the camera when the segment boxes are expanded, which gives their + // silhouette as the light sees it. + // + void render_shadow_casters(const Mat4x4& view_matrix, const Mat4x4& projection_matrix, const Vec3& light_position); + // + // ORCA: realistic view. The shadow map the toolpaths sample, in the given texture unit. + // intensity == 0, the default, turns the lookup off and restores the plain shading. + // + void set_shadow_map(int texture_unit, const Mat4x4& light_view_projection, float intensity, float texel_size); + // + // ORCA: tone applied to the shaded toolpaths, to pay back the light the lighting term, + // the shadow and the SSAO pass each take off. 1.0/1.0, the default, is a no-op; the + // caller decides which of the two it varies with the realistic view setting. + // + void set_tone(float exposure, float saturation); EViewType get_view_type() const { return m_settings.view_type; } void set_view_type(EViewType type); @@ -234,6 +252,20 @@ private: // std::array m_total_time{ 0.0f, 0.0f }; // + // Running sum of the vertex estimated times at each layer's first vertex, for each time mode, + // so that get_estimated_time_at() only accumulates the vertices of one layer. + // + std::array, TIME_MODES_COUNT> m_layer_start_times; + // + // For each layer L, the index of the first vertex whose layer_id is >= L (m_vertices.size() + // if there is none). Derived from the vertices, so it stays exact whatever order they arrive in. + // + std::vector m_layer_first_vertex; + // + // Scratch buffer for update_colors_texture(), kept alive across slider steps + // + std::vector m_colors_scratch; + // // Detected travel moves times // std::array m_travels_time{ 0.0f, 0.0f }; @@ -330,6 +362,13 @@ private: int m_uni_segments_height_width_angle_tex_id{ -1 }; int m_uni_segments_colors_tex_id{ -1 }; int m_uni_segments_segment_index_tex_id{ -1 }; + int m_uni_segments_shadow_map_id{ -1 }; + int m_uni_segments_shadow_light_vp_id{ -1 }; + int m_uni_segments_shadow_intensity_id{ -1 }; + int m_uni_segments_shadow_map_texel_id{ -1 }; + int m_uni_segments_exposure_id{ -1 }; + int m_uni_segments_saturation_id{ -1 }; + int m_uni_segments_bias_scale_id{ -1 }; // // Caches for OpenGL uniforms id for options shader // @@ -469,6 +508,27 @@ private: size_t m_enabled_options_tex_size{ 0 }; #endif // ENABLE_OPENGL_ES + // + // ORCA: realistic view. Shadow map state set by set_shadow_map(), consumed by the segments + // shader. m_rendering_shadow_casters forces the intensity to 0 for the depth pass, which + // must not sample the very map it is writing. + // + // Defaults past the four texture units render_segments() binds itself, so the sampler never + // aliases one of the buffer textures before the owner of the map has said where it lives. + int m_shadow_map_texture_unit{ 4 }; + Mat4x4 m_shadow_light_vp{ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; + float m_shadow_intensity{ 0.0f }; + float m_shadow_map_texel{ 0.0f }; + bool m_rendering_shadow_casters{ false }; + + // + // ORCA: realistic view. Tone set by set_tone(), consumed by the segments shader. + // The identity values leave the shading as it is outside realistic view. + // + float m_exposure{ 1.0f }; + float m_saturation{ 1.0f }; + + void apply_pending_updates(); void update_view_full_range(); void update_color_ranges(); void update_heights_widths(); diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index feb6301df3..c03f0cfe59 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp" #include "libslic3r/Time.hpp" @@ -355,7 +356,10 @@ namespace Slic3r obj->bind_state = "free"; obj->last_alive = Slic3r::Utils::get_current_time_utc(); - obj->m_is_online = true; + // Route through set_online_state() (rather than writing m_is_online directly) so the + // DeviceOnline lifecycle event fires consistently; same effective value/behavior + // here since the object was already online in the common case. + obj->set_online_state(true); obj->set_dev_name(dev_name); /* if (!obj->dev_ip.empty()) { Slic3r::GUI::wxGetApp().app_config->set_str("ip_address", obj->dev_id, obj->dev_ip); @@ -373,6 +377,10 @@ namespace Slic3r obj->bind_sec_link = sec_link; obj->dev_connection_name = connection_name; obj->bind_ssdp_version = ssdp_version; + // Discovery establishes the initial reachability state. Do not report it as an + // online transition; DeviceDiscovered below is the lifecycle event for a new + // device. Subsequent updates route through set_online_state(), so a known device + // still emits DeviceOnline/DeviceOffline when its reachability actually changes. obj->m_is_online = true; //load access code @@ -389,6 +397,15 @@ namespace Slic3r BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " New Machine, dev_id= " << dev_id << ", ip = " << dev_ip <<", printer_name = " << dev_name << ", con_type= " << connect_type <<", signal= " << printer_signal << ", bind_state= " << bind_state; + + // First discovery of a genuinely new device (not a periodic SSDP/heartbeat update to + // an already-known one, which is handled in the branch above). + { + LifecycleEventContext ctx; + ctx.name = dev_id; + ctx.code = LifecycleEvtCode::Ok; + fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx); + } } update_local_machine(*obj); } @@ -984,8 +1001,14 @@ namespace Slic3r } void DeviceManager::OnSelectedMachineChanged(const std::string& /*pre_dev_id*/, - const std::string& /*new_dev_id*/) + const std::string& new_dev_id) { + { + LifecycleEventContext ctx; + ctx.name = new_dev_id; // empty string is a valid deselection + ctx.code = LifecycleEvtCode::Ok; + fire_lifecycle_event(LifecycleEvent::DeviceSelected, ctx); + } if (MachineObject* obj_ = get_selected_machine()) { GUI::wxGetApp().sidebar().update_sync_status(obj_); if(m_agent->get_filament_sync_mode() == FilamentSyncMode::subscription) diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index fd60f80d25..6bd690e9a6 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Time.hpp" #include "libslic3r/Thread.hpp" #include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp" #include "GuiColor.hpp" @@ -2626,7 +2627,15 @@ void MachineObject::reset() void MachineObject::set_print_state(std::string status) { + const bool changed = (print_status != status); print_status = status; + if (changed) { + LifecycleEventContext ctx; + ctx.name = dev_id; + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = print_status; + fire_lifecycle_event(LifecycleEvent::PrintStateChanged, ctx); + } } int MachineObject::connect(bool use_openssl) @@ -2648,7 +2657,10 @@ int MachineObject::connect(bool use_openssl) int MachineObject::disconnect() { if (m_agent) { - return m_agent->disconnect_printer(); + const int result = m_agent->disconnect_printer(); + if (result == 0) + set_online_state(false); + return result; } return -1; } @@ -2678,8 +2690,16 @@ bool MachineObject::is_connecting() void MachineObject::set_online_state(bool on_off) { + const bool changed = (m_is_online != on_off); m_is_online = on_off; if (!on_off) m_active_state = NotActive; + if (changed) { + LifecycleEventContext ctx; + ctx.name = dev_id; + ctx.code = LifecycleEvtCode::Ok; + ctx.msg = on_off ? "online" : "offline"; + fire_lifecycle_event(on_off ? LifecycleEvent::DeviceOnline : LifecycleEvent::DeviceOffline, ctx); + } } bool MachineObject::is_info_ready(bool check_version) const @@ -2806,13 +2826,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ parse_msg_count++; std::chrono::system_clock::time_point clock_start = std::chrono::system_clock::now(); - this->set_online_state(true); - - std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now(); - auto diff1 = std::chrono::duration_cast(curr_time - last_update_time); - - /* update last received time */ - last_update_time = std::chrono::system_clock::now(); json j_pre; bool parse_ok = false; @@ -2825,8 +2838,29 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ /* post process payload */ sanitizeToUtf8(payload); BOOST_LOG_TRIVIAL(info) << "parse_json: sanitize to utf8"; + try { + j_pre = json::parse(payload); + parse_ok = true; + } + catch (...) {} } + bool client_disconnected = false; + if (parse_ok && j_pre.is_object() && j_pre.contains("event") && j_pre["event"].is_object() && + j_pre["event"].contains("event") && j_pre["event"]["event"].is_string()) { + client_disconnected = j_pre["event"]["event"].get() == "client.disconnected"; + } + + // A disconnect notification is a transport message too, but it must not first mark an + // already-offline device as online through the generic message-received path. + set_online_state(!client_disconnected); + + std::chrono::system_clock::time_point curr_time = std::chrono::system_clock::now(); + auto diff1 = std::chrono::duration_cast(curr_time - last_update_time); + + /* update last received time */ + last_update_time = std::chrono::system_clock::now(); + try { bool restored_json = false; json j; @@ -4597,19 +4631,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ } } - // event info - try { - if (j.contains("event")) { - if (j["event"].contains("event")) { - if (j["event"]["event"].get() == "client.disconnected") - set_online_state(false); - else if (j["event"]["event"].get() == "client.connected") - set_online_state(true); - } - } - } - catch (...) {} - if (!key_field_only) { BOOST_LOG_TRIVIAL(trace) << "parse_json m_active_state =" << m_active_state; parse_state_changed_event(); diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 914c8f7868..ca3b7c3c44 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -897,6 +897,7 @@ public: bool is_connected(); bool is_connecting(); + // Emits DeviceOnline or DeviceOffline only when the reachability state changes. void set_online_state(bool on_off); bool is_online() { return m_is_online; } bool is_info_ready(bool check_version = true) const; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3b3354d86f..68e81493c1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -104,6 +104,31 @@ static std::string get_view_type_string(libvgcode::EViewType view_type) return ""; } +// ORCA: Stable, locale independent names used to persist a view type in the application config. +// Keep these in sync with the entries of libvgcode::EViewType exposed in the preview combo box. +static const std::vector>& view_type_config_map() +{ + static const std::vector> map = { + { "summary", libvgcode::EViewType::Summary }, + { "feature_type", libvgcode::EViewType::FeatureType }, + { "color_print", libvgcode::EViewType::ColorPrint }, + { "speed", libvgcode::EViewType::Speed }, + { "actual_speed", libvgcode::EViewType::ActualSpeed }, + { "acceleration", libvgcode::EViewType::Acceleration }, + { "jerk", libvgcode::EViewType::Jerk }, + { "height", libvgcode::EViewType::Height }, + { "width", libvgcode::EViewType::Width }, + { "volumetric_flow_rate", libvgcode::EViewType::VolumetricFlowRate }, + { "actual_volumetric_flow_rate", libvgcode::EViewType::ActualVolumetricFlowRate }, + { "layer_time_linear", libvgcode::EViewType::LayerTimeLinear }, + { "layer_time_logarithmic", libvgcode::EViewType::LayerTimeLogarithmic }, + { "fan_speed", libvgcode::EViewType::FanSpeed }, + { "temperature", libvgcode::EViewType::Temperature }, + { "pressure_advance", libvgcode::EViewType::PressureAdvance }, + }; + return map; +} + // Find an index of a value in a sorted vector, which is in . // Returns -1 if there is no such member. static int find_close_layer_idx(const std::vector &zs, double &z, double eps) @@ -1091,9 +1116,7 @@ void GCodeViewer::init(ConfigOptionMode mode, PresetBundle* preset_bundle) // Default view type at first slice. // May be overridden in load() once we know how many tools are actually used in the G-code. m_nozzle_nums = preset_bundle ? preset_bundle->get_printer_extruder_count() : 1; - auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType); - m_view_type_sel = (it != view_type_items.end()) ? std::distance(view_type_items.begin(), it) : 0; - set_view_type(libvgcode::EViewType::FeatureType); + apply_default_view_type(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finished"); } @@ -1114,6 +1137,73 @@ void GCodeViewer::set_scale(float scale) } } +// ORCA: Preview default view type preference, see "preview_default_view_type" in the application config. +std::string GCodeViewer::view_type_to_config_name(libvgcode::EViewType type) +{ + for (const auto& [name, value] : view_type_config_map()) { + if (value == type) + return name; + } + return std::string(); +} + +bool GCodeViewer::view_type_from_config_name(const std::string& name, libvgcode::EViewType& type) +{ + for (const auto& [config_name, value] : view_type_config_map()) { + if (config_name == name) { + type = value; + return true; + } + } + return false; +} + +std::vector> GCodeViewer::default_view_type_choices() +{ + std::vector> choices = { + { "auto", _u8L("Automatic") }, + { "last", _u8L("Last used") }, + }; + for (const auto& [name, type] : view_type_config_map()) + choices.push_back({ name, get_view_type_string(type) }); + return choices; +} + +void GCodeViewer::select_view_type(libvgcode::EViewType type) +{ + auto it = std::find(view_type_items.begin(), view_type_items.end(), type); + m_view_type_sel = (it != view_type_items.end()) ? static_cast(std::distance(view_type_items.begin(), it)) : 0; + set_view_type(type); +} + +// ORCA: Pick the view type the preview opens with, following the "preview_default_view_type" preference: +// a fixed view type, the one the user picked last ("last"), or the automatic choice ("auto", the default) +// which shows Filament for multi material prints and Line Type for single material ones. +// The default is only (re)applied when it actually changes, so a view type picked by hand survives a reslice. +void GCodeViewer::apply_default_view_type() +{ + const std::string preference = wxGetApp().app_config->get("preview_default_view_type"); + + std::string key = preference; + libvgcode::EViewType type = libvgcode::EViewType::FeatureType; + if (preference == "last") { + if (!view_type_from_config_name(wxGetApp().app_config->get("preview_last_view_type"), type)) + type = libvgcode::EViewType::FeatureType; + } + else if (!view_type_from_config_name(preference, type)) { + // "auto", or an unknown value written by a newer version + const bool multi_material = m_viewer.get_used_extruders_count() > 1; + type = multi_material ? libvgcode::EViewType::ColorPrint : libvgcode::EViewType::FeatureType; + key = multi_material ? "auto_multi_material" : "auto_single_material"; + } + + if (m_applied_default_view_type_key == key) + return; + + m_applied_default_view_type_key = key; + select_view_type(type); +} + void GCodeViewer::update_by_mode(ConfigOptionMode mode) { view_type_items.clear(); @@ -1395,27 +1485,8 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const // load_toolpaths(gcode_result, build_volume, exclude_bounding_box); - // ORCA: Apply smart default view type when extruder count changes. - // Multi-color: ColorPrint (Filament), Single-color: FeatureType (Line Type). - // User selections persist within same extruder count, defaults reapply on count change. - int current_count = m_viewer.get_used_extruders_count(); - if (current_count > 1) { - if (m_last_extruder_count_default_applied != 2) { - auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::ColorPrint); - if (it != view_type_items.end()) - m_view_type_sel = std::distance(view_type_items.begin(), it); - set_view_type(libvgcode::EViewType::ColorPrint); - m_last_extruder_count_default_applied = 2; - } - } else { - if (m_last_extruder_count_default_applied != 1) { - auto it = std::find(view_type_items.begin(), view_type_items.end(), libvgcode::EViewType::FeatureType); - if (it != view_type_items.end()) - m_view_type_sel = std::distance(view_type_items.begin(), it); - set_view_type(libvgcode::EViewType::FeatureType); - m_last_extruder_count_default_applied = 1; - } - } + // ORCA: Apply the default view type now that we know how many tools the G-code actually uses. + apply_default_view_type(); // BBS: data for rendering color arrangement recommendation m_nozzle_nums = print.config().option("nozzle_diameter")->values.size(); @@ -1627,7 +1698,7 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height) glsafe(::glEnable(GL_DEPTH_TEST)); render_shells(canvas_width, canvas_height); - if (m_viewer.get_extrusion_roles().empty()) + if (m_viewer.get_extrusion_roles_count() == 0) return; render_toolpaths(); @@ -1641,6 +1712,29 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height) m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type()); } +void GCodeViewer::render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position) +{ + if (!has_data()) + return; + + m_viewer.render_shadow_casters( + libvgcode::convert(static_cast(light_view_matrix.matrix().cast())), + libvgcode::convert(static_cast(light_projection_matrix.matrix().cast())), + libvgcode::convert(static_cast(light_position.cast()))); +} + +void GCodeViewer::set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size) +{ + m_viewer.set_shadow_map(texture_unit, + libvgcode::convert(static_cast(light_view_projection.matrix().cast())), + intensity, texel_size); +} + +void GCodeViewer::set_tone(float exposure, float saturation) +{ + m_viewer.set_tone(exposure, saturation); +} + void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin) { if (m_viewer.get_extrusion_roles().empty()) @@ -3426,6 +3520,12 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); + // Loop invariant, but built lazily: this lambda runs once per extruder on every frame + // and most prints reach neither colour change below, so fetching it up front would cost + // more than the per-item fetch it replaces. + std::vector zs; + bool zs_built = false; + for (const auto& item : custom_gcode_per_print_z) { if (extruder_id + 1 != static_cast(item.extruder)) continue; @@ -3433,7 +3533,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv if (item.type != ColorChange) continue; - const std::vector zs = m_viewer.get_layers_zs(); + if (!zs_built) { + zs = m_viewer.get_layers_zs(); + zs_built = true; + } auto lower_b = std::lower_bound(zs.begin(), zs.end(), static_cast(item.print_z - epsilon())); if (lower_b == zs.end()) @@ -3558,6 +3661,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv m_view_type_sel = i; set_view_type(view_type_items[m_view_type_sel]); reset_visible(view_type_items[m_view_type_sel]); + // ORCA: remember the pick so the "Last used" preview default can restore it + const std::string view_type_name = view_type_to_config_name(view_type_items[m_view_type_sel]); + if (!view_type_name.empty()) + wxGetApp().app_config->set("preview_last_view_type", view_type_name); update_moves_slider(); #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT imgui.set_requires_extra_frame(); @@ -4582,6 +4689,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv // ORCA: Get layer Zs as doubles std::vector layer_zs = get_layers_zs(); + // loop invariant, same reason as the layer Zs above + const std::vector layer_times = m_viewer.get_layers_estimated_times(); for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) { ImGui::Dummy({window_padding, window_padding}); @@ -4601,7 +4710,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.text(buf); ImGui::SameLine(max_len * 1.5); - std::vector layer_times = m_viewer.get_layers_estimated_times(); float custom_gcode_time = 0; if (layer > 0) { @@ -4650,7 +4758,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::string print_str = _u8L("Model printing time"); std::string total_str = _u8L("Total time"); float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x; - if (m_viewer.get_layers_estimated_times().empty()) + if (m_viewer.get_layers_count() == 0) max_len += ImGui::CalcTextSize(total_str.c_str()).x; else { if (m_viewer.get_view_type() == libvgcode::EViewType::FeatureType) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 9b82a81b2f..f4ebe2e81d 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -228,7 +228,8 @@ private: std::vector view_type_items; std::vector view_type_items_str; int m_view_type_sel = 0; - int m_last_extruder_count_default_applied{0}; // 0=unset, 1=single, 2+=multi + // ORCA: which default view type was last applied, see apply_default_view_type(). Empty until the first one is applied. + std::string m_applied_default_view_type_key; std::vector options_items; bool m_legend_visible{ true }; @@ -279,6 +280,13 @@ public: void render_scene(int canvas_width, int canvas_height); // Legend, sliders, the marker's position window and the G-code window, all ImGui. void render_overlay(int canvas_width, int canvas_height, int right_margin); + // ORCA: realistic view. Depth-only pass drawing the toolpaths as the light sees them, into + // the shadow map the caller has bound, and the map they sample back in render_scene. + void render_shadow_casters(const Transform3d& light_view_matrix, const Transform3d& light_projection_matrix, const Vec3d& light_position); + void set_shadow_map(int texture_unit, const Transform3d& light_view_projection, float intensity, float texel_size); + // ORCA: tone applied to the shaded toolpaths, paying back the light the lighting term, + // the shadow and the SSAO pass each take off. 1.0/1.0 is a no-op. + void set_tone(float exposure, float saturation); //BBS // void _render_calibration_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); // void _render_calibration_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, OpenGLManager& opengl_manager); @@ -324,6 +332,16 @@ public: void set_view_type(libvgcode::EViewType type) { m_viewer.set_view_type(type); } + // ORCA: select a view type in the preview combo box and apply it + void select_view_type(libvgcode::EViewType type); + // ORCA: apply the "preview_default_view_type" preference, see the definition for the supported values + void apply_default_view_type(); + + // ORCA: stable, locale independent name of a view type, as stored in the application config + static std::string view_type_to_config_name(libvgcode::EViewType type); + static bool view_type_from_config_name(const std::string& name, libvgcode::EViewType& type); + // ORCA: (config value, translated label) pairs for the "preview_default_view_type" preference combo box + static std::vector> default_view_type_choices(); void reset_visible(libvgcode::EViewType type) { if (type == libvgcode::EViewType::FeatureType) { auto roles = m_viewer.get_extrusion_roles(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 190eeedbb4..887d13aedc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -43,6 +43,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/MacDarkMode.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include @@ -2199,8 +2200,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size) // Recorded by PartPlate::render_icons() below, when it runs. wxGetApp().plater()->get_partplate_list().clear_hover_tooltip(); glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - // Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps - // the Preview / Assemble canvases from sampling a stale map with an outdated light matrix. + // Invalidate the shadow map each frame; the View3D and Preview paths below rebuild it. This + // keeps the Assemble canvas from sampling a stale map with an outdated light matrix. m_shadow_map_valid = false; _render_background(); @@ -2251,6 +2252,8 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size) _render_selection(); _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes); _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, true, hover_id); + // Realistic view: the print casts a shadow onto the plate here as it does in View3D. + _render_shadows(camera.get_view_matrix(), camera.get_projection_matrix()); // BBS: GUI refactor: add canvas size as parameters _render_gcode(cnv_size.get_width(), cnv_size.get_height()); } @@ -5026,8 +5029,16 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); - if (object_moved) + if (object_moved) { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "moved"; + if (done.size() == 1) + ctx.name = m_model->objects[done.begin()->first]->name; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); + } // BBS: support wipe-tower for multi-plates for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) { @@ -5148,8 +5159,16 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) //BBS: nofity object list to update wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items(); - if (!done.empty()) + if (!done.empty()) { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "rotated"; + if (done.size() == 1) + ctx.name = m_model->objects[done.begin()->first]->name; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + } m_dirty = true; } @@ -5240,8 +5259,16 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) //BBS: notify object info update wxGetApp().plater()->show_object_info(); - if (!done.empty()) + if (!done.empty()) { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "scaled"; + if (done.size() == 1) + ctx.name = m_model->objects[done.begin()->first]->name; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); + } m_dirty = true; } @@ -7644,11 +7671,20 @@ bool GLCanvas3D::_is_fxaa_enabled() const return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_FXAA_ENABLED); } +bool GLCanvas3D::_is_realistic_view_enabled() const +{ + const AppConfig* cfg = wxGetApp().app_config; + if (cfg == nullptr || !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE)) + return false; + // Prepare and Assemble follow the umbrella toggle alone; Preview needs its own opt-in. + return m_canvas_type != ECanvasType::CanvasPreview || cfg->get_bool(SETTING_OPENGL_REALISTIC_PREVIEW); +} + bool GLCanvas3D::_is_ssao_enabled() const { if (wxGetApp().app_config == nullptr) return false; - return wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE) && + return _is_realistic_view_enabled() && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO); } @@ -7803,95 +7839,23 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height) const Camera& camera = wxGetApp().plater()->get_camera(); - GLint prev_stencil_mask = 0xFF; - glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask)); - GLboolean prev_stencil_test = GL_FALSE; - glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test)); - GLboolean prev_depth_mask = GL_TRUE; - glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask)); - GLint prev_depth_func = GL_LESS; - glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func)); - glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); - // Build stencil mask for bed/plate and apply SSAO only outside this mask. - glsafe(::glEnable(GL_STENCIL_TEST)); - glsafe(::glStencilMask(0xFF)); - glsafe(::glClearStencil(0)); - glsafe(::glClear(GL_STENCIL_BUFFER_BIT)); - glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF)); - glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)); - // Mark only visible plate pixels (do not exclude objects in front of plate). - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - glsafe(::glDepthFunc(GL_LEQUAL)); - - GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE }; - glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask)); - glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); - - if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) { - GLShaderProgram* flat = wxGetApp().get_shader("flat"); - if (flat != nullptr) { - flat->start_using(); - flat->set_uniform("projection_matrix", camera.get_projection_matrix()); - - GLModel plate_mask; - GLModel::Geometry mask; - mask.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - - if (build_volume.type() == BuildVolume_Type::Rectangle) { - const BoundingBox3Base bb = build_volume.bounding_volume(); - mask.reserve_vertices(4); - mask.reserve_indices(6); - mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.min.y(), 0.0f)); - mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.min.y(), 0.0f)); - mask.add_vertex(Vec3f((float)bb.max.x(), (float)bb.max.y(), 0.0f)); - mask.add_vertex(Vec3f((float)bb.min.x(), (float)bb.max.y(), 0.0f)); - mask.add_triangle(0, 1, 2); - mask.add_triangle(0, 2, 3); - } else if (build_volume.type() == BuildVolume_Type::Circle) { - const Vec2f c = Vec2f(unscaled(build_volume.circle().center.x()), unscaled(build_volume.circle().center.y())); - const float r = unscaled(build_volume.circle().radius); - const int segments = 64; - mask.reserve_vertices(segments + 1); - mask.reserve_indices(segments * 3); - mask.add_vertex(Vec3f(c.x(), c.y(), 0.0f)); - for (int i = 0; i < segments; ++i) { - const float a = (2.0f * float(PI) * float(i)) / float(segments); - mask.add_vertex(Vec3f(c.x() + r * std::cos(a), c.y() + r * std::sin(a), 0.0f)); - } - for (int i = 0; i < segments; ++i) { - const unsigned int i1 = 1 + i; - const unsigned int i2 = 1 + ((i + 1) % segments); - mask.add_triangle(0, i1, i2); - } - } - - if (mask.vertices_count() > 0 && mask.indices_count() > 0) { - plate_mask.init_from(std::move(mask)); - flat->set_uniform("view_model_matrix", camera.get_view_matrix()); - plate_mask.render(flat); - } - flat->stop_using(); - } - } - - glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glStencilMask(0x00)); - glsafe(::glStencilFunc(GL_NOTEQUAL, 1, 0xFF)); - glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)); - shader->start_using(); shader->set_uniform("view_model_matrix", Transform3d::Identity()); shader->set_uniform("projection_matrix", Transform3d::Identity()); shader->set_uniform("color_texture", 0); shader->set_uniform("depth_texture", 1); shader->set_uniform("inv_tex_size", Vec2f(1.0f / static_cast(width), 1.0f / static_cast(height))); - shader->set_uniform("z_near", camera.get_near_z()); shader->set_uniform("z_far", camera.get_far_z()); + // The shader reconstructs the surface normal from the depth buffer, there being no normal + // target to read: it unprojects a pixel back into view space, then measures the result + // against world +Z expressed in view space to tell a top surface from a wall. + const Matrix4d inv_projection_matrix = camera.get_projection_matrix().matrix().inverse(); + shader->set_uniform("inv_projection_matrix", inv_projection_matrix); + const Vec3d up_view = (camera.get_view_matrix().matrix().block<3, 3>(0, 0) * Vec3d::UnitZ()).normalized(); + shader->set_uniform("up_view", up_view); glsafe(::glActiveTexture(GL_TEXTURE0)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_ssao_color_texture_id)); @@ -7903,13 +7867,6 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height) glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); shader->stop_using(); - if (!prev_stencil_test) - glsafe(::glDisable(GL_STENCIL_TEST)); - glsafe(::glStencilMask(prev_stencil_mask)); - glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3])); - - glsafe(::glDepthMask(prev_depth_mask)); - glsafe(::glDepthFunc(prev_depth_func)); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -8154,15 +8111,19 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform { if (wxGetApp().app_config == nullptr) return; - if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE)) + if (!_is_realistic_view_enabled()) return; if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS)) return; - if (m_volumes.empty()) - return; - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) + // The preview canvas holds no volumes of its own for FFF. Once slicing has run its printed + // geometry is the G-code toolpaths, which both cast into the map here and sample it back in + // _render_gcode; before slicing there are only shells, and nothing casts at all. View3D and + // SLA preview use m_volumes. The shells are deliberately never casters: they are a + // translucent ghost of the whole object, so they would drop the solid shadow of a print that + // has not been sliced, and at any layer below the last, one that is not there yet. + const bool toolpath_casters = m_canvas_type == ECanvasType::CanvasPreview && m_gcode_viewer.has_data(); + if (!toolpath_casters && m_volumes.empty()) return; if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) { @@ -8174,10 +8135,30 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform // Bounding box of the printable objects (the shadow casters). BoundingBoxf3 obj_bb; - for (const GLVolume* volume : m_volumes.volumes) { - if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) - continue; - obj_bb.merge(volume->transformed_bounding_box()); + if (toolpath_casters) { + // Merged corner by corner: BoundingBoxf3(min, max) marks itself undefined at zero + // Z extent, which a single layer print gives, and the check below would then drop + // every shadow in the frame. + const BoundingBoxf3& paths_bb = m_gcode_viewer.get_paths_bounding_box(); + if ((paths_bb.min.array() <= paths_bb.max.array()).all()) { + obj_bb.merge(paths_bb.min); + obj_bb.merge(paths_bb.max); + } + // Only the enabled layers are drawn, so fitting the map to the whole print wastes + // its depth range and makes contact shadows shift as the slider moves. The z = 0 + // shadow is enclosed separately below, so the plate shadow is unaffected. + const std::vector layer_zs = m_gcode_viewer.get_layers_zs(); + if (!layer_zs.empty()) { + const size_t top = std::min(m_gcode_viewer.get_layers_z_range()[1], layer_zs.size() - 1); + obj_bb.max.z() = std::max(obj_bb.min.z(), std::min(obj_bb.max.z(), layer_zs[top])); + } + } + else { + for (const GLVolume* volume : m_volumes.volumes) { + if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) + continue; + obj_bb.merge(volume->transformed_bounding_box()); + } } if (!obj_bb.defined) return; // no objects to cast shadows @@ -8299,16 +8280,21 @@ void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform glsafe(::glPolygonOffset(4.0f, 4.0f)); glsafe(::glDisable(GL_CULL_FACE)); - shader->start_using(); - shader->set_uniform("projection_matrix", Transform3d(light_proj)); - for (GLVolume* volume : m_volumes.volumes) { - if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) - continue; - const Transform3d view_model = Transform3d(light_view) * volume->world_matrix(); - shader->set_uniform("view_model_matrix", view_model); - volume->model.render(shader); + if (toolpath_casters) + m_gcode_viewer.render_shadow_casters(Transform3d(light_view), Transform3d(light_proj), eye); + // Only this branch draws through "flat"; the toolpaths bring their own program. + else if (GLShaderProgram* shader = wxGetApp().get_shader("flat"); shader != nullptr) { + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d(light_proj)); + for (GLVolume* volume : m_volumes.volumes) { + if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower) + continue; + const Transform3d view_model = Transform3d(light_view) * volume->world_matrix(); + shader->set_uniform("view_model_matrix", view_model); + volume->model.render(shader); + } + shader->stop_using(); } - shader->stop_using(); // Restore state glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); @@ -8517,7 +8503,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with return; } - const bool realistic_mode = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE); + const bool realistic_mode = _is_realistic_view_enabled(); const bool realistic_phong = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_PHONG); const std::string shader_name = (realistic_mode && realistic_phong) ? "phong" : "gouraud"; GLShaderProgram* shader = wxGetApp().get_shader(shader_name); @@ -8741,7 +8727,34 @@ void GLCanvas3D::_render_wireframe_overlay() //BBS: GUI refactor: add canvas size as parameters void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) { + // Realistic view: the toolpaths receive the same depth map they were rendered into by + // _render_shadows, which is what gives them object-on-object and self shadows. Intensity 0 + // short-circuits the lookup in the shader, so this is inert whenever the map is missing. + const bool receive_shadows = m_shadow_map_valid && m_shadow_map_texture_id != 0 && m_shadow_map_size != 0; + if (receive_shadows) { + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + m_gcode_viewer.set_shadow_map(4, m_shadow_light_vp, 0.35f, 1.0f / static_cast(m_shadow_map_size)); + } + else + m_gcode_viewer.set_shadow_map(4, Transform3d::Identity(), 0.0f, 0.0f); + + // The lighting term leaves the print dimmer and duller than the legend colours. Saturation + // pays back the duller half in both modes; brightness only where something takes light off + // again - realistic view with at least one lossy pass on - else the lift would just clip. + const AppConfig* cfg = wxGetApp().app_config; + const bool lossy_passes = cfg != nullptr && _is_realistic_view_enabled() && + (cfg->get_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS) || cfg->get_bool(SETTING_OPENGL_PHONG_SSAO)); + m_gcode_viewer.set_tone(lossy_passes ? 1.1f : 1.0f, 1.15f); + m_gcode_viewer.render_scene(canvas_width, canvas_height); + + if (receive_shadows) { + glsafe(::glActiveTexture(GL_TEXTURE4)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); + } } void GLCanvas3D::_render_gcode_overlay(int canvas_width, int canvas_height) @@ -9831,7 +9844,7 @@ void GLCanvas3D::_render_canvas_toolbar() ); create_menu_item( _utf8(L("Realistic View")), - m_canvas_type != ECanvasType::CanvasPreview, // not work on preview + true, // work on all cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE), [&cfg]{ cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE)); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ad5188f2f3..75820694b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1324,6 +1324,7 @@ private: void _picking_pass(); void _rectangular_selection_picking_pass(); bool _is_fxaa_enabled() const; + bool _is_realistic_view_enabled() const; bool _is_ssao_enabled() const; int _get_effective_fps_cap() const; bool _is_fps_overlay_enabled() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 48bd58b1a5..29bab18aeb 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2182,6 +2182,8 @@ void GUI_App::init_networking_callbacks() obj->command_get_access_code(); if (m_agent) m_agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer()); + + obj->set_online_state(true); } }); }); @@ -2220,6 +2222,8 @@ void GUI_App::init_networking_callbacks() obj->command_get_version(); event.SetInt(0); event.SetString(obj->get_dev_id()); + + obj->set_online_state(true); } else if (state == ConnectStatus::ConnectStatusFailed) { // Orca: only update status if same device id if (m_device_manager->selected_machine != dev_id) return; @@ -2235,10 +2239,14 @@ void GUI_App::init_networking_callbacks() wxGetApp().show_dialog(text); } event.SetInt(-1); + + obj->set_online_state(false); } else if (state == ConnectStatus::ConnectStatusLost) { m_device_manager->set_selected_machine(""); event.SetInt(-1); BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = lost"; + + obj->set_online_state(false); } else { event.SetInt(-1); BOOST_LOG_TRIVIAL(info) << "set_on_local_connect_fn: state = " << state; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2d77200eef..5220e026c7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,5 +1,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "GUI_ObjectList.hpp" #include "GUI_Factories.hpp" //#include "GUI_ObjectLayers.hpp" @@ -10,6 +11,7 @@ #include "BitmapComboBox.hpp" #include "MainFrame.hpp" #include "slic3r/Utils/UndoRedo.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -1159,17 +1161,39 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const if (m_objects_model->GetItemType(item) & itObject) { std::string name = m_objects_model->GetName(item).ToUTF8().data(); if (obj->name != name) { + const std::string previous_name = obj->name; obj->name = name; // if object has just one volume, rename this volume too if (obj->volumes.size() == 1) obj->volumes[0]->name = obj->name; Slic3r::save_object_mesh(*obj); + + LifecycleEventContext ctx; + ctx.name = name; + ctx.previous_name = previous_name; + ctx.id = std::to_string(obj->id().id); + ctx.index = obj_idx; + ctx.source = "object"; + fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx); } return; } if (volume_id < 0) return; - obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); + std::string name = m_objects_model->GetName(item).ToUTF8().data(); + if (obj->volumes[volume_id]->name == name) + return; + + const std::string previous_name = obj->volumes[volume_id]->name; + obj->volumes[volume_id]->name = name; + + LifecycleEventContext ctx; + ctx.name = name; + ctx.previous_name = previous_name; + ctx.id = std::to_string(obj->volumes[volume_id]->id().id); + ctx.index = volume_id; + ctx.source = "volume"; + fire_lifecycle_event(LifecycleEvent::ObjectRenamed, ctx); } void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const @@ -3519,7 +3543,14 @@ void ObjectList::delete_all_connectors_for_object(int obj_idx) obj->delete_connectors(); if (obj->volumes.empty() || !obj->has_solid_mesh()) { + const std::string deleted_obj_name = obj->name; model.delete_object(idx); + { + Slic3r::LifecycleEventContext ctx; + ctx.name = deleted_obj_name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx); + } m_objects_model->Delete(m_objects_model->GetItemById(idx)); continue; } @@ -4088,6 +4119,13 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut()); Expand(m_objects_model->GetParent(item)); + { + Slic3r::LifecycleEventContext ctx; + ctx.name = model_object->name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectAdded, ctx); + } + if (!do_info_update) return; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index c0cc7bdb5a..438bda2cc2 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -13,6 +13,7 @@ #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "libnest2d/common.hpp" @@ -698,6 +699,13 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; } + { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "arranged"; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + } + m_plater->update(); // BBS //wxGetApp().obj_manipul()->set_dirty(); diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 5364cb5648..961b8af566 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "libnest2d/common.hpp" #include @@ -347,6 +348,13 @@ void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr) m_plater->arrange(); } m_plater->update(); + + { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "arranged"; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + } } m_plater->mark_plate_toolbar_image_dirty(); diff --git a/src/slic3r/GUI/Jobs/OrientJob.cpp b/src/slic3r/GUI/Jobs/OrientJob.cpp index ee8ea875c0..ed9117e8bf 100644 --- a/src/slic3r/GUI/Jobs/OrientJob.cpp +++ b/src/slic3r/GUI/Jobs/OrientJob.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "libslic3r/PresetBundle.hpp" @@ -254,6 +255,12 @@ void OrientJob::finalize(bool canceled, std::exception_ptr &eptr) mesh.apply(); } + if (!m_selected.empty()) { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + ctx.msg = "auto_oriented"; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectTransformed, ctx); + } m_plater->update(); diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index dae05494e3..76e45eb1d7 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -1,4 +1,5 @@ #include "PrintJob.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -152,6 +153,18 @@ void PrintJob::process(Ctl &ctl) int result = -1; std::string http_body; + const auto mark_lifecycle_started = [this]() { + if (m_lifecycle_started) + return; + + LifecycleEventContext start_ctx; + start_ctx.name = m_project_name; + start_ctx.device_id = m_dev_id; + start_ctx.source = "print_job"; + fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx); + m_lifecycle_started = true; + }; + int total_plate_num = plate_data.plate_count; if (!plate_data.is_valid) { total_plate_num = m_plater->get_partplate_list().get_plate_count(); @@ -173,7 +186,7 @@ void PrintJob::process(Ctl &ctl) } } - m_project_name = truncate_string(m_project_name, 100); + const std::string transport_project_name = truncate_string(m_project_name, 100); int curr_plate_idx = 0; if (m_print_type == "from_normal") { @@ -372,11 +385,11 @@ void PrintJob::process(Ctl &ctl) } } - if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", m_project_name, curr_plate_idx).ToStdString(); } - if (params.project_name.empty()) {params.project_name = m_project_name;} + if (params.preset_name.empty() && m_print_type == "from_normal") { params.preset_name = wxString::Format("%s_plate_%d", transport_project_name, curr_plate_idx).ToStdString(); } + if (params.project_name.empty()) {params.project_name = transport_project_name;} if (m_is_calibration_task) { - params.project_name = m_project_name; + params.project_name = transport_project_name; params.origin_model_id = ""; } @@ -543,6 +556,7 @@ void PrintJob::process(Ctl &ctl) if (m_print_type == "from_sdcard_view") { BOOST_LOG_TRIVIAL(info) << "print_job: try to send with cloud, model is sdcard view"; ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); + mark_lifecycle_started(); result = m_agent->start_sdcard_print(params, update_fn, cancel_fn); } else if (params.connection_type != "lan") { if (params.dev_ip.empty()) @@ -566,6 +580,7 @@ void PrintJob::process(Ctl &ctl) BOOST_LOG_TRIVIAL(info) << "print_job: use ftp send print only"; ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); is_try_lan_mode = true; + mark_lifecycle_started(); result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn); if (result < 0) { error_text = wxString::Format(_L("Access code:%s IP address:%s"), params.password, params.dev_ip); @@ -582,6 +597,7 @@ void PrintJob::process(Ctl &ctl) // try to send local with record BOOST_LOG_TRIVIAL(info) << "print_job: try to start local print with record"; ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); + mark_lifecycle_started(); result = m_agent->start_local_print_with_record(params, update_fn, cancel_fn, wait_fn); if (result == 0) { params.comments = ""; @@ -597,18 +613,21 @@ void PrintJob::process(Ctl &ctl) // try to send with cloud BOOST_LOG_TRIVIAL(warning) << "print_job: try to send with cloud"; ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); + // Started was already emitted before the local attempt. result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn); } } else { BOOST_LOG_TRIVIAL(info) << "print_job: send with cloud"; ctl.update_status(curr_percent, _u8L("Sending print job through cloud service")); + mark_lifecycle_started(); result = m_agent->start_print(params, update_fn, cancel_fn, wait_fn); } } } else { if (this->could_emmc_print) { ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); + mark_lifecycle_started(); result = m_agent->start_local_print(params, update_fn, cancel_fn); } else { switch(this->sdcard_state) { @@ -619,6 +638,7 @@ void PrintJob::process(Ctl &ctl) if(this->has_sdcard) { // means the storage is abnormal but can be used option is enabled ctl.update_status(curr_percent, _u8L("Sending print job over LAN, but the Storage in the printer is abnormal and print-issues may be caused by this.")); + mark_lifecycle_started(); result = m_agent->start_local_print(params, update_fn, cancel_fn); break; } @@ -629,6 +649,7 @@ void PrintJob::process(Ctl &ctl) return; case DevStorage::SdcardState::HAS_SDCARD_NORMAL: ctl.update_status(curr_percent, _u8L("Sending print job over LAN")); + mark_lifecycle_started(); result = m_agent->start_local_print(params, update_fn, cancel_fn); break; default: @@ -686,6 +707,7 @@ void PrintJob::process(Ctl &ctl) } wxQueueEvent(m_plater, evt); m_job_finished = true; + m_lifecycle_success = true; } } @@ -698,6 +720,19 @@ void PrintJob::finalize(bool canceled, std::exception_ptr &eptr) { eptr = std::current_exception(); } + if (m_lifecycle_started && !m_lifecycle_finished) { + LifecycleEventContext finish_ctx; + finish_ctx.name = m_project_name; + finish_ctx.device_id = m_dev_id; + finish_ctx.source = "print_job"; + finish_ctx.code = canceled ? LifecycleEvtCode::Warn : + (eptr || !m_lifecycle_success ? LifecycleEvtCode::Error : LifecycleEvtCode::Ok); + finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" : + (m_lifecycle_success ? "" : "failed")); + fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx); + m_lifecycle_finished = true; + } + if (canceled || eptr) return; } diff --git a/src/slic3r/GUI/Jobs/PrintJob.hpp b/src/slic3r/GUI/Jobs/PrintJob.hpp index 9a23b04054..4be68031d9 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.hpp +++ b/src/slic3r/GUI/Jobs/PrintJob.hpp @@ -43,6 +43,9 @@ class PrintJob : public Job std::function m_success_fun{nullptr}; std::string m_dev_id; bool m_job_finished{ false }; + bool m_lifecycle_started{ false }; + bool m_lifecycle_finished{ false }; + bool m_lifecycle_success{ false }; int m_print_job_completed_id = 0; wxString m_completed_evt_data; std::function m_enter_ip_address_fun_fail{ nullptr }; diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index d27b18f24b..505475e4a9 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -1,4 +1,5 @@ #include "SendJob.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "libslic3r/MTUtils.hpp" @@ -148,6 +149,13 @@ void SendJob::process(Ctl &ctl) } } + LifecycleEventContext start_ctx; + start_ctx.name = m_project_name; + start_ctx.device_id = m_dev_id; + start_ctx.source = "send_job"; + fire_lifecycle_event(LifecycleEvent::SendJobStarted, start_ctx); + m_lifecycle_started = true; + int total_plate_num = m_plater->get_partplate_list().get_plate_count(); PartPlate* plate = m_plater->get_partplate_list().get_plate(job_data.plate_idx); @@ -424,6 +432,19 @@ void SendJob::finalize(bool canceled, std::exception_ptr &eptr) eptr = std::current_exception(); } + if (m_lifecycle_started && !m_lifecycle_finished) { + LifecycleEventContext finish_ctx; + finish_ctx.name = m_project_name; + finish_ctx.device_id = m_dev_id; + finish_ctx.source = "send_job"; + finish_ctx.code = canceled ? LifecycleEvtCode::Warn : + (eptr ? LifecycleEvtCode::Error : (m_job_finished ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error)); + finish_ctx.msg = canceled ? "cancelled" : (eptr ? "exception" : + (m_job_finished ? "" : "failed")); + fire_lifecycle_event(LifecycleEvent::SendJobFinished, finish_ctx); + m_lifecycle_finished = true; + } + if (canceled || eptr) return; } diff --git a/src/slic3r/GUI/Jobs/SendJob.hpp b/src/slic3r/GUI/Jobs/SendJob.hpp index 1d4e025d6e..86d180ac8c 100644 --- a/src/slic3r/GUI/Jobs/SendJob.hpp +++ b/src/slic3r/GUI/Jobs/SendJob.hpp @@ -22,6 +22,8 @@ class SendJob : public Job PrintPrepareData job_data; std::string m_dev_id; bool m_job_finished{ false }; + bool m_lifecycle_started{ false }; + bool m_lifecycle_finished{ false }; int m_print_job_completed_id = 0; bool m_is_check_mode{false}; bool m_check_and_continue{false}; diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 428f45c845..3493506228 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -29,6 +29,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "I18N.hpp" #include "GUI_App.hpp" @@ -2626,11 +2627,20 @@ void PartPlate::set_plate_name(const std::string& name) if (boost::equals(m_name, name)) return; + const std::string previous_name = m_name; m_name = name; if (m_print != nullptr) m_print->set_plate_name(name); invalidate_plate_name_texture(); + + if (m_plater != nullptr && !m_plater->is_loading_project()) { + LifecycleEventContext ctx; + ctx.name = name; + ctx.previous_name = previous_name; + ctx.index = m_plate_index; + fire_lifecycle_event(LifecycleEvent::PlateRenamed, ctx); + } } //get the print's object, result and index @@ -4741,9 +4751,16 @@ int PartPlateList::create_plate(bool adjust_position) if (m_plater) { // In GUI mode - wxGetApp().obj_list()->on_plate_added(plate); + wxGetApp().obj_list()->on_plate_added(plate); } + if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) { + LifecycleEventContext ctx; + ctx.name = plate->get_plate_name(); + ctx.index = new_index; + fire_lifecycle_event(LifecycleEvent::PlateCreated, ctx); + } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":created a new plate %1%") % new_index; return new_index; } @@ -4842,6 +4859,7 @@ int PartPlateList::delete_plate(int index) BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(":plate %1%, has an invalid index %2%") % index % plate->get_index(); return -1; } + const std::string plate_name = plate->get_plate_name(); if (m_plater) { // In GUI mode @@ -4924,6 +4942,13 @@ int PartPlateList::delete_plate(int index) delete plate; + if (m_plater != nullptr && m_intialized && !m_plater->is_loading_project()) { + LifecycleEventContext ctx; + ctx.name = plate_name; + ctx.index = index; + fire_lifecycle_event(LifecycleEvent::PlateDeleted, ctx); + } + // FIX: context of BackgroundSliceProcess and gcode preview need to be updated before ObjectList::reload_all_plates(). #if 0 if (m_plater != nullptr) { @@ -5032,6 +5057,7 @@ int PartPlateList::select_plate(int index) if (m_plate_list.empty() || index >= m_plate_list.size()) { return -1; } + const int previous_index = m_current_plate; // BBS: erase unnecessary snapshot if (get_curr_plate_index() != index && m_intialized) { @@ -5058,6 +5084,13 @@ int PartPlateList::select_plate(int index) //wxQueueEvent(m_plater, new SimpleEvent(EVT_GLCANVAS_PLATE_SELECT)); } + if (previous_index != index && m_intialized && m_plater != nullptr && !m_plater->is_loading_project()) { + LifecycleEventContext ctx; + ctx.name = m_plate_list[index]->get_plate_name(); + ctx.index = index; + fire_lifecycle_event(LifecycleEvent::PlateSelected, ctx); + } + return 0; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4a58c4548..c5d28919c7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,6 +57,7 @@ #include #include "libslic3r/libslic3r.h" +#include "libslic3r/LifecycleEvents.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/DRC.hpp" #include "libslic3r/Format/STEP.hpp" @@ -10271,7 +10272,14 @@ void Plater::priv::remove(size_t obj_idx) view3D->enable_layers_editing(false); m_worker.cancel_all(); + std::string obj_name = (obj_idx < model.objects.size()) ? model.objects[obj_idx]->name : std::to_string(obj_idx); model.delete_object(obj_idx); + { + Slic3r::LifecycleEventContext ctx; + ctx.name = obj_name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx); + } //BBS: notify partplate the instance removed partplate_list.notify_instance_removed(obj_idx, -1); update(); @@ -10304,7 +10312,14 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx, bool refresh_immedia if (obj->is_cut()) sidebar->obj_list()->invalidate_cut_info_for_object(obj_idx); + std::string obj_name = obj->name; model.delete_object(obj_idx); + { + Slic3r::LifecycleEventContext ctx; + ctx.name = obj_name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ObjectDeleted, ctx); + } //BBS: notify partplate the instance removed partplate_list.notify_instance_removed(obj_idx, -1); @@ -10350,10 +10365,22 @@ void Plater::priv::delete_all_objects_from_model() void Plater::priv::reset(bool apply_presets_change) { + // TakeSnapshot below and load_current_presets() further down each re-evaluate the + // aggregate dirty flag against a baseline that hasn't been reset yet, so they can toggle + // is_dirty() back and forth several times before it settles; coalesce those into one event. + ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(dirty_state); + Plater::TakeSnapshot snapshot(q, _u8L("Reset Project"), UndoRedo::SnapshotType::ProjectSeparator); clear_warnings(); + const std::string closed_project_name = into_u8(get_project_filename()); + if (!closed_project_name.empty() || !model.objects.empty()) { + Slic3r::LifecycleEventContext ctx; + ctx.name = closed_project_name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectClosed, ctx); + } // A new project must not inherit the previous project's published selection (Feature A/B). m_has_pending_published = false; m_pending_published_keys.clear(); @@ -12781,11 +12808,15 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) notification_manager->set_slicing_progress_export_possible(); // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. + const std::string lifecycle_job_name = this->background_process.fff_print() ? + this->background_process.fff_print()->output_filename() : std::string(); this->background_process.reset_export(); // This bool stops showing export finished notification even when process_completed_with_error is false bool has_error = false; + std::string lifecycle_error_msg; if (evt.error()) { auto message = evt.format_error_message(); + lifecycle_error_msg = message.first; if (evt.critical_error()) { if (q->m_tracking_popup_menu) { // We don't want to pop-up a message box when tracking a pop-up menu. @@ -12823,6 +12854,14 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) is_finished = true; } + { + Slic3r::LifecycleEventContext ctx; + ctx.name = lifecycle_job_name; + ctx.code = evt.cancelled() ? Slic3r::LifecycleEvtCode::Warn : (has_error ? Slic3r::LifecycleEvtCode::Error : Slic3r::LifecycleEvtCode::Ok); + ctx.msg = evt.cancelled() ? "cancelled" : (has_error ? lifecycle_error_msg : std::string()); + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::SlicingJobComplete, ctx); + } + //BBS: set the current plater's slice result to valid if (!this->background_process.empty()) this->background_process.get_current_plate()->update_slice_result_valid_state(evt.success()); @@ -15356,21 +15395,33 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_ //get_partplate_list().reinit(); //get_partplate_list().update_slice_context_to_current_plate(p->background_process); //p->preview->update_gcode_result(p->partplate_list.get_current_slice_result()); - reset(transfer_preset_changes); - reset_project_dirty_after_save(); - reset_project_dirty_initial_presets(); - wxGetApp().update_saved_preset_from_current_preset(); - update_project_dirty_from_presets(); + if (!silent) { + Slic3r::LifecycleEventContext ctx; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::NewProject, ctx); + } + { + // Same rationale as in Plater::priv::reset(): the whole reset + preset-reload + + // baseline-reset sequence below settles into its final dirty state only once it + // completes, so hold notifications until then to avoid firing on transient flips. + ProjectDirtyStateManager::NotificationSuppressor dirty_notify_suppressor(p->dirty_state); - //reset project - p->project.reset(); - //set project name - if (project_name.empty()) - p->set_project_name(_L("Untitled")); - else - p->set_project_name(project_name); + reset(transfer_preset_changes); + reset_project_dirty_after_save(); + reset_project_dirty_initial_presets(); + wxGetApp().update_saved_preset_from_current_preset(); + update_project_dirty_from_presets(); - Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator); + //reset project + p->project.reset(); + //set project name + if (project_name.empty()) + p->set_project_name(_L("Untitled")); + else + p->set_project_name(project_name); + + Plater::TakeSnapshot snapshot(this, "New Project", UndoRedo::SnapshotType::ProjectSeparator); + } Model m; model().load_from(m); // new id avoid same path name @@ -15488,6 +15539,13 @@ void Plater::load_project(wxString const& filename2, p->set_project_name(_L("Untitled")); } + { + Slic3r::LifecycleEventContext ctx; + ctx.name = into_u8(load_restore ? originfile : filename); + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectOpened, ctx); + } + } else { if (using_exported_file()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " using ecported set project filename: " << filename; @@ -15559,11 +15617,23 @@ int Plater::save_project(bool saveAs) if (full_pathnames) { save_strategy = save_strategy | SaveStrategy::FullPathSources; } + { + Slic3r::LifecycleEventContext ctx; + ctx.name = into_u8(filename); + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectBeforeSave, ctx); + } if (export_3mf(into_path(filename), save_strategy) < 0) { MessageDialog(this, _L("Failed to save the project.\nPlease check whether the folder exists online or if other programs have the project file open."), _L("Save project"), wxOK | wxICON_WARNING).ShowModal(); return wxID_CANCEL; } + { + Slic3r::LifecycleEventContext ctx; + ctx.name = into_u8(filename); + ctx.code = Slic3r::LifecycleEvtCode::Ok; + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::ProjectAfterSave, ctx); + } Slic3r::remove_backup(model(), false); @@ -20896,6 +20966,14 @@ void Plater::changed_object(ModelObject &object){ // Check outside bed get_current_canvas3D()->requires_check_outside_state(); + + if (!is_loading_project()) { + LifecycleEventContext ctx; + ctx.name = object.name; + ctx.id = std::to_string(object.id().id); + ctx.source = "geometry"; + fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx); + } } void Plater::changed_object(int obj_idx) @@ -20932,6 +21010,20 @@ void Plater::changed_objects(const std::vector& object_idxs) // update print this->p->schedule_background_process(); + + if (!is_loading_project()) { + for (size_t obj_idx : object_idxs) { + if (obj_idx >= p->model.objects.size() || p->model.objects[obj_idx] == nullptr) + continue; + + LifecycleEventContext ctx; + ctx.name = p->model.objects[obj_idx]->name; + ctx.id = std::to_string(p->model.objects[obj_idx]->id().id); + ctx.index = static_cast(obj_idx); + ctx.source = "geometry"; + fire_lifecycle_event(LifecycleEvent::ObjectChanged, ctx); + } + } } void Plater::schedule_background_process(bool schedule/* = true*/) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index be4bfcfa63..001866bfd9 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1932,6 +1932,15 @@ void PreferencesDialog::create_items() ); g_sizer->Add(item_realistic_phong); + auto item_realistic_preview = create_item_checkbox( + _L("Enable in Preview"), + _L("Also applies realistic view to the Preview canvas, not just Prepare.\n" + "Preview draws the full toolpath geometry, so shadows and SSAO cost considerably" + " more there than on a plain model."), + SETTING_OPENGL_REALISTIC_PREVIEW + ); + g_sizer->Add(item_realistic_preview); + auto item_realistic_ssao = create_item_checkbox( _L("SSAO ambient occlusion"), _L("Applies SSAO in realistic view."), @@ -2019,6 +2028,23 @@ void PreferencesDialog::create_items() //// GRAPHICS > G-code Preview g_sizer->Add(create_item_title(_L("G-code Preview")), 1, wxEXPAND); + // ORCA: view type the preview opens with + std::vector PreviewViewTypeLabels; + std::vector PreviewViewTypeValues; + for (const auto& [value, label] : GCodeViewer::default_view_type_choices()) { + PreviewViewTypeValues.push_back(value); + PreviewViewTypeLabels.push_back(from_u8(label)); + } + auto item_preview_view_type = create_item_combobox( + _L("Default view type"), + _L("The color scheme the sliced preview opens with.\n" + "Automatic: Filament for multi material prints, Line Type for single material ones.\n" + "Last used: the view type you selected last.\n" + "Any other value always opens that view type.\n" + "You can still switch the view type in the preview afterwards."), + "preview_default_view_type", PreviewViewTypeLabels, PreviewViewTypeValues); + g_sizer->Add(item_preview_view_type); + auto item_dim_previous_layers = create_item_checkbox( _L("Dim lower layers"), _L("When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."), diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index c986328f71..53eb3cd353 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -6,6 +6,7 @@ #include "MainFrame.hpp" #include "I18N.hpp" #include "Plater.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include @@ -17,13 +18,23 @@ namespace GUI { void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty) { + const bool was_dirty = is_dirty(); m_plater_dirty = dirty; + notify_dirty_change(was_dirty, "undo_redo"); if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized()) wxGetApp().mainframe->update_title(); } +void ProjectDirtyStateManager::set_plater_dirty(bool is_dirty) +{ + const bool was_dirty = this->is_dirty(); + m_plater_dirty = is_dirty; + notify_dirty_change(was_dirty, "plater"); +} + void ProjectDirtyStateManager::update_from_presets() { + const bool was_dirty = is_dirty(); m_presets_dirty = false; // check switching of the presets only for exist/loaded project, but not for new GUI_App &app = wxGetApp(); @@ -45,18 +56,53 @@ void ProjectDirtyStateManager::update_from_presets() } m_presets_dirty |= app.has_unsaved_preset_changes(); m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config; + notify_dirty_change(was_dirty, "presets"); app.mainframe->update_title(); } void ProjectDirtyStateManager::reset_after_save() { + const bool was_dirty = is_dirty(); this->reset_initial_presets(); m_plater_dirty = false; m_presets_dirty = false; m_project_config_dirty = false; + notify_dirty_change(was_dirty, "save"); wxGetApp().mainframe->update_title(); } +void ProjectDirtyStateManager::notify_dirty_change(bool was_dirty, const char *source) +{ + if (m_suppress_depth > 0) + return; + + const bool dirty = is_dirty(); + if (was_dirty == dirty) + return; + + LifecycleEventContext ctx; + ctx.code = LifecycleEvtCode::Ok; + ctx.dirty = dirty; + ctx.source = source; + fire_lifecycle_event(LifecycleEvent::ProjectDirtyChanged, ctx); +} + +void ProjectDirtyStateManager::begin_suppress_notifications() +{ + if (m_suppress_depth == 0) + m_suppress_entry_dirty = is_dirty(); + ++m_suppress_depth; +} + +void ProjectDirtyStateManager::end_suppress_notifications() +{ + assert(m_suppress_depth > 0); + if (m_suppress_depth > 0) + --m_suppress_depth; + if (m_suppress_depth == 0) + notify_dirty_change(m_suppress_entry_dirty, "batch"); +} + void ProjectDirtyStateManager::reset_initial_presets() { m_initial_presets.fill(std::string{}); @@ -168,4 +214,3 @@ void ProjectDirtyStateManager::render_debug_window() const } // namespace GUI } // namespace Slic3r - diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 171d19e219..fd121d1d85 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -14,21 +14,42 @@ public: void reset_after_save(); void reset_initial_presets(); - void set_plater_dirty(bool is_dirty) { m_plater_dirty = is_dirty; } + void set_plater_dirty(bool is_dirty); bool is_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; } bool is_presets_dirty() const { return m_presets_dirty; } + // RAII guard coalescing dirty-state updates: while any guard is alive, ProjectDirtyChanged + // notifications are held back; when the outermost guard is destroyed, at most one + // notification fires, reflecting only the net change across the whole guarded scope. + class NotificationSuppressor + { + public: + explicit NotificationSuppressor(ProjectDirtyStateManager &owner) : m_owner(owner) { m_owner.begin_suppress_notifications(); } + ~NotificationSuppressor() { m_owner.end_suppress_notifications(); } + NotificationSuppressor(const NotificationSuppressor &) = delete; + NotificationSuppressor &operator=(const NotificationSuppressor &) = delete; + private: + ProjectDirtyStateManager &m_owner; + }; + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW private: + void notify_dirty_change(bool was_dirty, const char *source); + void begin_suppress_notifications(); + void end_suppress_notifications(); + // Does the Undo / Redo stack indicate the project is dirty? bool m_plater_dirty { false }; // Do the presets indicate the project is dirty? bool m_presets_dirty { false }; // Is the project config dirty? bool m_project_config_dirty { false }; + // NotificationSuppressor nesting depth and the dirty state observed when the outermost guard began. + int m_suppress_depth { 0 }; + bool m_suppress_entry_dirty { false }; // Keeps track of preset names selected at the time of last project save. std::array m_initial_presets; DynamicPrintConfig m_initial_project_config; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f6c1cbf901..1312d0e11d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -38,6 +38,7 @@ #include "slic3r/Utils/NetworkAgentFactory.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/plugin/PluginConfig.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" @@ -6964,6 +6965,20 @@ bool Tab::select_preset( } load_current_preset(); + { + Slic3r::LifecycleEventContext ctx; + ctx.name = preset_name; + ctx.code = Slic3r::LifecycleEvtCode::Ok; + switch (m_type) { + case Preset::TYPE_PRINT: ctx.msg = "print"; break; + case Preset::TYPE_SLA_PRINT: ctx.msg = "sla_print"; break; + case Preset::TYPE_FILAMENT: ctx.msg = "filament"; break; + case Preset::TYPE_SLA_MATERIAL: ctx.msg = "sla_material"; break; + case Preset::TYPE_PRINTER: ctx.msg = "printer"; break; + default: break; + } + Slic3r::fire_lifecycle_event(Slic3r::LifecycleEvent::PresetSelected, ctx); + } if (delete_third_printer) { wxGetApp().CallAfter([filament_presets, process_presets]() { diff --git a/src/slic3r/GUI/TaskManager.cpp b/src/slic3r/GUI/TaskManager.cpp index d294d140c5..64f3e5d6a5 100644 --- a/src/slic3r/GUI/TaskManager.cpp +++ b/src/slic3r/GUI/TaskManager.cpp @@ -1,10 +1,13 @@ #include "TaskManager.hpp" #include "libslic3r/Thread.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "nlohmann/json.hpp" #include "MainFrame.hpp" #include "GUI_App.hpp" +#include + using namespace nlohmann; namespace Slic3r { @@ -214,17 +217,34 @@ int TaskManager::schedule(TaskStateInfo* task) boost::thread* new_sending_thread = new boost::thread(); *new_sending_thread = Slic3r::create_thread( [this, task] { + // Keep both lifecycle callbacks on this per-task worker thread. Plugin observers can + // therefore associate Started and Finished for one task with a single execution context. + LifecycleEventContext start_ctx; + start_ctx.name = task->params().project_name; + start_ctx.device_id = task->params().dev_id; + start_ctx.job_id = std::to_string(task->task_info_id); + start_ctx.source = "task_manager"; + fire_lifecycle_event(LifecycleEvent::PrintJobStarted, start_ctx); + + int result = -1; if (!m_agent) { BOOST_LOG_TRIVIAL(trace) << "task_manager: NetworkAgent is nullptr"; - return; } - assert(m_agent); + else { + assert(m_agent); + try { // DEBUG FOR TEST #if 0 - int result = start_print_test(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn); + result = start_print_test(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn); #else - int result = m_agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn); + result = m_agent->start_print(task->get_params(), task->update_status_fn, task->cancel_fn, task->wait_fn); #endif + } catch (const std::exception& ex) { + BOOST_LOG_TRIVIAL(error) << "task_manager: start_print threw: " << ex.what(); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "task_manager: start_print threw an unknown exception"; + } + } if (result == 0) { last_sent_timestamp = std::chrono::system_clock::now(); task->set_sent_time(last_sent_timestamp); @@ -237,6 +257,16 @@ int TaskManager::schedule(TaskStateInfo* task) task->set_state(TaskState::TS_SEND_CANCELED); } } + + LifecycleEventContext finish_ctx; + finish_ctx.name = task->params().project_name; + finish_ctx.device_id = task->params().dev_id; + finish_ctx.job_id = std::to_string(task->task_info_id); + finish_ctx.source = "task_manager"; + finish_ctx.code = result == 0 ? LifecycleEvtCode::Ok : + (task->is_canceled() ? LifecycleEvtCode::Warn : LifecycleEvtCode::Error); + finish_ctx.msg = result == 0 ? "" : (task->is_canceled() ? "cancelled" : "failed"); + fire_lifecycle_event(LifecycleEvent::PrintJobFinished, finish_ctx); /* remove from sending task list */ m_scedule_mutex.lock(); diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 37e0a41f83..04709fef4c 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -24,6 +24,7 @@ #include "CrealityPrint.hpp" #include "../GUI/PrintHostDialogs.hpp" #include "../GUI/MainFrame.hpp" +#include "slic3r/plugin/PluginManager.hpp" #include "Obico.hpp" #include "Flashforge.hpp" #include "SimplyPrint.hpp" @@ -360,12 +361,29 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job) { emit_progress(0); // Indicate the upload is starting + // Captured before upload_data is moved into upload() below. + const std::string upload_filename = the_job.upload_data.source_path.filename().string(); + + { + LifecycleEventContext ctx; + ctx.name = upload_filename; + ctx.code = LifecycleEvtCode::Ok; + fire_lifecycle_event(LifecycleEvent::UploadStarted, ctx); + } + bool success = the_job.printhost->upload(std::move(the_job.upload_data), [this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); }, [this](wxString error) { this->error_fn(std::move(error)); }, [this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); } ); + { + LifecycleEventContext ctx; + ctx.name = upload_filename; + ctx.code = success ? LifecycleEvtCode::Ok : LifecycleEvtCode::Error; + fire_lifecycle_event(LifecycleEvent::UploadFinished, ctx); + } + if (success) { emit_progress(100); if (the_job.switch_to_device_tab) { diff --git a/src/slic3r/plugin/PluginHooks.cpp b/src/slic3r/plugin/PluginHooks.cpp index c6aaaf803c..2d84df5fdb 100644 --- a/src/slic3r/plugin/PluginHooks.cpp +++ b/src/slic3r/plugin/PluginHooks.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/Exception.hpp" +#include "libslic3r/LifecycleEvents.hpp" #include "libslic3r/Print.hpp" #include "libslic3r_version.h" @@ -46,6 +47,16 @@ void install_capability_resolver() }); } +// Global libslic3r-side seam (Slic3r::fire_lifecycle_event, in libslic3r/LifecycleEvents.hpp): +// broadcasts to every loaded, enabled capability regardless of type, unlike the SlicingPipeline +// hook below which only targets picker-selected SlicingPipeline capabilities. +void install_lifecycle_event_hook() +{ + set_lifecycle_hook_fn([](LifecycleEvent event, const LifecycleEventContext& ctx) { + PluginManager::instance().dispatch_lifecycle_event(event, ctx); + }); +} + // Print::process() fires this hook at each pipeline seam on the slicing worker // thread; here we run the picker-selected SlicingPipeline capabilities. Per // capability we acquire the GIL, honor cancellation, and convert a plugin @@ -122,12 +133,14 @@ void install() { install_capability_resolver(); install_slicing_pipeline_hook(); + install_lifecycle_event_hook(); } void uninstall() { ConfigBase::set_resolve_capability_fn(nullptr); Print::set_slicing_pipeline_hook_fn(nullptr); + set_lifecycle_hook_fn(nullptr); } } // namespace Slic3r::plugin_hooks diff --git a/src/slic3r/plugin/PluginHooks.hpp b/src/slic3r/plugin/PluginHooks.hpp index bff276c5df..5eeacfa449 100644 --- a/src/slic3r/plugin/PluginHooks.hpp +++ b/src/slic3r/plugin/PluginHooks.hpp @@ -14,8 +14,9 @@ namespace Slic3r::plugin_hooks { void install(); // Reset every hook to null so none can enter Python after the interpreter -// finalizes. Called from PluginManager::shutdown(); callers must have stopped -// background slicing first (resetting a hook while process() runs is a race). +// finalizes. The lifecycle-event hook drains callbacks already in progress +// before returning. Other hooks retain their existing caller-side shutdown +// requirements. void uninstall(); } // namespace Slic3r::plugin_hooks diff --git a/src/slic3r/plugin/PluginManager.cpp b/src/slic3r/plugin/PluginManager.cpp index 57bce7d2f2..022f85eb1f 100644 --- a/src/slic3r/plugin/PluginManager.cpp +++ b/src/slic3r/plugin/PluginManager.cpp @@ -1,5 +1,6 @@ #include "PluginManager.hpp" +#include #include #include #include @@ -134,7 +135,8 @@ void PluginManager::shutdown() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": PluginManager shutdown enter"; // Detach the libslic3r hooks first so nothing dispatches into Python while (or after) plugins - // unload. Callers stop background slicing before this. + // unload. The lifecycle-event hook also drains callbacks already in progress before returning; + // the remaining hook seams retain their existing shutdown requirements. plugin_hooks::uninstall(); // Reject new plugin loads before we drain. @@ -2089,4 +2091,33 @@ ExecutionResult PluginManager::run_script_capability(const std::string& plugin_k return result; } +void PluginManager::dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx) { + const auto is_canceled = [&ctx]() { + return ctx.cancellation_check && ctx.cancellation_check(); + }; + + if (is_canceled()) + return; + + for (const auto& cap : get_plugin_capabilities()) { + if (!cap || !cap->is_enabled()) continue; + if (is_canceled()) + break; + try { + cap->on_lifecycle_event(evt, ctx); + } catch (const std::exception& ex) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name() + << "' on_lifecycle_event(" << lifecycle_event_to_string(evt) << ") threw: " << ex.what() + << " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code) + << ", msg='" << ctx.msg << "']"; + } catch (...) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin '" << cap->audit_plugin_key() << "/" << cap->name() + << "' on_lifecycle_event(" << lifecycle_event_to_string(evt) + << ") threw a non-standard exception" + << " [ctx name='" << ctx.name << "', code=" << lifecycle_evt_code_to_string(ctx.code) + << ", msg='" << ctx.msg << "']"; + } + } +} + } // namespace Slic3r diff --git a/src/slic3r/plugin/PluginManager.hpp b/src/slic3r/plugin/PluginManager.hpp index e1fedaff94..891f787bf2 100644 --- a/src/slic3r/plugin/PluginManager.hpp +++ b/src/slic3r/plugin/PluginManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,6 @@ #include #include "CloudPluginService.hpp" -#include "PluginFsUtils.hpp" #include "PluginDescriptor.hpp" #include "PluginLoader.hpp" #include "PluginConfig.hpp" @@ -210,6 +210,8 @@ public: ExecutionResult run_script_capability(const std::string& plugin_key, const std::string& capability_name, std::string& error); + void dispatch_lifecycle_event(LifecycleEvent evt, const LifecycleEventContext& ctx); + private: PluginManager() = default; PluginManager(const PluginManager&) = delete; diff --git a/src/slic3r/plugin/PyPluginTrampoline.hpp b/src/slic3r/plugin/PyPluginTrampoline.hpp index ad7fe25f10..4c77f1ba9c 100644 --- a/src/slic3r/plugin/PyPluginTrampoline.hpp +++ b/src/slic3r/plugin/PyPluginTrampoline.hpp @@ -132,6 +132,11 @@ public: { ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled); } + + void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) override + { + ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_lifecycle_event, event, ctx); + } }; class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline diff --git a/src/slic3r/plugin/PythonPluginBridge.cpp b/src/slic3r/plugin/PythonPluginBridge.cpp index 11bb6159cc..a88695005c 100644 --- a/src/slic3r/plugin/PythonPluginBridge.cpp +++ b/src/slic3r/plugin/PythonPluginBridge.cpp @@ -377,6 +377,61 @@ void bind_python_api(pybind11::module_& m) .value("FatalError", PluginResult::FatalError) .export_values(); + py::enum_(m, "LifecycleEvent", "Application lifecycle moment passed to on_lifecycle_event") + .value("NewProject", LifecycleEvent::NewProject) + .value("ProjectOpened", LifecycleEvent::ProjectOpened) + .value("ProjectBeforeSave", LifecycleEvent::ProjectBeforeSave) + .value("ProjectAfterSave", LifecycleEvent::ProjectAfterSave) + .value("ProjectClosed", LifecycleEvent::ProjectClosed) + .value("ProjectDirtyChanged", LifecycleEvent::ProjectDirtyChanged) + .value("SliceStarted", LifecycleEvent::SliceStarted) + .value("SliceGeometryFinished", LifecycleEvent::SliceGeometryFinished) + .value("GCodeExportStarted", LifecycleEvent::GCodeExportStarted) + .value("GCodeExportFinished", LifecycleEvent::GCodeExportFinished) + .value("SlicingJobComplete", LifecycleEvent::SlicingJobComplete) + .value("ObjectAdded", LifecycleEvent::ObjectAdded) + .value("ObjectDeleted", LifecycleEvent::ObjectDeleted) + .value("ObjectTransformed", LifecycleEvent::ObjectTransformed) + .value("ObjectChanged", LifecycleEvent::ObjectChanged) + .value("ObjectRenamed", LifecycleEvent::ObjectRenamed) + .value("PlateCreated", LifecycleEvent::PlateCreated) + .value("PlateDeleted", LifecycleEvent::PlateDeleted) + .value("PlateSelected", LifecycleEvent::PlateSelected) + .value("PlateRenamed", LifecycleEvent::PlateRenamed) + .value("PresetSelected", LifecycleEvent::PresetSelected) + .value("PresetSaved", LifecycleEvent::PresetSaved) + .value("PrintStateChanged", LifecycleEvent::PrintStateChanged) + .value("DeviceOnline", LifecycleEvent::DeviceOnline) + .value("DeviceOffline", LifecycleEvent::DeviceOffline) + .value("DeviceDiscovered", LifecycleEvent::DeviceDiscovered) + .value("DeviceSelected", LifecycleEvent::DeviceSelected) + .value("UploadStarted", LifecycleEvent::UploadStarted) + .value("UploadFinished", LifecycleEvent::UploadFinished) + .value("PrintJobStarted", LifecycleEvent::PrintJobStarted) + .value("PrintJobFinished", LifecycleEvent::PrintJobFinished) + .value("SendJobStarted", LifecycleEvent::SendJobStarted) + .value("SendJobFinished", LifecycleEvent::SendJobFinished) + .export_values(); + + py::enum_(m, "LifecycleEvtCode", "Outcome code accompanying a LifecycleEventContext") + .value("Ok", LifecycleEvtCode::Ok) + .value("Error", LifecycleEvtCode::Error) + .value("Warn", LifecycleEvtCode::Warn) + .export_values(); + + py::class_(m, "LifecycleEventContext", "Payload accompanying a LifecycleEvent") + .def(py::init<>()) + .def_readonly("name", &LifecycleEventContext::name) + .def_readonly("code", &LifecycleEventContext::code) + .def_readonly("msg", &LifecycleEventContext::msg) + .def_readonly("id", &LifecycleEventContext::id) + .def_readonly("previous_name", &LifecycleEventContext::previous_name) + .def_readonly("device_id", &LifecycleEventContext::device_id) + .def_readonly("job_id", &LifecycleEventContext::job_id) + .def_readonly("source", &LifecycleEventContext::source) + .def_readonly("index", &LifecycleEventContext::index) + .def_readonly("dirty", &LifecycleEventContext::dirty); + py::class_(m, "PluginContext", "Context shared with plugin entry points") .def(py::init<>()) .def_readwrite("orca_version", &PluginContext::orca_version); @@ -401,6 +456,9 @@ void bind_python_api(pybind11::module_& m) .def("get_type", &PluginCapabilityInterface::get_type) .def("on_load", &PluginCapabilityInterface::on_load) .def("on_unload", &PluginCapabilityInterface::on_unload) + .def("on_lifecycle_event", &PluginCapabilityInterface::on_lifecycle_event, + "Override to react to an application lifecycle moment (LifecycleEvent) and its\n" + "LifecycleEventContext payload. Available on every capability type.") .def("has_config_ui", &PluginCapabilityInterface::has_config_ui, "Override to return True to replace the host's default JSON editor with your own HTML\n" "UI, returned by get_config_ui(). Every capability is configurable and appears in the\n" diff --git a/src/slic3r/plugin/PythonPluginInterface.hpp b/src/slic3r/plugin/PythonPluginInterface.hpp index 8b7518cf1c..2702275166 100644 --- a/src/slic3r/plugin/PythonPluginInterface.hpp +++ b/src/slic3r/plugin/PythonPluginInterface.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace Slic3r { enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown }; @@ -167,6 +169,8 @@ public: virtual void on_unload() {} virtual void on_cancelled() {} + virtual void on_lifecycle_event(LifecycleEvent event, const LifecycleEventContext& ctx) { (void) event; (void) ctx; } + // ── C++-only host state, never exposed to Python. Set by the loader at materialization. ── // // The capability owns its own identity and enable flag: they are read once under the GIL, live diff --git a/tests/slic3rutils/test_plugin_lifecycle.cpp b/tests/slic3rutils/test_plugin_lifecycle.cpp index 63a4e6827b..bac3a19ea9 100644 --- a/tests/slic3rutils/test_plugin_lifecycle.cpp +++ b/tests/slic3rutils/test_plugin_lifecycle.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -12,8 +13,11 @@ #include #include +#include #include +#include #include +#include #include using namespace Slic3r; @@ -114,6 +118,72 @@ PluginDescriptor descriptor_of(PluginManager& manager, const std::string& plugin } // namespace +TEST_CASE("Lifecycle hook shutdown drains concurrent dispatch", "[PluginLifecycle][LifecycleEvents]") +{ + std::mutex mutex; + std::condition_variable cv; + bool callback_entered = false; + bool release_callback = false; + bool shutdown_started = false; + bool shutdown_finished = false; + + set_lifecycle_hook_fn([&](LifecycleEvent, const LifecycleEventContext&) { + std::unique_lock lock(mutex); + callback_entered = true; + cv.notify_all(); + cv.wait(lock, [&] { return release_callback; }); + }); + + std::thread dispatch_thread([] { + LifecycleEventContext ctx; + fire_lifecycle_event(LifecycleEvent::ProjectOpened, ctx); + }); + + bool callback_was_entered = false; + { + std::unique_lock lock(mutex); + callback_was_entered = cv.wait_for(lock, std::chrono::seconds(5), [&] { return callback_entered; }); + } + if (!callback_was_entered) { + { + std::lock_guard lock(mutex); + release_callback = true; + cv.notify_all(); + } + dispatch_thread.join(); + set_lifecycle_hook_fn(nullptr); + FAIL("Lifecycle callback did not start"); + } + + std::thread shutdown_thread([&] { + { + std::lock_guard lock(mutex); + shutdown_started = true; + cv.notify_all(); + } + set_lifecycle_hook_fn(nullptr); + std::lock_guard lock(mutex); + shutdown_finished = true; + cv.notify_all(); + }); + + bool shutdown_was_started = false; + { + std::unique_lock lock(mutex); + shutdown_was_started = cv.wait_for(lock, std::chrono::seconds(5), [&] { return shutdown_started; }); + if (shutdown_was_started) + CHECK_FALSE(cv.wait_for(lock, std::chrono::milliseconds(100), [&] { return shutdown_finished; })); + release_callback = true; + cv.notify_all(); + } + + dispatch_thread.join(); + shutdown_thread.join(); + if (!shutdown_was_started) + FAIL("Lifecycle hook shutdown did not start"); + CHECK(shutdown_finished); +} + TEST_CASE("A discovered script plugin loads and materializes its capability", "[PluginLifecycle][Python]") { ScopedPluginManager plugin_system;