mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-19 06:53:02 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87f5aa298e |
@@ -330,13 +330,12 @@ jobs:
|
||||
# sources are unchanged, so a re-run of the same commit would skip the
|
||||
# OrcaSlicer module and ship no test asset. A per-run value in that module's
|
||||
# env keeps it rebuilding; orca_deps stays cached, and the compiler cache
|
||||
# still serves the rebuild. run-tests on the same module builds the test
|
||||
# binaries.
|
||||
- name: Inject commit hash, run-tests and cache buster into Flatpak manifest
|
||||
# still serves the rebuild.
|
||||
- name: Inject commit hash and flatpak-builder cache buster into Flatpak manifest
|
||||
env:
|
||||
flatpak_builder_cache_buster: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
run: |
|
||||
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n run-tests: true\n build-options:\n env:\n flatpak_builder_cache_buster: \"$flatpak_builder_cache_buster\"\n git_commit_hash: \"$git_commit_hash\"|}" \
|
||||
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n flatpak_builder_cache_buster: \"$flatpak_builder_cache_buster\"\n git_commit_hash: \"$git_commit_hash\"|}" \
|
||||
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
|
||||
shell: bash
|
||||
# flatpak-builder's --ccache only wraps cc and gcc, and the manifest builds
|
||||
@@ -382,6 +381,9 @@ jobs:
|
||||
save-cache: false
|
||||
arch: ${{ matrix.variant.arch }}
|
||||
upload-artifact: false
|
||||
# run-tests fires the module's build-only test-commands; keep-build-dirs
|
||||
# retains the binaries for the packaging step below.
|
||||
run-tests: true
|
||||
keep-build-dirs: true
|
||||
# The build has just touched everything it can use, so an object untouched
|
||||
# for a week is dead, usually orphaned by a flag change.
|
||||
@@ -447,13 +449,10 @@ jobs:
|
||||
# the bounds checks are compiled in, so a stripped exe still catches them.
|
||||
find "$d/build_flatpak/tests" -type f -perm -u+x -exec strip --strip-unneeded {} + 2>/dev/null || true
|
||||
# At runtime the tests read tests/ (TEST_DATA_DIR), scripts/, and under
|
||||
# resources/ the shipped profiles (PROFILES_DIR), the printers/ maps, and
|
||||
# the icon SVGs (NativeCommands icon names are checked against them).
|
||||
# resources/ the shipped profiles (PROFILES_DIR) and the printers/ maps.
|
||||
find "$d" -mindepth 1 -maxdepth 1 -type d \
|
||||
! -name tests ! -name build_flatpak ! -name scripts ! -name resources -exec rm -rf {} +
|
||||
find "$d/resources" -mindepth 1 -maxdepth 1 ! -name profiles ! -name printers ! -name images -exec rm -rf {} +
|
||||
# Only the SVGs are read; the png/ico/icns/gif assets are ~35MB of dead weight.
|
||||
find "$d/resources/images" -mindepth 1 -maxdepth 1 ! -name '*.svg' -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$d/resources" -mindepth 1 -maxdepth 1 ! -name profiles ! -name printers -exec rm -rf {} +
|
||||
tar -cf flatpak-test-asset.tar flatpak_app "$d"
|
||||
- name: Upload flatpak test asset
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
@@ -448,26 +448,9 @@ jobs:
|
||||
|
||||
- name: Install nsis
|
||||
if: runner.os == 'Windows' && !vars.SELF_HOSTED
|
||||
shell: pwsh
|
||||
# The Chocolatey community feed intermittently 504s, and `choco install`
|
||||
# exits 0 when package resolution fails that way. Unchecked, the job then
|
||||
# builds for ~25 minutes before `cpack -G NSIS` reports a missing makensis.
|
||||
# Retry the install and verify makensis itself, so a real failure stops here.
|
||||
run: |
|
||||
dir "C:/Program Files (x86)/Windows Kits/10/Include"
|
||||
$nsisDir = Join-Path ${env:ProgramFiles(x86)} 'NSIS'
|
||||
$makensis = Join-Path $nsisDir 'makensis.exe'
|
||||
for ($attempt = 1; $attempt -le 3 -and -not (Test-Path $makensis); $attempt++) {
|
||||
if ($attempt -gt 1) { Start-Sleep -Seconds (15 * $attempt) }
|
||||
Write-Host "::group::choco install nsis (attempt $attempt)"
|
||||
choco install nsis --yes --no-progress
|
||||
Write-Host "::endgroup::"
|
||||
}
|
||||
if (-not (Test-Path $makensis)) {
|
||||
throw "NSIS install failed: $makensis not found after 3 attempts."
|
||||
}
|
||||
& $makensis /VERSION
|
||||
$nsisDir | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8
|
||||
choco install nsis
|
||||
|
||||
- name: Build slicer Win
|
||||
if: runner.os == 'Windows'
|
||||
@@ -702,18 +685,6 @@ jobs:
|
||||
name: OrcaSlicer_profile_validator_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
|
||||
path: './build/src/Release/OrcaSlicer_profile_validator'
|
||||
|
||||
# generate_system_cache is what scripts/build_preset_cache.sh bakes the
|
||||
# <vendor>.opc caches with; it was already built by the "Build system
|
||||
# preset cache (Linux)" step above. The .opc format is 64-bit
|
||||
# little-endian native, i.e. identical across every platform Orca ships,
|
||||
# so only the Linux binary is published.
|
||||
- name: Upload generate_system_cache Ubuntu
|
||||
if: ${{ ! env.ACT && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: generate_system_cache_Linux_ubuntu_${{ env.ubuntu-ver }}_${{ env.ver }}
|
||||
path: './build/src/dev-utils/Release/generate_system_cache'
|
||||
|
||||
- name: Deploy Ubuntu release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && env.deploy_nightly == 'true' && runner.os == 'Linux' && !vars.SELF_HOSTED }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
@@ -744,17 +715,6 @@ jobs:
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
- name: Deploy Ubuntu generate_system_cache release
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: ./build/src/dev-utils/Release/generate_system_cache
|
||||
asset_name: generate_system_cache_Linux${{ env.ubuntu-ver-str }}_nightly
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1
|
||||
|
||||
- name: Deploy orca_custom_preset_tests
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' && ! env.ACT && github.ref == 'refs/heads/main' && runner.os == 'Linux' && !vars.SELF_HOSTED && inputs.arch != 'aarch64' }}
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
name: Post-merge profiles
|
||||
|
||||
# Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a
|
||||
# profile change lands on main or a release branch, rebuild the affected vendors'
|
||||
# binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on
|
||||
# a per-Orca-version release of the profiles repo. From there OrcaCloud's OTA
|
||||
# Manager lists the asset, a maintainer attaches a changelog and hits Publish, and
|
||||
# only then does it become a live OTA update - this workflow does none of that
|
||||
# last part (no changelog, no R2, no webhook).
|
||||
#
|
||||
# Asset contract expected by OrcaCloud's release scanner:
|
||||
# ^(\d+\.\d+\.\d+)_([^_]+)_(\d+(?:\.\d+){3})_(\d{12})\.zip$
|
||||
# <orca_ver>_<vendor>_<profile_version>_<UTC yyyymmddHHMM>.zip (zip root: <vendor>.opc)
|
||||
#
|
||||
# Setup (App + secrets): docs/ota/post-merge-profiles-setup.md
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
- 'resources/profiles/**'
|
||||
- '.github/workflows/post_merge_profiles.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# One run per branch; let a run finish rather than cancel it, since it publishes.
|
||||
concurrency:
|
||||
group: post-merge-profiles-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# generate_system_cache is published to this repo's own nightly-builds release
|
||||
# by build_orca.yml's Linux leg. The job guard pins github.repository to
|
||||
# OrcaSlicer/OrcaSlicer, so this resolves there.
|
||||
TOOL_REPO: ${{ github.repository }}
|
||||
TOOL_ASSET: generate_system_cache_Linux_Ubuntu2404_nightly
|
||||
# Where per-vendor ZIP assets are published; OrcaCloud's OTA reads this repo.
|
||||
PROFILES_OWNER: OrcaSlicer
|
||||
PROFILES_REPO: orcaslicer-profiles
|
||||
|
||||
jobs:
|
||||
publish_profile_caches:
|
||||
name: Publish profile caches
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
|
||||
# FOLDER_MERGERS is an environment-scoped variable, shared with the PR
|
||||
# merge bot. Keep this environment free of protection rules so this
|
||||
# push-triggered job does not wait for a reviewer.
|
||||
environment: merge-delegation
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
# Enough history to reach github.event.before for the changed-vendor
|
||||
# diff on a normal push; deeper pushes fall back to HEAD^..HEAD in the
|
||||
# step below. fetch-depth: 0 would clone all of OrcaSlicer's history.
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Resolve changed vendors
|
||||
id: vendors
|
||||
shell: bash
|
||||
env:
|
||||
FOLDER_MERGERS: ${{ vars.FOLDER_MERGERS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base='${{ github.event.before }}'
|
||||
head='${{ github.sha }}'
|
||||
# Zero SHA (branch created / force push) or manual dispatch: fall back
|
||||
# to this commit's own diff.
|
||||
if [ -z "$base" ] || [ "$base" = "0000000000000000000000000000000000000000" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
|
||||
base="$head^"
|
||||
fi
|
||||
echo "Diffing $base..$head"
|
||||
|
||||
mapfile -t candidates < <(
|
||||
git diff --name-only "$base" "$head" -- resources/profiles \
|
||||
| sed -nE 's#^resources/profiles/([^/]+)/.*#\1#p; s#^resources/profiles/([^/]+)\.json$#\1#p' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
vendors=()
|
||||
for v in "${candidates[@]:-}"; do
|
||||
[ -n "$v" ] || continue
|
||||
json="resources/profiles/$v.json"
|
||||
# A vendor has a manifest plus either a preset directory or a version
|
||||
# field; this drops non-vendor files such as blacklist.json.
|
||||
if [ -f "$json" ] && { [ -d "resources/profiles/$v" ] || jq -e '.version' "$json" >/dev/null 2>&1; }; then
|
||||
vendors+=("$v")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#vendors[@]}" -eq 0 ]; then
|
||||
echo "No changed vendor profiles in this push; nothing to publish."
|
||||
echo "vendors=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# A vendor is eligible only when both the profile directory and its
|
||||
# sibling bundle JSON are covered by at least one FOLDER_MERGERS
|
||||
# grant. The account part is intentionally ignored here: this is a
|
||||
# post-merge safety check, not an authorization check for a command.
|
||||
grants=()
|
||||
while IFS= read -r raw_line; do
|
||||
line="${raw_line#"${raw_line%%[![:space:]]*}"}"
|
||||
line="${line%"${line##*[![:space:]]}"}"
|
||||
[ -n "$line" ] || continue
|
||||
[[ "$line" == \#* ]] && continue
|
||||
[[ "$line" == *:* ]] || continue
|
||||
|
||||
grant="${line#*:}"
|
||||
grant="${grant#"${grant%%[![:space:]]*}"}"
|
||||
grant="${grant%"${grant##*[![:space:]]}"}"
|
||||
while [[ "$grant" == */ ]]; do grant="${grant%/}"; done
|
||||
grants+=("$grant")
|
||||
done <<< "${FOLDER_MERGERS:-}"
|
||||
|
||||
is_granted() {
|
||||
local path="$1"
|
||||
local grant
|
||||
for grant in "${grants[@]:-}"; do
|
||||
if [[ "$path" == "$grant" || "$path" == "$grant/"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
unauthorized=()
|
||||
for v in "${vendors[@]}"; do
|
||||
if ! is_granted "resources/profiles/$v" || ! is_granted "resources/profiles/$v.json"; then
|
||||
unauthorized+=("$v")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#unauthorized[@]}" -ne 0 ]; then
|
||||
echo "Changed vendor profiles are not covered by FOLDER_MERGERS: ${unauthorized[*]}"
|
||||
echo "No profile caches will be published for this push."
|
||||
echo "vendors=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf 'Changed vendors: %s\n' "${vendors[*]}"
|
||||
echo "vendors=${vendors[*]}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve Orca version
|
||||
id: orca
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
raw="$(sed -nE 's/^set\(SoftFever_VERSION "([^"]+)".*/\1/p' version.inc | head -1)"
|
||||
[ -n "$raw" ] || { echo "::error::could not read SoftFever_VERSION from version.inc"; exit 1; }
|
||||
orca_ver="${raw%%-*}"
|
||||
if ! [[ "$orca_ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Orca version '$orca_ver' (from '$raw') is not X.Y.Z"; exit 1
|
||||
fi
|
||||
# release_tag is what the desktop client sends as orca_version and what
|
||||
# OrcaCloud keys R2 on; orca_ver (X.Y.Z) is the asset-name prefix.
|
||||
echo "release_tag=$raw" >> "$GITHUB_OUTPUT"
|
||||
echo "orca_ver=$orca_ver" >> "$GITHUB_OUTPUT"
|
||||
echo "Orca version: release_tag=$raw asset_prefix=$orca_ver"
|
||||
|
||||
- name: Validate profile versions
|
||||
id: pver
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
: > "$RUNNER_TEMP/pver.tsv"
|
||||
for v in ${{ steps.vendors.outputs.vendors }}; do
|
||||
pv="$(jq -r '.version // empty' "resources/profiles/$v.json")"
|
||||
if ! [[ "$pv" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::vendor $v version '${pv:-<none>}' must be 4 numeric parts (A.B.C.D) for the OTA asset name; fix resources/profiles/$v.json"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\t%s\n' "$v" "$pv" >> "$RUNNER_TEMP/pver.tsv"
|
||||
echo "$v -> $pv"
|
||||
done
|
||||
|
||||
- name: Download generate_system_cache
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
shell: bash
|
||||
env:
|
||||
# gh (with the default token) rather than an unauthenticated curl: keeps
|
||||
# working if TOOL_REPO is ever private and avoids anonymous rate limits.
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh release download nightly-builds --repo "$TOOL_REPO" \
|
||||
--pattern "$TOOL_ASSET" --output generate_system_cache --clobber
|
||||
chmod +x generate_system_cache
|
||||
|
||||
- name: Build caches and package assets
|
||||
id: pkg
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# One timestamp for the whole run so a multi-vendor merge groups together.
|
||||
ts="$(date -u +%Y%m%d%H%M)"
|
||||
orca_ver='${{ steps.orca.outputs.orca_ver }}'
|
||||
out="$RUNNER_TEMP/assets"
|
||||
mkdir -p "$out"
|
||||
|
||||
for v in ${{ steps.vendors.outputs.vendors }}; do
|
||||
./generate_system_cache -p "$GITHUB_WORKSPACE/resources/profiles" -v "$v" -l 2
|
||||
opc="resources/profiles/$v.opc"
|
||||
[ -f "$opc" ] || { echo "::error::$opc was not generated"; exit 1; }
|
||||
pv="$(awk -F'\t' -v v="$v" '$1==v{print $2}' "$RUNNER_TEMP/pver.tsv")"
|
||||
name="${orca_ver}_${v}_${pv}_${ts}.zip"
|
||||
( cd resources/profiles && zip -q -j "$out/$name" "$v.opc" )
|
||||
echo "packaged $name"
|
||||
done
|
||||
echo "dir=$out" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Mint profiles-repo token
|
||||
id: token
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.PROFILES_APP_ID }}
|
||||
private-key: ${{ secrets.PROFILES_APP_PRIVATE_KEY }}
|
||||
owner: ${{ env.PROFILES_OWNER }}
|
||||
repositories: ${{ env.PROFILES_REPO }}
|
||||
|
||||
- name: Publish assets to profiles release
|
||||
if: steps.vendors.outputs.vendors != ''
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
RELEASE_TAG: ${{ steps.orca.outputs.release_tag }}
|
||||
ASSET_DIR: ${{ steps.pkg.outputs.dir }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repo="$PROFILES_OWNER/$PROFILES_REPO"
|
||||
if ! gh release view "$RELEASE_TAG" --repo "$repo" >/dev/null 2>&1; then
|
||||
echo "Creating release $RELEASE_TAG on $repo"
|
||||
gh release create "$RELEASE_TAG" --repo "$repo" \
|
||||
--title "$RELEASE_TAG" --notes "Profile cache assets for Orca $RELEASE_TAG." \
|
||||
--latest=false
|
||||
fi
|
||||
# Asset names are timestamp-unique; a clash means a bug, so don't --clobber.
|
||||
gh release upload "$RELEASE_TAG" --repo "$repo" "$ASSET_DIR"/*.zip
|
||||
|
||||
{
|
||||
echo "### Published to \`$repo\` release \`$RELEASE_TAG\`"
|
||||
for f in "$ASSET_DIR"/*.zip; do echo "- \`$(basename "$f")\`"; done
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
+30
-2696
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -125,7 +125,6 @@ src/slic3r/GUI/ThermalPreconditioningDialog.hpp
|
||||
src/slic3r/GUI/Jobs/SLAImportJob.cpp
|
||||
src/slic3r/GUI/Jobs/UpgradeNetworkJob.cpp
|
||||
src/slic3r/GUI/AboutDialog.cpp
|
||||
src/slic3r/GUI/ActionRegistry.cpp
|
||||
src/slic3r/GUI/AMSMaterialsSetting.cpp
|
||||
src/slic3r/GUI/ExtrusionCalibration.cpp
|
||||
src/slic3r/GUI/AmsMappingPopup.cpp
|
||||
@@ -160,7 +159,6 @@ src/slic3r/GUI/SelectMachinePop.cpp
|
||||
src/slic3r/GUI/StatusPanel.cpp
|
||||
src/slic3r/GUI/Monitor.cpp
|
||||
src/slic3r/GUI/MsgDialog.cpp
|
||||
src/slic3r/GUI/NativeCommands.cpp
|
||||
src/slic3r/GUI/NotificationManager.hpp
|
||||
src/slic3r/GUI/NotificationManager.cpp
|
||||
src/slic3r/GUI/ObjectDataViewModel.cpp
|
||||
@@ -181,8 +179,6 @@ src/slic3r/GUI/PublishDialog.cpp
|
||||
src/slic3r/GUI/PublishSettingsDialog.cpp
|
||||
src/slic3r/GUI/SavePresetDialog.cpp
|
||||
src/slic3r/GUI/Search.cpp
|
||||
src/slic3r/GUI/SettingsIndex.cpp
|
||||
src/slic3r/GUI/SpeedDialDialog.cpp
|
||||
src/slic3r/GUI/Selection.cpp
|
||||
src/slic3r/GUI/SelectMachine.cpp
|
||||
src/slic3r/GUI/PrePrintChecker.cpp
|
||||
@@ -296,6 +292,5 @@ src/slic3r/GUI/AMSDryControl.cpp
|
||||
src/slic3r/GUI/AMSDryControl.hpp
|
||||
src/libslic3r/PresetBundle.cpp
|
||||
src/slic3r/GUI/CAD/DesignPanel.cpp
|
||||
src/slic3r/GUI/CAD/SketchInlineEditor.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoPrimitive.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSketch.cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M14.5,1.5v12a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1V1.5a1,1,0,0,1,1-1h12a1,1,0,0,1,1,1Z" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/><polyline points="4,5 6.5,7.5 4,10" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><line x1="8" y1="10" x2="11" y2="10" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/></svg>
|
||||
|
Before Width: | Height: | Size: 524 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path d="M11.5,5.5v5c0,.55-.45,1-1,1H1.5c-.55,0-1-.45-1-1V1.5C.5.95.95.5,1.5.5h5M3.5,6.5v2h2l5.59-5.59c.55-.55.55-1.45,0-2s-1.45-.55-2,0L3.5,6.5Z" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/></svg>
|
||||
|
Before Width: | Height: | Size: 340 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "OrcaFilamentLibrary",
|
||||
"version": "02.04.00.08",
|
||||
"version": "02.04.00.07",
|
||||
"force_update": "0",
|
||||
"description": "Orca Filament Library",
|
||||
"filament_list": [
|
||||
@@ -304,14 +304,6 @@
|
||||
"name": "Generic PA-CF @System",
|
||||
"sub_path": "filament/Generic PA-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-66 @base",
|
||||
"sub_path": "filament/INSLOGIC/INSLOGIC PA6-66 @base.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-CF @base",
|
||||
"sub_path": "filament/INSLOGIC/INSLOGIC PA6-CF @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu PC @base",
|
||||
"sub_path": "filament/Bambu/Bambu PC @base.json"
|
||||
@@ -444,10 +436,6 @@
|
||||
"name": "GreenGate3D PETG @base",
|
||||
"sub_path": "filament/GreenGate3D/GreenGate3D PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PETG Pro @base",
|
||||
"sub_path": "filament/INSLOGIC/INSLOGIC PETG Pro @base.json"
|
||||
},
|
||||
{
|
||||
"name": "JAYO PETG @base",
|
||||
"sub_path": "filament/JAYO/JAYO PETG @base.json"
|
||||
@@ -648,10 +636,6 @@
|
||||
"name": "Generic PLA-CF @System",
|
||||
"sub_path": "filament/Generic PLA-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PLA Pro @base",
|
||||
"sub_path": "filament/INSLOGIC/INSLOGIC PLA Pro @base.json"
|
||||
},
|
||||
{
|
||||
"name": "JAYO PLA @base",
|
||||
"sub_path": "filament/JAYO/JAYO PLA @base.json"
|
||||
@@ -1132,30 +1116,6 @@
|
||||
"name": "Fiberon PA612-CF @System",
|
||||
"sub_path": "filament/Polymaker/Fiberon PA612-CF @System.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-66 @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @BBL P2S.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-66 @P1-X1",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-66 @P1-X1.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-66 @Prusa CORE One",
|
||||
"sub_path": "filament/INSLOGIC/Prusa/INSLOGIC PA6-66 @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-CF @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-CF @BBL P2S.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-CF @P1-X1",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PA6-CF @P1-X1.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PA6-CF @Prusa CORE One",
|
||||
"sub_path": "filament/INSLOGIC/Prusa/INSLOGIC PA6-CF @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "Bambu PC @System",
|
||||
"sub_path": "filament/Bambu/Bambu PC @System.json"
|
||||
@@ -1352,18 +1312,6 @@
|
||||
"name": "GreenGate3D PETG @System",
|
||||
"sub_path": "filament/GreenGate3D/GreenGate3D PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PETG Pro @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PETG Pro @BBL P2S.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PETG Pro @P1-X1",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PETG Pro @P1-X1.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PETG Pro @Prusa CORE One",
|
||||
"sub_path": "filament/INSLOGIC/Prusa/INSLOGIC PETG Pro @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "JAYO PETG @BBL A1 0.4 nozzle",
|
||||
"sub_path": "filament/JAYO/BBL/JAYO PETG @BBL A1 0.4 nozzle.json"
|
||||
@@ -1752,18 +1700,6 @@
|
||||
"name": "Generic PLA Matte @System",
|
||||
"sub_path": "filament/Generic PLA Matte @System.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PLA Pro @BBL P2S",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @BBL P2S.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PLA Pro @P1-X1",
|
||||
"sub_path": "filament/INSLOGIC/BBL/INSLOGIC PLA Pro @P1-X1.json"
|
||||
},
|
||||
{
|
||||
"name": "INSLOGIC PLA Pro @Prusa CORE One",
|
||||
"sub_path": "filament/INSLOGIC/Prusa/INSLOGIC PLA Pro @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "JAYO PLA @BBL A1 0.4 nozzle",
|
||||
"sub_path": "filament/JAYO/BBL/JAYO PLA @BBL A1 0.4 nozzle.json"
|
||||
|
||||
-275
@@ -1,275 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-66 @BBL P2S",
|
||||
"inherits": "INSLOGIC PA6-66 @base",
|
||||
"from": "system",
|
||||
"setting_id": "UHHdgZgE7e8KR2GV",
|
||||
"instantiation": "true",
|
||||
"activate_air_filtration": [
|
||||
"1"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"1"
|
||||
],
|
||||
"close_additional_fan_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"complete_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"during_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"50"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"50"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"20"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_extruder_compatibility": [
|
||||
"0"
|
||||
],
|
||||
"filament_cooling_before_tower": [
|
||||
"10",
|
||||
"10"
|
||||
],
|
||||
"filament_preheat_temperature_delta": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_dist": [
|
||||
"10"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_length": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_ironing_area": [
|
||||
"4"
|
||||
],
|
||||
"filament_tower_interface_purge_volume": [
|
||||
"20"
|
||||
],
|
||||
"filament_tower_interface_print_temp": [
|
||||
"-1"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.10"
|
||||
],
|
||||
"filament_dev_ams_drying_ams_limitations": [
|
||||
"1"
|
||||
],
|
||||
"filament_dev_ams_drying_heat_distortion_temperature": [
|
||||
"90"
|
||||
],
|
||||
"filament_dev_ams_drying_temperature": [
|
||||
"65",
|
||||
"80",
|
||||
"65",
|
||||
"80"
|
||||
],
|
||||
"filament_dev_ams_drying_time": [
|
||||
"12",
|
||||
"12",
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_dev_chamber_drying_bed_temperature": [
|
||||
"90.0"
|
||||
],
|
||||
"filament_dev_chamber_drying_time": [
|
||||
"18"
|
||||
],
|
||||
"filament_dev_drying_cooling_temperature": [
|
||||
"65"
|
||||
],
|
||||
"filament_dev_drying_softening_temperature": [
|
||||
"85"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0"
|
||||
],
|
||||
"filament_flush_temp_fast": [
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_is_support": [
|
||||
"0"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0"
|
||||
],
|
||||
"filament_pre_cooling_temperature_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_prime_volume": [
|
||||
"45"
|
||||
],
|
||||
"filament_prime_volume_nc": [
|
||||
"60"
|
||||
],
|
||||
"filament_printable": [
|
||||
"3"
|
||||
],
|
||||
"filament_ramming_travel_time_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1"
|
||||
],
|
||||
"filament_ramming_volumetric_speed_nc": [
|
||||
"-1"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"none"
|
||||
],
|
||||
"filament_shrink": [
|
||||
"100%"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"additional_fan_full_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_retract_length_nc": [
|
||||
"14"
|
||||
],
|
||||
"filament_change_length": [
|
||||
"10"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"50"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"50"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"255",
|
||||
"255"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"255",
|
||||
"255"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"280"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"250"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"95"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"10%"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"0"
|
||||
],
|
||||
"required_nozzle_HRC": [
|
||||
"3"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"6"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"supertack_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"supertack_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"74.2"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"50"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"50"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\nM145 P0 ; set airduct mode to cooling mode for cooling\nM106 P2 S255 ; turn on auxiliary fan for cooling\nM106 P3 S127 ; turn on chamber fan for cooling\nM1002 gcode_claim_action : 29\nM191 S0 ; wait for chamber temp\nM106 P2 S102 ; turn on chamber cooling fan\nM106 P10 S0 ; turn off left aux fan\nM142 P6 R30 S40 U0.3 V0.8 ; set PETG exhaust chamber autocooling\n"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n\n"
|
||||
],
|
||||
"filament_adhesiveness_category": [
|
||||
"400"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab P2S 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-66 @P1-X1",
|
||||
"inherits": "INSLOGIC PA6-66 @base",
|
||||
"from": "system",
|
||||
"setting_id": "X6EcSgHdfuxoY1lL",
|
||||
"instantiation": "true",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"50"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"50"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"50"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"50"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"95%"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"255",
|
||||
"255"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"2"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"2"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\n"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"255",
|
||||
"255"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"70.4"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"250",
|
||||
"250"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"280",
|
||||
"280"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0",
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab X1 0.4 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab X1E 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-275
@@ -1,275 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-CF @BBL P2S",
|
||||
"inherits": "INSLOGIC PA6-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "Km9iCy4A8kukE0AA",
|
||||
"instantiation": "true",
|
||||
"activate_air_filtration": [
|
||||
"1"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"1"
|
||||
],
|
||||
"close_additional_fan_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"complete_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"during_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"20"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_extruder_compatibility": [
|
||||
"0"
|
||||
],
|
||||
"filament_cooling_before_tower": [
|
||||
"10",
|
||||
"10"
|
||||
],
|
||||
"filament_preheat_temperature_delta": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_dist": [
|
||||
"10"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_length": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_ironing_area": [
|
||||
"4"
|
||||
],
|
||||
"filament_tower_interface_purge_volume": [
|
||||
"20"
|
||||
],
|
||||
"filament_tower_interface_print_temp": [
|
||||
"-1"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.12"
|
||||
],
|
||||
"filament_dev_ams_drying_ams_limitations": [
|
||||
"1"
|
||||
],
|
||||
"filament_dev_ams_drying_heat_distortion_temperature": [
|
||||
"90"
|
||||
],
|
||||
"filament_dev_ams_drying_temperature": [
|
||||
"65",
|
||||
"80",
|
||||
"65",
|
||||
"80"
|
||||
],
|
||||
"filament_dev_ams_drying_time": [
|
||||
"12",
|
||||
"12",
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_dev_chamber_drying_bed_temperature": [
|
||||
"90.0"
|
||||
],
|
||||
"filament_dev_chamber_drying_time": [
|
||||
"18"
|
||||
],
|
||||
"filament_dev_drying_cooling_temperature": [
|
||||
"65"
|
||||
],
|
||||
"filament_dev_drying_softening_temperature": [
|
||||
"85"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0"
|
||||
],
|
||||
"filament_flush_temp_fast": [
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_is_support": [
|
||||
"0"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0"
|
||||
],
|
||||
"filament_pre_cooling_temperature_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_prime_volume": [
|
||||
"45"
|
||||
],
|
||||
"filament_prime_volume_nc": [
|
||||
"60"
|
||||
],
|
||||
"filament_printable": [
|
||||
"3"
|
||||
],
|
||||
"filament_ramming_travel_time_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1"
|
||||
],
|
||||
"filament_ramming_volumetric_speed_nc": [
|
||||
"-1"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"none"
|
||||
],
|
||||
"filament_shrink": [
|
||||
"100%"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"additional_fan_full_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_retract_length_nc": [
|
||||
"14"
|
||||
],
|
||||
"filament_change_length": [
|
||||
"10"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"275",
|
||||
"275"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"280",
|
||||
"280"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"290"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"270"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"95"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"10%"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"0"
|
||||
],
|
||||
"required_nozzle_HRC": [
|
||||
"3"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"6"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"supertack_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"supertack_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"74.2"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\nM145 P0 ; set airduct mode to cooling mode for cooling\nM106 P2 S255 ; turn on auxiliary fan for cooling\nM106 P3 S127 ; turn on chamber fan for cooling\nM1002 gcode_claim_action : 29\nM191 S0 ; wait for chamber temp\nM106 P2 S102 ; turn on chamber cooling fan\nM106 P10 S0 ; turn off left aux fan\nM142 P6 R30 S40 U0.3 V0.8 ; set PETG exhaust chamber autocooling\n"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n\n"
|
||||
],
|
||||
"filament_adhesiveness_category": [
|
||||
"400"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab P2S 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-CF @P1-X1",
|
||||
"inherits": "INSLOGIC PA6-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "ky3k6Q9Fyi3QwCHL",
|
||||
"instantiation": "true",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"95%"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"280",
|
||||
"280"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"2"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"2"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\n"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"275",
|
||||
"275"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"70.4"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"270",
|
||||
"270"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"290",
|
||||
"290"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0",
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab X1 0.4 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab X1E 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-328
@@ -1,328 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PETG Pro @BBL P2S",
|
||||
"inherits": "INSLOGIC PETG Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "6iAMtvQjja2ZKenA",
|
||||
"instantiation": "true",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"25"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"260"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"95%"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"8"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20",
|
||||
"20"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1"
|
||||
],
|
||||
"filament_ramming_volumetric_speed_nc": [
|
||||
"-1"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"first_x_layer_fan_speed": [
|
||||
"0"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0"
|
||||
],
|
||||
"filament_pre_cooling_temperature_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time_nc": [
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"activate_air_filtration": [
|
||||
"0"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"1"
|
||||
],
|
||||
"close_additional_fan_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"complete_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"during_print_exhaust_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"filament_extruder_compatibility": [
|
||||
"0"
|
||||
],
|
||||
"filament_cooling_before_tower": [
|
||||
"10",
|
||||
"10"
|
||||
],
|
||||
"filament_preheat_temperature_delta": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_dist": [
|
||||
"10"
|
||||
],
|
||||
"filament_tower_interface_pre_extrusion_length": [
|
||||
"0"
|
||||
],
|
||||
"filament_tower_ironing_area": [
|
||||
"4"
|
||||
],
|
||||
"filament_tower_interface_purge_volume": [
|
||||
"20"
|
||||
],
|
||||
"filament_tower_interface_print_temp": [
|
||||
"-1"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.28"
|
||||
],
|
||||
"filament_dev_ams_drying_ams_limitations": [
|
||||
"1",
|
||||
"0"
|
||||
],
|
||||
"filament_dev_ams_drying_heat_distortion_temperature": [
|
||||
"75"
|
||||
],
|
||||
"filament_dev_ams_drying_temperature": [
|
||||
"65",
|
||||
"65",
|
||||
"55",
|
||||
"55"
|
||||
],
|
||||
"filament_dev_ams_drying_time": [
|
||||
"12",
|
||||
"12",
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_dev_chamber_drying_bed_temperature": [
|
||||
"80"
|
||||
],
|
||||
"filament_dev_chamber_drying_time": [
|
||||
"12.0"
|
||||
],
|
||||
"filament_dev_drying_cooling_temperature": [
|
||||
"55"
|
||||
],
|
||||
"filament_dev_drying_softening_temperature": [
|
||||
"60"
|
||||
],
|
||||
"filament_is_support": [
|
||||
"0"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_prime_volume": [
|
||||
"45"
|
||||
],
|
||||
"filament_prime_volume_nc": [
|
||||
"60"
|
||||
],
|
||||
"filament_printable": [
|
||||
"3"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"none"
|
||||
],
|
||||
"filament_shrink": [
|
||||
"100%"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"additional_fan_full_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
],
|
||||
"filament_retract_length_nc": [
|
||||
"14"
|
||||
],
|
||||
"filament_change_length": [
|
||||
"10"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"240"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"required_nozzle_HRC": [
|
||||
"3"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"supertack_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"supertack_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"81"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\nM145 P0 ; set airduct mode to cooling mode for cooling\nM106 P2 S255 ; turn on auxiliary fan for cooling\nM106 P3 S127 ; turn on chamber fan for cooling\nM1002 gcode_claim_action : 29\nM191 S0 ; wait for chamber temp\nM106 P2 S102 ; turn on chamber cooling fan\nM106 P10 S0 ; turn off left aux fan\nM142 P6 R30 S40 U0.3 V0.8 ; set PETG exhaust chamber autocooling\n"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n\n"
|
||||
],
|
||||
"filament_adhesiveness_category": [
|
||||
"300"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab P2S 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PETG Pro @P1-X1",
|
||||
"inherits": "INSLOGIC PETG Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "miOBX0bGzziXI6SE",
|
||||
"instantiation": "true",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"95%"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"2"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"2"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\n"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"70.4"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"240",
|
||||
"240"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"260",
|
||||
"260"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0",
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab X1 0.4 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab X1E 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PLA Pro @BBL P2S",
|
||||
"inherits": "INSLOGIC PLA Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "UAd1QXW7vm9gwoEc",
|
||||
"instantiation": "true",
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"all"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
],
|
||||
"filament_change_length": [
|
||||
"5"
|
||||
],
|
||||
"filament_prime_volume": [
|
||||
"30"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; filament start gcode\n{if (bed_temperature[current_extruder] >55)||(bed_temperature_initial_layer[current_extruder] >55)}M106 P3 S200\n{elsif(bed_temperature[current_extruder] >50)||(bed_temperature_initial_layer[current_extruder] >50)}M106 P3 S150\n{elsif(bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}M106 P3 S50\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_ramming_volumetric_speed_nc": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"10",
|
||||
"10"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"first_x_layer_fan_speed": [
|
||||
"40"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_pre_cooling_temperature_nc": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time_nc": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"215",
|
||||
"215"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"215",
|
||||
"215"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0",
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"205",
|
||||
"205"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab P2S 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PLA Pro @P1-X1",
|
||||
"inherits": "INSLOGIC PLA Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "InmXr3LoNKKDfnQu",
|
||||
"instantiation": "true",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"95%"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"3"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; filament end gcode \n"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98",
|
||||
"0.98"
|
||||
],
|
||||
"reduce_fan_stop_start_freq": [
|
||||
"1"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_retraction_minimum_travel": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"1",
|
||||
"1"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop_types": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_wipe_distance": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"215",
|
||||
"215"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"2"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"60"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"2"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"; Filament gcode\n"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"215",
|
||||
"215"
|
||||
],
|
||||
"temperature_vitrification": [
|
||||
"70.4"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"205",
|
||||
"205"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"245",
|
||||
"245"
|
||||
],
|
||||
"filament_flush_temp": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_flush_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_volumetric_speed": [
|
||||
"-1",
|
||||
"-1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"nil",
|
||||
"nil"
|
||||
],
|
||||
"filament_extruder_variant": [
|
||||
"Direct Drive Standard",
|
||||
"Direct Drive High Flow"
|
||||
],
|
||||
"filament_pre_cooling_temperature": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_ramming_travel_time": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"filament_adaptive_volumetric_speed": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"long_retractions_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"retraction_distances_when_ec": [
|
||||
"0",
|
||||
"0"
|
||||
],
|
||||
"volumetric_speed_coefficients": [
|
||||
"0 0 0 0 0 0",
|
||||
"0 0 0 0 0 0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"0"
|
||||
],
|
||||
"additional_cooling_fan_speed": [
|
||||
"70"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Bambu Lab X1 0.4 nozzle",
|
||||
"Bambu Lab X1 Carbon 0.4 nozzle",
|
||||
"Bambu Lab P1S 0.4 nozzle",
|
||||
"Bambu Lab X1E 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-66 @base",
|
||||
"inherits": "fdm_filament_pa",
|
||||
"from": "system",
|
||||
"filament_id": "OF02mnRc",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"INSLOGIC"
|
||||
],
|
||||
"filament_type": [
|
||||
"PA"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.08"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"1.75"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"250"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"280"
|
||||
],
|
||||
"filament_notes": [
|
||||
"Inslogic Nylon PA6/66 TDS (20.01.2025). Nozzle sizes 0.2/0.4/0.6mm. Nozzle 250-280C. Bed 30-50C. Printing speed 50-150mm/s. Cooling fan 100%. Textured PEI Sheet. Drying 80C for 48h or 110C for 3h. TDS linear speed is not converted to filament_max_volumetric_speed."
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-CF @base",
|
||||
"inherits": "fdm_filament_pa",
|
||||
"from": "system",
|
||||
"filament_id": "OFDkHRsW",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"INSLOGIC"
|
||||
],
|
||||
"filament_type": [
|
||||
"PA-CF"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.20"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"1.75"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"270"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"290"
|
||||
],
|
||||
"filament_notes": [
|
||||
"Inslogic PA6-CF TDS (20.01.2025). Nozzle sizes 0.4/0.6mm. Nozzle 270-290C at 50-100mm/s. Bed 50-70C. Cooling fan 30%. Textured PEI Sheet. Drying 90C for 12h or 110C for 4h. Carbon-fiber reinforced; abrasive filament. TDS linear speed is not converted to filament_max_volumetric_speed."
|
||||
],
|
||||
"compatible_printers": [],
|
||||
"required_nozzle_HRC": [
|
||||
"3"
|
||||
]
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PETG Pro @base",
|
||||
"inherits": "fdm_filament_pet",
|
||||
"from": "system",
|
||||
"filament_id": "OFBdBfg1",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"INSLOGIC"
|
||||
],
|
||||
"filament_type": [
|
||||
"PETG"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.26"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"1.75"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"230"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"270"
|
||||
],
|
||||
"filament_notes": [
|
||||
"Inslogic PETG Pro TDS (12.02.2024). Nozzle sizes 0.2/0.4/0.6mm. 230-240C at 50-100mm/s; 240-255C at 100-300mm/s; 255-270C at 300-600mm/s. Bed 60-70C. Cooling fan 100%. Bed: Smooth PEI Sheet / High Temperature Plate. Drying 50C for 4h. TDS linear speed is not converted to filament_max_volumetric_speed."
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PLA Pro @base",
|
||||
"inherits": "fdm_filament_pla",
|
||||
"from": "system",
|
||||
"filament_id": "OFvPeNbA",
|
||||
"instantiation": "false",
|
||||
"filament_vendor": [
|
||||
"INSLOGIC"
|
||||
],
|
||||
"filament_type": [
|
||||
"PLA"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.20"
|
||||
],
|
||||
"filament_diameter": [
|
||||
"1.75"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_soluble": [
|
||||
"0"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"195"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"220"
|
||||
],
|
||||
"filament_notes": [
|
||||
"Inslogic PLA Pro TDS (12.02.2024). Nozzle sizes 0.2/0.4/0.6mm. 195-205C at 50-100mm/s; 205-220C at 100-300mm/s. Bed 50-60C. Cooling fan 100%. Bed: Textured PEI Sheet / Cool Plate. Drying 50C for 4h. TDS linear speed is not converted to filament_max_volumetric_speed."
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-66 @Prusa CORE One",
|
||||
"inherits": "INSLOGIC PA6-66 @base",
|
||||
"from": "system",
|
||||
"setting_id": "Kuv4zCw8jhxYOzhB",
|
||||
"instantiation": "true",
|
||||
"hot_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"40"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"40"
|
||||
],
|
||||
"enable_overhang_bridge_fan": [
|
||||
"1"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"chamber_minimal_temperature": [
|
||||
"0"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"0"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; Filament-specific end gcode"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.99"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"100"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"3.5"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_multitool_ramming": [
|
||||
"0"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"10"
|
||||
],
|
||||
"filament_multitool_ramming_volume": [
|
||||
"10"
|
||||
],
|
||||
"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_retraction_minimum_travel": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_above": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_below": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"29"
|
||||
],
|
||||
"filament_toolchange_delay": [
|
||||
"0"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"265"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"170"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
""
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"265"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.4 nozzle",
|
||||
"Prusa CORE One L 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PA6-CF @Prusa CORE One",
|
||||
"inherits": "INSLOGIC PA6-CF @base",
|
||||
"from": "system",
|
||||
"setting_id": "ZCYSMR6VCj0IrYAF",
|
||||
"instantiation": "true",
|
||||
"hot_plate_temp": [
|
||||
"60"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"60"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"60"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"60"
|
||||
],
|
||||
"enable_overhang_bridge_fan": [
|
||||
"1"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"30"
|
||||
],
|
||||
"chamber_minimal_temperature": [
|
||||
"0"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"0"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; Filament-specific end gcode"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.99"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"100"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"3.5"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_multitool_ramming": [
|
||||
"0"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"10"
|
||||
],
|
||||
"filament_multitool_ramming_volume": [
|
||||
"10"
|
||||
],
|
||||
"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_retraction_minimum_travel": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_above": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_below": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"29"
|
||||
],
|
||||
"filament_toolchange_delay": [
|
||||
"0"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"280"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"170"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"30"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"30"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
""
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"280"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.4 nozzle",
|
||||
"Prusa CORE One L 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PETG Pro @Prusa CORE One",
|
||||
"inherits": "INSLOGIC PETG Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "yHUGQJhN0HUa7A5V",
|
||||
"instantiation": "true",
|
||||
"hot_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"65"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"65"
|
||||
],
|
||||
"enable_overhang_bridge_fan": [
|
||||
"1"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"chamber_minimal_temperature": [
|
||||
"0"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"0"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; Filament-specific end gcode"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.99"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"100"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"3.5"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_multitool_ramming": [
|
||||
"0"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"10"
|
||||
],
|
||||
"filament_multitool_ramming_volume": [
|
||||
"10"
|
||||
],
|
||||
"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_retraction_minimum_travel": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_above": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_below": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"29"
|
||||
],
|
||||
"filament_toolchange_delay": [
|
||||
"0"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"250"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"170"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
""
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"250"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.4 nozzle",
|
||||
"Prusa CORE One L 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "INSLOGIC PLA Pro @Prusa CORE One",
|
||||
"inherits": "INSLOGIC PLA Pro @base",
|
||||
"from": "system",
|
||||
"setting_id": "Qkscha5uuuFXbvbB",
|
||||
"instantiation": "true",
|
||||
"hot_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"55"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"55"
|
||||
],
|
||||
"enable_overhang_bridge_fan": [
|
||||
"1"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"100"
|
||||
],
|
||||
"chamber_minimal_temperature": [
|
||||
"0"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"0"
|
||||
],
|
||||
"slow_down_for_layer_cooling": [
|
||||
"1"
|
||||
],
|
||||
"close_fan_the_first_x_layers": [
|
||||
"0"
|
||||
],
|
||||
"filament_end_gcode": [
|
||||
"; Filament-specific end gcode"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.99"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"100"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"3.5"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"2"
|
||||
],
|
||||
"filament_deretraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"15"
|
||||
],
|
||||
"filament_multitool_ramming": [
|
||||
"0"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"10"
|
||||
],
|
||||
"filament_multitool_ramming_volume": [
|
||||
"10"
|
||||
],
|
||||
"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_retraction_minimum_travel": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_before_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_when_changing_layer": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_length": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_length_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_z_hop": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_above": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_lift_below": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retract_restart_extra_toolchange": [
|
||||
"nil"
|
||||
],
|
||||
"filament_retraction_speed": [
|
||||
"nil"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"29"
|
||||
],
|
||||
"filament_toolchange_delay": [
|
||||
"0"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
],
|
||||
"filament_wipe": [
|
||||
"nil"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"210"
|
||||
],
|
||||
"full_fan_speed_layer": [
|
||||
"0"
|
||||
],
|
||||
"idle_temperature": [
|
||||
"170"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"100"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"100"
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
""
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"210"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.4 nozzle",
|
||||
"Prusa CORE One L 0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -19,10 +19,7 @@
|
||||
<span class="plugin-search-icon" aria-hidden="true">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.5" y2="16.5"/></svg>
|
||||
</span>
|
||||
<span class="plugin-search-field">
|
||||
<span class="search-ghost" id="ghost" hidden aria-hidden="true"><span class="search-ghost-typed" id="ghostTyped"></span><span class="search-ghost-suffix" id="ghostSuffix"></span></span>
|
||||
<input class="plugin-search-input" id="q" type="text" placeholder="Search actions" spellcheck="false" autocomplete="off" aria-label="Search actions" />
|
||||
</span>
|
||||
<input class="plugin-search-input" id="q" type="text" placeholder="Search actions" spellcheck="false" autocomplete="off" aria-label="Search actions" />
|
||||
<button class="plugin-search-clear" id="clear" type="button" title="Clear" aria-label="Clear search" hidden>
|
||||
<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><line x1="5" y1="5" x2="11" y2="11"/><line x1="11" y1="5" x2="5" y2="11"/></svg>
|
||||
</button>
|
||||
@@ -30,7 +27,6 @@
|
||||
<div class="dial-count" id="count" hidden></div>
|
||||
</div>
|
||||
<div class="dial-list" id="list"></div>
|
||||
<div class="dial-detail" id="detail" hidden></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,427 +13,33 @@ vm.runInContext(fs.readFileSync(__dirname + "/speeddial.js", "utf8"), ctx);
|
||||
assert.equal(typeof ctx.parseId, "undefined", "opaque action ids must never be parsed");
|
||||
|
||||
const duplicateActions = [
|
||||
{ id: "0123456789abcdef", title: "Repair", source: "Mesh Tools" },
|
||||
{ id: "fedcba9876543210", title: "Repair", source: "Mesh Tools" }
|
||||
{ id: "0123456789abcdef", title: "Repair", source: "Mesh Tools" },
|
||||
{ id: "fedcba9876543210", title: "Repair", source: "Mesh Tools" }
|
||||
];
|
||||
assert.equal(
|
||||
ctx.actionLabel(duplicateActions[0], duplicateActions),
|
||||
"Repair from Mesh Tools (0123456789abcdef)",
|
||||
"duplicate labels should use the opaque id without interpreting its contents"
|
||||
ctx.actionLabel(duplicateActions[0], duplicateActions),
|
||||
"Repair from Mesh Tools (0123456789abcdef)",
|
||||
"duplicate labels should use the opaque id without interpreting its contents"
|
||||
);
|
||||
|
||||
assert.equal(ctx.shouldRenderActionList(""), false, "an empty search shows the recents+pool list");
|
||||
assert.equal(ctx.shouldRenderActionList(" "), false, "whitespace-only search shows the recents+pool list");
|
||||
assert.equal(ctx.shouldRenderActionList(""), false, "an empty search keeps the action list hidden");
|
||||
assert.equal(ctx.shouldRenderActionList(" "), false, "whitespace-only search keeps the action list hidden");
|
||||
assert.equal(ctx.shouldRenderActionList("r"), true, "typing starts rendering matching actions");
|
||||
|
||||
// commandList: an empty query shows recents first, then every other action; a typed query filters all.
|
||||
assert.deepEqual(ctx.commandList(duplicateActions, [], ""), duplicateActions,
|
||||
"empty query + no recents shows the whole action pool");
|
||||
assert.deepEqual(ctx.commandList(duplicateActions, [duplicateActions[0]], ""),
|
||||
[duplicateActions[0], duplicateActions[1]],
|
||||
"empty query shows the recent first, then the remaining actions");
|
||||
assert.deepEqual(ctx.commandList(duplicateActions, [], "rep"), duplicateActions,
|
||||
"a typed query filters actions (both identical titles match) instead of showing recents");
|
||||
|
||||
// filterTabs (tab phase): an empty query keeps the whole list; a typed query filters by title/id.
|
||||
const tabOptions = [
|
||||
{ id: "home", title: "Home" },
|
||||
{ id: "prepare", title: "Prepare" },
|
||||
{ id: "monitor", title: "Device" },
|
||||
{ id: "project", title: "Project" }
|
||||
];
|
||||
assert.deepEqual(ctx.filterTabs(tabOptions, ""), tabOptions,
|
||||
"empty query keeps the whole tab list");
|
||||
assert.equal(ctx.filterTabs(tabOptions, "prep").length, 1,
|
||||
"a typed query filters tabs by title");
|
||||
assert.equal(ctx.filterTabs(tabOptions, "Device").length, 1,
|
||||
"a typed query matches a tab title");
|
||||
assert.deepEqual(ctx.filterTabs(tabOptions, "zzz"), [],
|
||||
"a typed query with no match returns an empty list");
|
||||
|
||||
// tabTitle: pages added with an empty title (e.g. MainFrame's Home tab) fall back to the id.
|
||||
assert.equal(ctx.tabTitle({ id: "home", title: "" }), "Home",
|
||||
"an empty title falls back to the title-cased id");
|
||||
assert.equal(ctx.tabTitle({ id: "home" }), "Home",
|
||||
"a missing title falls back to the title-cased id");
|
||||
assert.equal(ctx.tabTitle({ id: "prepare", title: "Prepare" }), "Prepare",
|
||||
"a populated title is kept as-is");
|
||||
assert.equal(ctx.tabTitle({ id: "prepare", title: " Prepare" }), "Prepare",
|
||||
"a stray leading space in a tab title is trimmed so the label shows cleanly");
|
||||
assert.equal(ctx.filterTabs([{ id: "home", title: "" }], "home").length, 1,
|
||||
"an untitled tab still matches a typed query via the id/title fallback");
|
||||
assert.equal(ctx.filterTabs([{ id: "prepare", title: " Prepare" }], "prepare").length, 1,
|
||||
"a leading-space tab title still matches a typed query");
|
||||
|
||||
// The main phase is ONE pool: commands/plugins/settings are all actions, ranked by relevance
|
||||
// (no group headers, no actions-vs-settings discrimination).
|
||||
const pool = [
|
||||
{ id: "c1", title: "Layer Height", source: "Quality", group: "Quality : Layers", input: "" },
|
||||
{ id: "s1", title: "Go to layer (percent)", source: "OrcaSlicer", group: "Commands", input: "percent" },
|
||||
{ id: "c2", title: "Top Surface Layers", source: "Quality", group: "Quality : Layers", input: "" }
|
||||
];
|
||||
assert.deepEqual(ctx.searchActions(pool, ""), pool, "an empty query returns the pool unchanged");
|
||||
assert.equal(ctx.searchActions(pool, "zzz").length, 0, "a query with no match returns nothing");
|
||||
// "layer" matches multiple; the exact-titled action ranks above the loosely-matching command.
|
||||
assert.equal(ctx.searchActions(pool, "layer")[0].id, "c1",
|
||||
"a title-exact match ranks above a partial match");
|
||||
assert.equal(ctx.searchActions(pool, "layer").length >= 2, true,
|
||||
"both a setting and a command match the same query in the same list");
|
||||
assert.equal(ctx.searchActions(pool, "surface")[0].id, "c2",
|
||||
"a later-but-precise match still ranks by relevance, not by pool type");
|
||||
|
||||
// A perfect match (the needle as one contiguous run) outranks a fuzzy match of the same field - and a
|
||||
// contiguous GROUP/header hit ("Recent Projects") beats a scattered fuzzy TITLE hit ("Retraction Length"),
|
||||
// which is what the old flat title-bonus ranking got backwards.
|
||||
const perfectPool = [
|
||||
{ id: "set", title: "Retraction Length", source: "Process : Quality : Retraction", group: "", input: "" },
|
||||
{ id: "recent", title: "myproject.3mf", source: "/home/me/projects/myproject.3mf", group: "Recent Projects", input: "" }
|
||||
];
|
||||
assert.equal(ctx.searchActions(perfectPool, "recent")[0].id, "recent",
|
||||
"a contiguous header/group match ranks above a scattered fuzzy title match");
|
||||
// Within a perfect match, the row-name (title) outranks the header (group): the action whose TITLE
|
||||
// contains the needle perfectly beats the action whose GROUP does, both being contiguous matches.
|
||||
const titleFirstPool = [
|
||||
{ id: "grp", title: "Delete Selected", source: "OrcaSlicer", group: "Object", input: "" },
|
||||
{ id: "t", title: "Object Preview", source: "OrcaSlicer", group: "View", input: "" }
|
||||
];
|
||||
assert.equal(ctx.searchActions(titleFirstPool, "object")[0].id, "t",
|
||||
"a perfect title match ranks above an equally-perfect group match");
|
||||
|
||||
// Highlighting: the needle is matched as a whole word / most-contiguous run, so "orient" lights up the
|
||||
// whole word in "Auto-Orient" instead of the stray "o" of "Auto" plus "rient" (greedy-leftmost).
|
||||
const orientPool = [
|
||||
{ id: "ao", title: "Auto-Orient", source: "OrcaSlicer", group: "Object", input: "" }
|
||||
];
|
||||
ctx.searchActions(orientPool, "orient");
|
||||
assert.deepEqual(ctx.matchIndex.ao.title, [[5, 11]],
|
||||
"a whole-word match highlights the full word, not a scattered fuzzy pick");
|
||||
|
||||
// A fuzzy source-only match with a very late start still counts, even though its score is negative;
|
||||
// the old `score < 0` sentinel mistook it for "no field matched" and dropped the action.
|
||||
const negativePool = [
|
||||
{ id: "n", title: "Unrelated", source: "o" + "x".repeat(200) + "rnt", group: "", input: "" }
|
||||
];
|
||||
assert.equal(ctx.searchActions(negativePool, "ornt").length, 1,
|
||||
"a low-score fuzzy match is not mistaken for no match");
|
||||
|
||||
// A setting whose displayed title is the page row label keeps the descriptive ConfigOptionDef name as
|
||||
// a search-only alias, so the old wording still finds it without being shown.
|
||||
const aliasPool = [
|
||||
{ id: "rev", title: "Reverse on even", full_label: "Overhang reversal", source: "Process : Quality : Overhangs", group: "", input: "" }
|
||||
];
|
||||
assert.deepEqual(ctx.searchActions(aliasPool, "overhang reversal").map(function (a) { return a.id; }), ["rev"],
|
||||
"the descriptive full_label is searchable even though the title shows the row label");
|
||||
assert.equal(ctx.matchIndex.rev.title, null,
|
||||
"an alias-only match does not highlight the displayed title");
|
||||
assert.deepEqual(ctx.searchActions(aliasPool, "reversal").map(function (a) { return a.id; }), ["rev"],
|
||||
"a token that exists only in the full_label still matches");
|
||||
|
||||
// Multi-token cross-field search: each whitespace-separated word must match SOME searchable field,
|
||||
// but different words may match different fields. "inner" is the title while "speed" and
|
||||
// "acceleration" live in the source breadcrumb, so the query as a whole is never contiguous in one
|
||||
// field - the old single-needle match found nothing for this.
|
||||
const crossPool = [
|
||||
{ id: "acc", title: "Inner wall", source: "Process : Speed : Acceleration", group: "", input: "" },
|
||||
{ id: "spd", title: "Inner wall", source: "Process : Speed : Other layers speed", group: "", input: "" },
|
||||
{ id: "other", title: "Outer wall", source: "Process : Quality : Walls", group: "", input: "" }
|
||||
];
|
||||
assert.deepEqual(
|
||||
ctx.searchActions(crossPool, "speed acceleration inner").map(function (a) { return a.id; }),
|
||||
["acc"],
|
||||
"words may match different fields and every word is required"
|
||||
);
|
||||
assert.deepEqual(
|
||||
ctx.searchActions(crossPool, "speed inner").map(function (a) { return a.id; }).sort(),
|
||||
["acc", "spd"],
|
||||
"a two-word title+source query matches every setting under Speed"
|
||||
);
|
||||
assert.deepEqual(
|
||||
ctx.searchActions(crossPool, "quality inner").map(function (a) { return a.id; }),
|
||||
[],
|
||||
"an action is dropped when any one word matches no field"
|
||||
);
|
||||
// Highlighting merges the per-field ranges the tokens produced.
|
||||
ctx.searchActions(crossPool, "speed acceleration inner");
|
||||
assert.deepEqual(ctx.matchIndex.acc.title, [[0, 5]], "the title token highlights in the title");
|
||||
assert.deepEqual(ctx.matchIndex.acc.source, [[10, 15], [18, 30]], "each path token highlights in the breadcrumb");
|
||||
|
||||
assert.deepEqual(ctx.queryTokens(" Speed Acceleration "), ["speed", "acceleration"],
|
||||
"a query splits into normalized whitespace-separated tokens");
|
||||
assert.deepEqual(ctx.queryTokens(""), [], "an empty query has no tokens");
|
||||
|
||||
// Inline completion: the LAST token is completed to a word in the top-ranked result, name first then
|
||||
// breadcrumb. Pure - the caller appends `suffix`.
|
||||
const compPool = [
|
||||
{ id: "acc", title: "Inner wall", source: "Process : Speed : Acceleration", group: "", input: "" },
|
||||
{ id: "smooth", title: "Smooth", source: "Process : Speed : Other layers speed", group: "", input: "" }
|
||||
];
|
||||
assert.equal(ctx.completionFor("speed acc", ctx.searchActions(compPool, "speed acc")).suffix, "eleration",
|
||||
"the last token completes to the next word in the breadcrumb");
|
||||
assert.equal(ctx.completionFor("inner w", ctx.searchActions(compPool, "inner w")).suffix, "all",
|
||||
"the title is preferred over the breadcrumb for completion");
|
||||
assert.equal(ctx.completionFor("inner wall", ctx.searchActions(compPool, "inner wall")), null,
|
||||
"an already-complete word has nothing to add");
|
||||
assert.equal(ctx.completionFor("", compPool), null, "an empty query has no completion");
|
||||
assert.equal(ctx.completionFor("zzz", ctx.searchActions(compPool, "zzz")), null,
|
||||
"a query with no match has no completion");
|
||||
|
||||
// actionCategory: a command/dynamic action's group is its category; a setting uses the top-level
|
||||
// source segment; every plugin shares one header; a category-less action falls back to "Other".
|
||||
assert.equal(ctx.actionCategory({ id: "c", group: "Help", source: "OrcaSlicer", kind: "command" }), "Help",
|
||||
"a command's group is its category");
|
||||
assert.equal(ctx.actionCategory({ id: "s", group: "", source: "Process : Quality : Layers", kind: "command" }), "Process",
|
||||
"a setting's category is the top-level source segment");
|
||||
assert.equal(ctx.actionCategory({ id: "s", group: "", source: "Filament : Cooling", kind: "command" }), "Filament",
|
||||
"a Filament setting groups under Filament");
|
||||
assert.equal(ctx.actionCategory({ id: "plugin_script_action:Foo:bar.py", group: "", source: "Gcode Optimizer", kind: "plugin" }), "Plugins",
|
||||
"every plugin shares one Plugins header");
|
||||
assert.equal(ctx.actionCategory({ id: "x", group: "", source: "", kind: "command" }), "Other",
|
||||
"a category-less action falls back to Other");
|
||||
|
||||
// groupActions: bucket by category, order the groups alphabetically, keep the incoming order within
|
||||
// each group (the pool arrives frecency-sorted).
|
||||
const groupPool = [
|
||||
{ id: "q1", title: "Q1", source: "Quality", group: "Quality", kind: "command" },
|
||||
{ id: "h1", title: "H1", source: "OrcaSlicer", group: "Help", kind: "command" },
|
||||
{ id: "p1", title: "P1", source: "Process : A", group: "", kind: "command" },
|
||||
{ id: "h2", title: "H2", source: "OrcaSlicer", group: "Help", kind: "command" }
|
||||
];
|
||||
assert.deepEqual(ctx.groupActions(groupPool).map(function (a) { return a.id; }), ["h1", "h2", "p1", "q1"],
|
||||
"groups are alphabetical and each group keeps its incoming order");
|
||||
|
||||
// commandList (the main-phase list) delegates to the ranked search for a typed query and returns
|
||||
// recents + the category-grouped pool for an empty query.
|
||||
const mixed = [
|
||||
{ id: "cmd", title: "Slice", source: "OrcaSlicer", group: "Commands", kind: "command", input: "" },
|
||||
{ id: "set", title: "Sparse Infill Density", source: "Quality", group: "Quality", kind: "command", input: "" }
|
||||
];
|
||||
assert.equal(ctx.commandList(mixed, [], "sli")[0].id, "cmd",
|
||||
"a typed query keeps the relevance-ranked action list (best match first)");
|
||||
assert.deepEqual(ctx.commandList(mixed, mixed.slice(0, 1), "").map(function (a) { return a.id; }), ["cmd", "set"],
|
||||
"an empty query shows the recents first and de-dupes them out of the tail");
|
||||
assert.deepEqual(ctx.commandList(mixed, [], "").map(function (a) { return a.id; }), ["cmd", "set"],
|
||||
"empty query + no recents shows the whole action pool");
|
||||
assert.deepEqual(ctx.commandList(mixed, [mixed[1]], "").map(function (a) { return a.id; }), ["set", "cmd"],
|
||||
"the recent is hoisted above the alphabetically-ordered groups");
|
||||
|
||||
// commandSections: "Recent" (when recents exist) plus one header per category in the grouped list;
|
||||
// a typed query or an empty list yields no headers. Uses a computed grouped list so the recents
|
||||
// hoist and the category ordering are exercised together.
|
||||
const sectionPool = [
|
||||
{ id: "cmd", title: "Slice", source: "OrcaSlicer", group: "Commands", kind: "command" },
|
||||
{ id: "help", title: "Shortcuts", source: "OrcaSlicer", group: "Help", kind: "command" },
|
||||
{ id: "set", title: "Infill", source: "Quality", group: "Quality", kind: "command" }
|
||||
];
|
||||
const sectionList = ctx.commandList(sectionPool, [sectionPool[0]], "");
|
||||
assert.deepEqual(sectionList.map(function (a) { return a.id; }), ["cmd", "help", "set"],
|
||||
"recents are hoisted, then the rest is grouped alphabetically (Commands, Help, Quality)");
|
||||
assert.deepEqual(ctx.commandSections(sectionList, 1, ""), { 0: "Recent", 1: "Help", 2: "Quality" },
|
||||
"recents + grouped actions get one header per category");
|
||||
assert.deepEqual(ctx.commandSections([sectionPool[0]], 1, ""), { 0: "Recent" },
|
||||
"a list that is all recents gets only the Recent header");
|
||||
assert.deepEqual(ctx.commandSections(ctx.commandList(sectionPool, [], ""), 0, ""),
|
||||
{ 0: "Commands", 1: "Help", 2: "Quality" },
|
||||
"with no recents the grouped list still gets category headers");
|
||||
assert.equal(ctx.commandSections(sectionList, 1, "sli"), null,
|
||||
"a typed query has no section headers");
|
||||
assert.equal(ctx.commandSections([], 0, ""), null,
|
||||
"an empty list has no section headers");
|
||||
|
||||
// selectedActionId: resolves the active list (recents+pool for an empty query, filtered list otherwise).
|
||||
assert.equal(
|
||||
ctx.selectedActionId({ zone: "list", i: 0 }, ctx.commandList(duplicateActions, [], ""), []),
|
||||
"0123456789abcdef",
|
||||
"Enter with an empty query resolves the first action in the recents+pool list"
|
||||
ctx.selectedActionId({ zone: "list", i: 0 }, duplicateActions, [], ""),
|
||||
null,
|
||||
"Enter with an empty query must not resolve to an action the blank list never showed"
|
||||
);
|
||||
assert.equal(
|
||||
ctx.selectedActionId({ zone: "list", i: 0 }, ctx.commandList(duplicateActions, [], "rep"), []),
|
||||
"0123456789abcdef",
|
||||
"a typed query resolves the list selection"
|
||||
ctx.selectedActionId({ zone: "list", i: 0 }, duplicateActions, [], "rep"),
|
||||
"0123456789abcdef",
|
||||
"a typed query resolves the list selection"
|
||||
);
|
||||
assert.equal(
|
||||
ctx.selectedActionId({ zone: "list", i: 0 }, ctx.commandList(duplicateActions, [duplicateActions[0]], ""), []),
|
||||
"0123456789abcdef",
|
||||
"Enter with an empty query resolves the recent entry"
|
||||
ctx.selectedActionId({ zone: "fav", i: 0 }, duplicateActions, ["fedcba9876543210"], ""),
|
||||
"fedcba9876543210",
|
||||
"favourites stay runnable with an empty query - the fav bar is always visible"
|
||||
);
|
||||
assert.equal(
|
||||
ctx.selectedActionId({ zone: "fav", i: 0 }, duplicateActions, ["fedcba9876543210"]),
|
||||
"fedcba9876543210",
|
||||
"favourites stay runnable with an empty query - the fav bar is always visible"
|
||||
);
|
||||
|
||||
// Fav quick-launch slots: digit 1..9 -> index 0..8, digit 0 -> index 9 (the 10th), else -1.
|
||||
assert.equal(ctx.favIndexForDigit("1"), 0, "digit 1 maps to the 1st favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("9"), 8, "digit 9 maps to the 9th favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("0"), 9, "digit 0 maps to the 10th (last) favourite slot");
|
||||
assert.equal(ctx.favIndexForDigit("x"), -1, "non-digit keys are not a slot");
|
||||
assert.equal(ctx.favIndexForDigit(""), -1, "an empty key is not a slot");
|
||||
|
||||
// Badge label per 0-based index: 0..8 -> "1".."9", index 9 -> "0", out of range -> null.
|
||||
assert.equal(ctx.favSlotForIndex(0), "1", "index 0 shows badge 1");
|
||||
assert.equal(ctx.favSlotForIndex(8), "9", "index 8 shows badge 9");
|
||||
assert.equal(ctx.favSlotForIndex(9), "0", "index 9 shows badge 0 (the 10th slot)");
|
||||
assert.equal(ctx.favSlotForIndex(10), null, "index 10 is beyond the cap");
|
||||
assert.equal(ctx.favSlotForIndex(-1), null, "negative index is not a slot");
|
||||
assert.equal(ctx.K_FAV_LIMIT, 10, "the slot count matches the quick-launch cap");
|
||||
|
||||
// favDigitFromEvent: prefer the physical code (so macOS Option+digit still maps even though e.key
|
||||
// is the composed symbol), and fall back to e.key for keyboards/synthetic events without a code.
|
||||
assert.equal(ctx.favDigitFromEvent({ code: "Digit1", key: "¡" }), "1", "Digit1 wins over a composed key");
|
||||
assert.equal(ctx.favDigitFromEvent({ code: "Digit0", key: "0" }), "0", "Digit0 is a physical digit");
|
||||
assert.equal(ctx.favDigitFromEvent({ code: "Numpad7", key: "7" }), "7", "numpad digits count");
|
||||
assert.equal(ctx.favDigitFromEvent({ code: "", key: "3" }), "3", "a missing code falls back to key");
|
||||
assert.equal(ctx.favDigitFromEvent({ key: "a" }), "a", "non-digit input is passed through (maps to -1)");
|
||||
assert.equal(ctx.favDigitFromEvent(null), "", "a null event yields no digit");
|
||||
|
||||
// nextSel: arrow-nav wrapping. Down wraps at the list bottom to the first row; Up wraps at the
|
||||
// list top to the last row ONLY when there's no fav bar above (else it goes to the fav bar).
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 2 }, "ArrowDown", 3, 0), { zone: "list", i: 0 },
|
||||
"ArrowDown at the last row wraps to the first row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 1 }, "ArrowDown", 3, 0), { zone: "list", i: 2 },
|
||||
"ArrowDown in the middle advances by one");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 3, 0), { zone: "list", i: 2 },
|
||||
"ArrowUp at the first row with no fav bar wraps to the last row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 3, 2), { zone: "fav", i: 0 },
|
||||
"ArrowUp at the first row with a fav bar goes to the fav bar (unchanged)");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 2 }, "ArrowUp", 3, 0), { zone: "list", i: 1 },
|
||||
"ArrowUp in the middle moves up by one");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "fav", i: 1 }, "ArrowDown", 3, 2), { zone: "list", i: 0 },
|
||||
"ArrowDown from the fav bar lands on the first list row");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowDown", 1, 0), { zone: "list", i: 0 },
|
||||
"a single-row list never wraps off the end");
|
||||
assert.deepEqual(ctx.nextSel({ zone: "list", i: 0 }, "ArrowUp", 1, 0), { zone: "list", i: 0 },
|
||||
"ArrowUp on the only row stays put");
|
||||
|
||||
// Windowed list reveal: how many rows must be materialized to cover `fromIndex` plus `size` more,
|
||||
// clamped to the total. Drives the "render the next window on scroll / arrow-nav" append.
|
||||
assert.equal(ctx.revealTarget(100, 0, 100), 100, "covers the whole list when the window reaches the end");
|
||||
assert.equal(ctx.revealTarget(100, 60, 100), 100, "clamps to the total at the tail");
|
||||
assert.equal(ctx.revealTarget(30, 5, 100), 30, "a short list is fully covered");
|
||||
assert.equal(ctx.revealTarget(100, 5, 50), 55, "reveals exactly fromIndex + size");
|
||||
assert.equal(ctx.revealTarget(100, -5, 50), 50, "negative start is clamped to the first row");
|
||||
assert.equal(ctx.revealTarget(200, 50, 100), 150, "a scroll viewpoint reveals a window past the current rows");
|
||||
assert.equal(ctx.revealTarget(10, 0, 50), 10, "a list shorter than one window stays fully materialized");
|
||||
|
||||
// spacerHeight: un-rendered rows (44px) plus un-rendered section headers (30px), never negative.
|
||||
assert.equal(ctx.spacerHeight(100, 50, 0, 0), 50 * 44, "the tail rows reserve their full height");
|
||||
assert.equal(ctx.spacerHeight(100, 100, 0, 0), 0, "a fully-rendered list needs no spacer");
|
||||
assert.equal(ctx.spacerHeight(100, 50, 3, 1), 50 * 44 + 2 * 30, "pending section headers reserve their height too");
|
||||
assert.equal(ctx.spacerHeight(10, 0, 2, 0), 10 * 44 + 2 * 30, "a short list still reserves its headers");
|
||||
assert.equal(ctx.spacerHeight(0, 0, 0, 0), 0, "an empty list has no spacer");
|
||||
assert.equal(ctx.spacerHeight(10, 20, 0, 5), 0, "over-rendered counters clamp to zero");
|
||||
|
||||
// visibleFavourites: the quick-bar drops pins whose action no longer exists (plugin unloaded,
|
||||
// command removed) and collapses duplicate ids, keeping the persisted pin order.
|
||||
assert.deepEqual(ctx.visibleFavourites(["a", "b", "c"], [{ id: "a" }, { id: "b" }]),
|
||||
["a", "b"], "a pin with no live action is dropped from the quick-bar");
|
||||
assert.deepEqual(ctx.visibleFavourites(["b", "a", "b"], [{ id: "a" }, { id: "b" }]),
|
||||
["b", "a"], "duplicate pins collapse to the first occurrence");
|
||||
assert.deepEqual(ctx.visibleFavourites([], [{ id: "a" }]), [], "no pins renders an empty quick-bar");
|
||||
assert.deepEqual(ctx.visibleFavourites(["a"], []), [], "a stale config with no actions renders nothing");
|
||||
|
||||
// actionIcon: the SVG base name for a tile's pictogram, or "" when the action has none (blank tile).
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Slice", icon: "media_play" }), "media_play",
|
||||
"an action's icon base name is returned verbatim");
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Go to tab...", icon: "" }), "",
|
||||
"an empty icon renders a blank tile");
|
||||
assert.equal(ctx.actionIcon({ id: "x", title: "Plugin action" }), "",
|
||||
"a missing icon field renders a blank tile");
|
||||
assert.equal(ctx.actionIcon(null), "",
|
||||
"a null action (tab row) renders a blank tile");
|
||||
|
||||
// tileIcon: the base name a tile renders - the action's own icon when present, else the placeholder.
|
||||
assert.equal(ctx.tileIcon({ id: "x", title: "Slice", icon: "media_play" }), "media_play",
|
||||
"an action with an icon keeps it");
|
||||
assert.equal(ctx.tileIcon({ id: "x", title: "Go to tab...", icon: "" }), "action_default",
|
||||
"an empty icon falls back to the placeholder");
|
||||
assert.equal(ctx.tileIcon({ id: "x", title: "Plugin action" }), "action_default",
|
||||
"a missing icon falls back to the placeholder");
|
||||
assert.equal(ctx.DEFAULT_ICON, "action_default", "the placeholder is the dedicated default glyph");
|
||||
assert.ok(fs.existsSync(__dirname + "/../../../images/action_default.svg"),
|
||||
"the placeholder SVG ships alongside the page's other icons");
|
||||
|
||||
// needsModeSwitch: a setting is gated only when its required mode outranks the user's current mode.
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "advanced" }, "simple"), true, "Advanced is gated in Simple mode");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "expert" }, "simple"), true, "Expert is gated in Simple mode");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "expert" }, "advanced"), true, "Expert is gated in Advanced mode");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "develop" }, "expert"), true, "Developer is gated in Expert mode");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "simple" }, "simple"), false, "a Simple setting is not gated");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "advanced" }, "advanced"), false, "an Advanced setting is not gated in Advanced mode");
|
||||
assert.equal(ctx.needsModeSwitch({ mode: "develop" }, "develop"), false, "a Developer setting is not gated in Developer mode");
|
||||
assert.equal(ctx.needsModeSwitch({}, "simple"), false, "a command with no mode is never gated");
|
||||
|
||||
// MODE_RANK must match the C++ ConfigOptionMode order (comSimple < comAdvanced < comExpert < comDevelop).
|
||||
assert.deepEqual(ctx.MODE_RANK, { simple: 0, advanced: 1, expert: 2, develop: 3 },
|
||||
"mode rank matches the C++ ConfigOptionMode order");
|
||||
|
||||
// modeBadge: the tag text for gated settings, empty once the setting is available.
|
||||
assert.equal(ctx.modeBadge({ mode: "advanced" }, "simple"), "Advanced", "Advanced badge text");
|
||||
assert.equal(ctx.modeBadge({ mode: "expert" }, "simple"), "Expert", "Expert badge text");
|
||||
assert.equal(ctx.modeBadge({ mode: "develop" }, "simple"), "Developer", "Developer badge text");
|
||||
assert.equal(ctx.modeBadge({ mode: "advanced" }, "advanced"), "", "no badge when the mode already matches");
|
||||
|
||||
// modeFilterFromQuery: whole-word, case-insensitive mode keywords -> internal mode values. "developer"
|
||||
// maps to the internal "develop"; "simple" is deliberately not a keyword; a prefix is not a match.
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("advanced"), ["advanced"], "Advanced is a mode keyword");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("Expert"), ["expert"], "the keyword is case-insensitive");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("developer"), ["develop"], "Developer maps to the develop value");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("develop"), ["develop"], "the internal develop spelling also works");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("expert retraction"), ["expert"], "a keyword is found among other text");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("advanced expert"), ["advanced", "expert"], "multiple keywords are deduped in order");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("advanced advanced"), ["advanced"], "a repeated keyword is deduped");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("simple"), [], "Simple is not a mode keyword");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery("advance"), [], "a mode-word prefix is not a whole-word match");
|
||||
assert.deepEqual(ctx.modeFilterFromQuery(""), [], "an empty query names no mode");
|
||||
|
||||
// searchActions mode union: a mode keyword keeps the normal text matches AND appends every setting
|
||||
// requiring that mode. Not a filter - a Simple setting literally named "Advanced..." still shows, and
|
||||
// commands (mode "simple") are never pulled in by a keyword.
|
||||
const modePool = [
|
||||
{ id: "a1", title: "Top Surface Layers", source: "Quality", group: "Quality : Layers", mode: "advanced" },
|
||||
{ id: "a2", title: "Advanced Detection", source: "Quality", group: "Quality", mode: "simple" },
|
||||
{ id: "a3", title: "Retraction Length", source: "Process", group: "Process : Quality", mode: "expert" },
|
||||
{ id: "a4", title: "Slice", source: "OrcaSlicer", group: "Commands", mode: "simple" }
|
||||
];
|
||||
var adv = ctx.searchActions(modePool, "advanced");
|
||||
assert.deepEqual(adv.map(function (a) { return a.id; }), ["a2", "a1"],
|
||||
"a text match (a2) ranks above the mode-only setting (a1), and no expert/command leaks in");
|
||||
var expert = ctx.searchActions(modePool, "expert");
|
||||
assert.deepEqual(expert.map(function (a) { return a.id; }), ["a3"], "the expert keyword pulls in the expert setting");
|
||||
assert.deepEqual(ctx.searchActions(modePool, "developer"), [], "no developer settings means no mode extras");
|
||||
assert.equal(ctx.searchActions(modePool, "retraction")[0].id, "a3",
|
||||
"a query with no mode keyword is unaffected by the mode union");
|
||||
assert.equal(ctx.searchActions([{ id: "both", title: "Advanced", source: "Quality", group: "", mode: "advanced" }], "advanced").length,
|
||||
1, "a setting that both matches text and requires the mode appears exactly once");
|
||||
|
||||
// actionHasWiki: the footer's wiki link/F1 path is offered only when the action carries a wiki flag.
|
||||
assert.equal(ctx.actionHasWiki({ id: "x", wiki: true }), true, "a wiki-flagged setting offers the wiki action");
|
||||
assert.equal(ctx.actionHasWiki({ id: "x", wiki: false }), false, "a setting without a wiki path offers nothing");
|
||||
assert.equal(ctx.actionHasWiki({ id: "x" }), false, "a missing wiki field offers nothing");
|
||||
assert.equal(ctx.actionHasWiki(null), false, "no action selected offers nothing");
|
||||
|
||||
// actionHasDetail: the footer strip appears only when the highlighted action has a description or
|
||||
// wiki link; selecting a plain command hides it.
|
||||
assert.equal(ctx.actionHasDetail({ id: "a", desc: "Layer height" }), true, "a description shows the footer");
|
||||
assert.equal(ctx.actionHasDetail({ id: "a", wiki: true }), true, "a wiki link shows the footer");
|
||||
assert.equal(ctx.actionHasDetail({ id: "a", desc: "Layer height", wiki: true }), true, "both show the footer");
|
||||
assert.equal(ctx.actionHasDetail({ id: "a", desc: "" }), false, "an empty description hides the footer");
|
||||
assert.equal(ctx.actionHasDetail({ id: "a", desc: "", wiki: false }), false, "empty description and false wiki hide the footer");
|
||||
assert.equal(ctx.actionHasDetail({ id: "a" }), false, "an action with neither hides the footer");
|
||||
assert.equal(ctx.actionHasDetail(null), false, "no selected action hides the footer");
|
||||
|
||||
// detailToggleVisible: the expand/collapse arrow is offered only when there is a description to
|
||||
// toggle. Collapse is a global preference, so the control stays for short tooltips too.
|
||||
assert.equal(ctx.detailToggleVisible({ id: "a", desc: "Layer height" }), true, "a description offers the toggle");
|
||||
assert.equal(ctx.detailToggleVisible({ id: "a", desc: "" }), false, "an empty description offers no toggle");
|
||||
assert.equal(ctx.detailToggleVisible({ id: "a", wiki: true }), false, "a wiki-only action has nothing to collapse");
|
||||
assert.equal(ctx.detailToggleVisible({ id: "a" }), false, "an action with no description offers no toggle");
|
||||
assert.equal(ctx.detailToggleVisible(null), false, "no selected action offers no toggle");
|
||||
|
||||
// stateFromPayload: the footer expansion is a persisted global and defaults to expanded when the
|
||||
// C++ payload omits it (first run / older config).
|
||||
assert.equal(ctx.stateFromPayload({}).tooltipExpanded, true, "expansion defaults to true when absent");
|
||||
assert.equal(ctx.stateFromPayload({ tooltip_expanded: false }).tooltipExpanded, false, "a collapsed payload is honored");
|
||||
assert.equal(ctx.stateFromPayload({ tooltip_expanded: true }).tooltipExpanded, true, "an expanded payload is honored");
|
||||
|
||||
console.log("ok");
|
||||
|
||||
@@ -1,549 +1,225 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--orca-font, "Segoe UI", sans-serif);
|
||||
font-size: 13px;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
background: var(--bg, var(--orca-bg, #fff));
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--orca-font, "Segoe UI", sans-serif);
|
||||
font-size: 13px;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
background: var(--bg, var(--orca-bg, #fff));
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.launcher {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
/* why: no height cap here - the launcher reports its true natural height to C++, which
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
overflow: hidden;
|
||||
/* why: no height cap here - the launcher reports its true natural height to C++, which
|
||||
sizes the popup to match (HTML is source of truth). The list's own max-height is what
|
||||
bounds growth; capping the launcher would make the measurement circular. */
|
||||
}
|
||||
|
||||
.fav-bar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 10px;
|
||||
border-bottom: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
overflow-x: auto;
|
||||
/* scroll horizontally once favourites overflow the row */
|
||||
scrollbar-width: thin;
|
||||
/* why: keep scrollIntoView (arrow-nav) from scrolling the first/last tile flush to the edge,
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 10px;
|
||||
border-bottom: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
overflow-x: auto; /* scroll horizontally once favourites overflow the row */
|
||||
scrollbar-width: thin;
|
||||
/* why: keep scrollIntoView (arrow-nav) from scrolling the first/last tile flush to the edge,
|
||||
which would clip its selected outline. Matches the 10px horizontal padding. */
|
||||
scroll-padding-inline: 10px;
|
||||
scroll-padding-inline: 10px;
|
||||
}
|
||||
|
||||
.fav-bar[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fav-bar[hidden] { display: none; }
|
||||
/* Name of the selected favourite, above the bar; left-aligned to the tiles' 10px inset. */
|
||||
.fav-eyebrow {
|
||||
flex: 0 0 auto;
|
||||
padding: 8px 10px 0;
|
||||
}
|
||||
|
||||
.fav-eyebrow { flex: 0 0 auto; padding: 8px 10px 0; }
|
||||
.fav-tile {
|
||||
flex: 0 0 auto;
|
||||
/* keep tiles full-size; don't shrink to fit - scroll instead */
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
/* why: mirror .tile centering - icons/placeholder are 16px, and a bare <button> inherits
|
||||
13px + UA padding, which would clip them. inline-flex + pad:0 + overflow:hidden fits them. */
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: var(--speed-tile-bg, #f0f0f0);
|
||||
border: 1px solid var(--speed-tile-border, #d8d8d8);
|
||||
flex: 0 0 auto; /* keep tiles full-size; don't shrink to fit - scroll instead */
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: hsl(var(--h) var(--speed-tile-text-s, 72%) var(--speed-tile-text-l, 38%));
|
||||
font-weight: 700;
|
||||
/* why: mirror .tile centering - tileCode can be 2-3 chars (e.g. "EA1") on collision, and a
|
||||
bare <button> inherits 13px + UA padding, clipping the code (e.g. "AE1"). inline-flex + 12px + pad:0 fits it. */
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: var(--speed-tile-bg, #f0f0f0);
|
||||
border: 1px solid var(--speed-tile-border, #d8d8d8);
|
||||
}
|
||||
|
||||
.fav-tile.sel {
|
||||
outline: 2px solid var(--main-color, var(--orca-accent, #009688));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Hover-revealed remove button in the tile's corner; sits inside the tile bounds so overflow:hidden clips it. */
|
||||
.fav-tile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fav-unpin {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
background: rgba(127, 127, 127, .35);
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fav-tile:hover .fav-unpin,
|
||||
.fav-tile.sel .fav-unpin {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.fav-unpin:hover {
|
||||
background: rgba(127, 127, 127, .6);
|
||||
}
|
||||
|
||||
/* Numbered quick-launch slot (Alt/Option+digit), top-left corner of the fav tile. */
|
||||
.fav-slot {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
min-width: 11px;
|
||||
height: 11px;
|
||||
padding: 0 2px;
|
||||
border-radius: 3px;
|
||||
font-size: 8px;
|
||||
font-weight: 600;
|
||||
line-height: 11px;
|
||||
text-align: center;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
background: rgba(127, 127, 127, .22);
|
||||
}
|
||||
|
||||
/* Transient flash banner pinned to the top of the launcher (e.g. "favourites are full"). */
|
||||
.dial-flash {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 10px;
|
||||
font-size: 11px;
|
||||
color: var(--plugin-status-warn, #b45309);
|
||||
background: var(--plugin-status-warn-bg, rgba(255, 193, 7, .22));
|
||||
animation: dial-flash-fade 2.5s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes dial-flash-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fav-tile.sel { outline: 2px solid var(--main-color, var(--orca-accent, #009688)); outline-offset: 2px; }
|
||||
.ctx-menu {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, .18);
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
min-width: 120px;
|
||||
padding: 4px;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,.18);
|
||||
}
|
||||
|
||||
.ctx-menu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ctx-menu[hidden] { display: none; }
|
||||
.ctx-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ctx-item:hover {
|
||||
background: var(--row-hover, rgba(127, 127, 127, .16));
|
||||
}
|
||||
|
||||
.ctx-item:disabled {
|
||||
opacity: .4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ctx-item:disabled:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dial-head {
|
||||
flex: 0 0 auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.ctx-item:hover { background: var(--row-hover, rgba(127,127,127,.16)); }
|
||||
.ctx-item:disabled { opacity: .4; cursor: default; }
|
||||
.ctx-item:disabled:hover { background: transparent; }
|
||||
.dial-head { flex: 0 0 auto; padding: 8px; }
|
||||
.plugin-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
background: var(--panel, var(--orca-bg, #fff));
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.plugin-search:focus-within {
|
||||
border-color: var(--main-color, var(--orca-accent, #009688));
|
||||
box-shadow: 0 0 0 2px rgba(0, 150, 136, .25);
|
||||
border-color: var(--main-color, var(--orca-accent, #009688));
|
||||
box-shadow: 0 0 0 2px rgba(0,150,136,.25);
|
||||
}
|
||||
|
||||
.plugin-search-icon {
|
||||
display: inline-flex;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
}
|
||||
|
||||
.plugin-search-icon { display: inline-flex; color: var(--muted, var(--orca-muted, #6b7280)); }
|
||||
.plugin-search-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
font: inherit;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* Wraps the input so the ghost completion can sit exactly at the caret. The ghost mirrors the typed
|
||||
text in an invisible run (reserving its width) followed by the muted suggested suffix. */
|
||||
.plugin-search-field {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-ghost {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
font: inherit;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.search-ghost[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-ghost-typed {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.search-ghost-suffix {
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.plugin-search-clear {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
cursor: pointer;
|
||||
background: rgba(127, 127, 127, .20);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
cursor: pointer;
|
||||
background: rgba(127,127,127,.20);
|
||||
}
|
||||
|
||||
.plugin-search-clear[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.plugin-search-clear[hidden] { display: none; }
|
||||
.dial-count {
|
||||
padding: 4px 4px 0;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
padding: 4px 4px 0;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
}
|
||||
|
||||
.dial-count[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dial-count[hidden] { display: none; }
|
||||
.dial-list {
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
/* ADJUST HEIGHT HERE. The popup auto-resizes to the content (HTML is the source of truth), so
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
/* ADJUST HEIGHT HERE. The popup auto-resizes to the content (HTML is the source of truth), so
|
||||
this list cap drives the whole window height: --rows full rows + a ~30% peek of the next row
|
||||
as a "scroll for more" affordance. Bump --rows to show more rows; change 0.3 for a bigger/
|
||||
smaller peek. --row-h MUST match .row min-height (44px). */
|
||||
--row-h: 44px;
|
||||
--rows: 5;
|
||||
max-height: calc(var(--rows) * var(--row-h) + 0.3 * var(--row-h) + 4px);
|
||||
overflow-y: auto;
|
||||
padding: 4px 4px 6px;
|
||||
scrollbar-width: thin;
|
||||
--row-h: 44px;
|
||||
--rows: 5;
|
||||
max-height: calc(var(--rows) * var(--row-h) + 0.3 * var(--row-h) + 4px);
|
||||
overflow-y: auto;
|
||||
padding: 4px 4px 6px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.dial-list.empty {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* Section header in the empty-query list ("Recent" then one per category). Its 30px height MUST match
|
||||
SECTION_H in speeddial.js, which reserves header space in the windowed-list bottom spacer. */
|
||||
.dial-section {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: .04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
/* Bottom spacer for the windowed command list: fills the un-rendered tail so the scrollbar reflects
|
||||
the full match count, while only a near-viewport window of rows exists in the DOM. */
|
||||
.dial-spacer-bottom {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.dial-list.empty { overflow-y: hidden; }
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 44px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 44px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
background: var(--row-hover, rgba(127, 127, 127, .16));
|
||||
}
|
||||
|
||||
.row.sel {
|
||||
background: var(--row-selected, rgba(0, 150, 136, .18));
|
||||
}
|
||||
|
||||
.row:hover { background: var(--row-hover, rgba(127,127,127,.16)); }
|
||||
.row.sel { background: var(--row-selected, rgba(0,150,136,.18)); }
|
||||
.tile {
|
||||
flex: 0 0 auto;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--speed-tile-bg, #f0f0f0);
|
||||
border: 1px solid var(--speed-tile-border, #d8d8d8);
|
||||
flex: 0 0 auto;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
color: hsl(var(--h) var(--speed-tile-text-s, 72%) var(--speed-tile-text-l, 38%));
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--speed-tile-bg, #f0f0f0);
|
||||
border: 1px solid var(--speed-tile-border, #d8d8d8);
|
||||
}
|
||||
|
||||
/* Native SVG pictogram in a tile. The Orca icon grid draws 1px strokes from 0.5..14.5 of the 16
|
||||
viewBox, so the visible glyph is 0..15 and, centered as-is, leaves one extra pixel on the right/
|
||||
bottom (the "Save Project leans left" look). Shift by half a pixel to center the visible artwork. */
|
||||
.tile-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
transform: translate(.5px, .5px);
|
||||
}
|
||||
|
||||
/* Tab-strip glyphs are drawn white for the dark tab bar; recolor to the shared #949494 gray so
|
||||
they read on both the light and dark tile backgrounds. */
|
||||
.tile-icon.tab-mono {
|
||||
filter: brightness(0) invert(.58);
|
||||
}
|
||||
|
||||
.row-left {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row-left { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
|
||||
.row-eyebrow {
|
||||
font-size: 10px;
|
||||
line-height: 1.3;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
opacity: .85;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 10px;
|
||||
line-height: 1.3;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
opacity: .85;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.row-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
.row-line { display: flex; align-items: center; gap: 7px; min-width: 0; }
|
||||
.row-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.row-name mark, .row-eyebrow mark { background: var(--plugin-status-warn-bg); color: var(--plugin-status-warn); border-radius: 2px; }
|
||||
.row-sc { flex: 0 0 auto; display: inline-flex; gap: 3px; }
|
||||
kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
|
||||
font-size: 11px;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
background: rgba(127,127,127,.12);
|
||||
border: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.row-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
.star {
|
||||
flex: 0 0 auto;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.row-name mark,
|
||||
.row-eyebrow mark {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Mode tag on settings above the user's current mode (Advanced/Expert/Developer). */
|
||||
.row-mode {
|
||||
flex: 0 0 auto;
|
||||
padding: 1px 6px;
|
||||
font-size: 10px;
|
||||
line-height: 1.4;
|
||||
border-radius: 8px;
|
||||
color: var(--plugin-status-warn);
|
||||
background: var(--plugin-status-warn-bg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pin {
|
||||
flex: 0 0 auto;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.pin svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.row:hover .pin:not(.on),
|
||||
.row.sel .pin:not(.on) {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.pin.on {
|
||||
opacity: 1;
|
||||
color: var(--main-color, var(--orca-accent, #009688));
|
||||
}
|
||||
|
||||
.pin:hover {
|
||||
background: rgba(127, 127, 127, .18);
|
||||
}
|
||||
|
||||
.star svg { display: block; }
|
||||
.row:hover .star:not(.on),
|
||||
.row.sel .star:not(.on) { opacity: .5; }
|
||||
.star.on { opacity: 1; color: var(--main-color, var(--orca-accent, #009688)); }
|
||||
.star:hover { background: rgba(127,127,127,.18); }
|
||||
.dial-empty {
|
||||
min-height: 64px;
|
||||
padding: 18px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Footer detail strip: the selected action's description, its wiki link and the expand/collapse
|
||||
arrow. Auto-sizes to the content; the description is clamped to 6 lines. */
|
||||
.dial-detail {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid var(--border, var(--orca-border, #ddd));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dial-detail[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 6;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.detail-wiki {
|
||||
flex: 0 0 auto;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
color: var(--main-color, var(--orca-accent, #009688));
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-wiki:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Expand/collapse arrow for the tooltip. Pinned right so it stays put whether or not the
|
||||
description/wiki are visible; the chevron points up (collapse) when expanded. */
|
||||
.detail-toggle {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
padding: 2px;
|
||||
background: transparent;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.detail-toggle:hover {
|
||||
color: var(--text, var(--orca-fg, #1b1c1e));
|
||||
}
|
||||
|
||||
.detail-toggle svg {
|
||||
transition: transform .12s ease;
|
||||
}
|
||||
|
||||
.detail-toggle[aria-expanded="true"] svg {
|
||||
transform: rotate(180deg);
|
||||
min-height: 64px;
|
||||
padding: 18px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--muted, var(--orca-muted, #6b7280));
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #efefef;
|
||||
--speed-tile-border: #d8d8d8;
|
||||
--speed-tile-text-s: 74%;
|
||||
--speed-tile-text-l: 34%;
|
||||
}
|
||||
|
||||
:root[data-orca-theme="dark"] {
|
||||
@@ -93,6 +95,8 @@
|
||||
/* Speed dial icon tile treatment */
|
||||
--speed-tile-bg: #34343b;
|
||||
--speed-tile-border: #50505a;
|
||||
--speed-tile-text-s: 82%;
|
||||
--speed-tile-text-l: 84%;
|
||||
}
|
||||
|
||||
/* Re-theme the shared common.css chrome through variables (replaces dark.css's
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
// Fold per-character so matched offsets stay in ORIGINAL string coordinates (highlighting slices the
|
||||
// original text; a separately-folded string would desync offsets).
|
||||
function FoldChar(ch) {
|
||||
// why: fast path - ASCII is already NFD-stable and diacritic-free, so the normalize/regex below are
|
||||
// no-ops. This skip is the hot cost in the Speed Dial search (thousands of settings per keystroke).
|
||||
if (ch.length === 1 && ch.charCodeAt(0) < 0x80)
|
||||
return ch;
|
||||
return ch.normalize("NFD").replace(/\p{Diacritic}/gu, ""); // accents always folded
|
||||
}
|
||||
|
||||
@@ -19,74 +15,6 @@ function Norm(ch, caseSensitive) {
|
||||
return caseSensitive ? folded : folded.toLowerCase(); // case-sensitivity is the only toggle
|
||||
}
|
||||
|
||||
// Pre-normalize a whole haystack with a length-preserving fold, so a caller can match it repeatedly
|
||||
// against one cached string. One input UTF-16 code unit always maps to one output code unit, so
|
||||
// indices stay aligned to the ORIGINAL text - the highlight ranges that FuzzyRangesNorm returns slice
|
||||
// the original correctly. Iterate by UTF-16 code unit (not Array.from code point) to mirror
|
||||
// FuzzyRanges' own indexing exactly.
|
||||
function NormText(text, caseSensitive) {
|
||||
const src = text || "";
|
||||
let out = "";
|
||||
for (let i = 0; i < src.length; i++)
|
||||
out += NormStable(src[i], caseSensitive);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Length-preserving variant of Norm: NFD can expand a code unit (Hangul syllables become Jamo) or
|
||||
// drop it (combining diacritics), and toLowerCase can expand one too (U+0130). Any of those would
|
||||
// desync highlight offsets, so fall back to the original unit whenever the fold is not 1:1.
|
||||
function NormStable(ch, caseSensitive) {
|
||||
const folded = FoldChar(ch);
|
||||
const stable = folded.length === 1 ? folded : ch;
|
||||
if (caseSensitive)
|
||||
return stable;
|
||||
const lower = stable.toLowerCase();
|
||||
return lower.length === 1 ? lower : stable;
|
||||
}
|
||||
|
||||
// Match a PRE-normalized haystack against a PRE-normalized needle (both produced by NormText with the
|
||||
// same caseSensitive flag). Skipping the per-character fold makes repeated matching (per keystroke over a
|
||||
// cached pool) cheap. Returns ranges in original coordinates, or null on no match.
|
||||
// Prefers the most-contiguous (smallest-span) occurrence over greedy-leftmost: a scattered match that
|
||||
// spans a stray earlier character is worse than a tight run later, so "orient" against a normalized
|
||||
// "auto-orient" returns [[5,11]] (the word) not [[3,4],[6,11]]. A fully contiguous run is the optimum
|
||||
// and short-circuits early.
|
||||
function FuzzyRangesNorm(haystackNorm, needleNorm) {
|
||||
const t = haystackNorm || "";
|
||||
const needle = needleNorm || "";
|
||||
if (!needle)
|
||||
return null;
|
||||
const n = t.length, nl = needle.length;
|
||||
let best = null; // {ranges, span, start}
|
||||
for (let start = 0; start < n; start++) {
|
||||
if (t[start] !== needle[0])
|
||||
continue;
|
||||
let qi = 0;
|
||||
const ranges = [];
|
||||
let lastEnd = start;
|
||||
for (let i = start; i < n && qi < nl; i++) {
|
||||
if (t[i] === needle[qi]) {
|
||||
const last = ranges[ranges.length - 1];
|
||||
if (last && last[1] === i)
|
||||
last[1] = i + 1;
|
||||
else
|
||||
ranges.push([i, i + 1]);
|
||||
lastEnd = i + 1;
|
||||
qi++;
|
||||
}
|
||||
}
|
||||
if (qi !== nl)
|
||||
continue;
|
||||
const span = lastEnd - start;
|
||||
if (!best || span < best.span || (span === best.span && start < best.start)) {
|
||||
best = { ranges, span, start };
|
||||
if (span === nl)
|
||||
return ranges; // can't beat a fully contiguous run
|
||||
}
|
||||
}
|
||||
return best ? best.ranges : null;
|
||||
}
|
||||
|
||||
function EscapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const vm = require("vm"), assert = require("assert"), fs = require("fs");
|
||||
const ctx = {};
|
||||
vm.createContext(ctx);
|
||||
vm.runInContext(fs.readFileSync(__dirname + "/fuzzy-search.js", "utf8"), ctx);
|
||||
const { FoldChar, Norm, NormText, EscapeRegExp, FuzzyRanges, WholeWordRanges, FuzzyRangesNorm } = ctx;
|
||||
const { FoldChar, Norm, EscapeRegExp, FuzzyRanges, WholeWordRanges } = ctx;
|
||||
|
||||
// FoldChar / Norm: accents fold, case only folds when case-insensitive.
|
||||
assert.equal(FoldChar("é"), "e");
|
||||
@@ -34,18 +34,4 @@ assert.deepEqual(WholeWordRanges("a.b", "a", false), [[0, 1]]); // '.' is a
|
||||
assert.equal(EscapeRegExp("a.b*"), "a\\.b\\*");
|
||||
assert.deepEqual(WholeWordRanges("c++ tool", "c", false), [[0, 1]]); // '+' would be a regex error unescaped
|
||||
|
||||
// FuzzyRangesNorm: prefers the most-contiguous (smallest-span) occurrence over greedy-leftmost, so a
|
||||
// stray earlier character does not steal the highlight from a later tight word.
|
||||
assert.deepEqual(FuzzyRangesNorm(NormText("Auto-Orient", false), NormText("orient", false)), [[5, 11]]);
|
||||
// A contiguous run inside a larger word is also preferred over a scattered greedy pick.
|
||||
assert.deepEqual(FuzzyRangesNorm(NormText("AutoOriented", false), NormText("orient", false)), [[4, 10]]);
|
||||
assert.equal(FuzzyRangesNorm(NormText("Measure", false), NormText("xyz", false)), null); // no subsequence
|
||||
|
||||
// NormText stays 1:1 with the original even when NFD expands (Hangul syllables) or drops (combining
|
||||
// diacritics, U+0130) a code unit, so highlight ranges remain aligned to the original string.
|
||||
assert.equal(NormText("각x", false).length, "각x".length);
|
||||
assert.equal(NormText("e\u0301x", false).length, "e\u0301x".length);
|
||||
assert.equal(NormText("\u0130x", false).length, "\u0130x".length);
|
||||
assert.equal(FuzzyRangesNorm(NormText("각abcdeXfgh", false), NormText("X", false))[0][0], 6);
|
||||
|
||||
console.log("ok");
|
||||
|
||||
@@ -384,7 +384,7 @@ modules:
|
||||
- cmake --build build_flatpak --target generate_system_cache -j$FLATPAK_BUILDER_N_JOBS
|
||||
- ./scripts/build_preset_cache.sh -n -b build_flatpak /app/share/OrcaSlicer/profiles
|
||||
|
||||
# Built (not run) here when CI injects run-tests, then shipped to a separate
|
||||
# Built (not run) here via the action's run-tests, then shipped to a separate
|
||||
# test job. Only the test sources compile; nothing installs to /app.
|
||||
test-commands:
|
||||
- cmake . -B build_flatpak -DBUILD_TESTS=ON
|
||||
|
||||
@@ -23,8 +23,7 @@ int main(int argc, char* argv[])
|
||||
#else
|
||||
("path,p", po::value<std::string>()->default_value("../../../resources/profiles"), "Path to profiles directory")
|
||||
#endif
|
||||
("log_level,l", po::value<int>()->default_value(2), "Log level (0=trace, 2=info, 4=error)")
|
||||
("vendor,v", po::value<std::string>()->default_value(""), "Vendor name. Optional; generate the cache for this vendor only (the Orca filament library is always included as the inheritance base). All vendors if not specified.");
|
||||
("log_level,l", po::value<int>()->default_value(2), "Log level (0=trace, 2=info, 4=error)");
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
@@ -39,7 +38,6 @@ int main(int argc, char* argv[])
|
||||
|
||||
const std::string profiles_path = vm["path"].as<std::string>();
|
||||
const int log_level = vm["log_level"].as<int>();
|
||||
const std::string vendor = vm["vendor"].as<std::string>();
|
||||
|
||||
if (!fs::exists(profiles_path) || !fs::is_directory(profiles_path)) {
|
||||
std::cerr << "Error: '" << profiles_path << "' is not a valid directory\n";
|
||||
@@ -61,12 +59,8 @@ int main(int argc, char* argv[])
|
||||
preset_bundle->set_is_validation_mode(true);
|
||||
preset_bundle->set_default_suppressed(true);
|
||||
preset_bundle->set_generate_vendor_caches(true);
|
||||
// Empty == every vendor. Otherwise only this vendor (plus the always-loaded
|
||||
// Orca filament library) is parsed, so only its <vendor>.opc is written.
|
||||
preset_bundle->set_vendor_to_validate(vendor);
|
||||
|
||||
std::cout << "Loading system presets from: " << profiles_path
|
||||
<< (vendor.empty() ? "" : " (vendor: " + vendor + ")") << "\n";
|
||||
std::cout << "Loading system presets from: " << profiles_path << "\n";
|
||||
|
||||
try {
|
||||
// In validation mode data_dir() is the profiles directory set above, so the
|
||||
@@ -77,14 +71,6 @@ int main(int argc, char* argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
// A specific vendor must have produced its own cache; the always-loaded
|
||||
// filament library alone would otherwise mask a misspelt or removed name.
|
||||
if (!vendor.empty() && !fs::exists(fs::path(profiles_path) / (vendor + ".opc"))) {
|
||||
std::cerr << "No cache was generated for vendor '" << vendor << "' under " << profiles_path
|
||||
<< " - check the vendor name.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t cache_count = 0;
|
||||
for (auto& entry : fs::directory_iterator(profiles_path))
|
||||
if (boost::iends_with(entry.path().string(), ".opc"))
|
||||
|
||||
@@ -283,17 +283,9 @@ void AppConfig::set_defaults()
|
||||
set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap));
|
||||
}
|
||||
|
||||
if (get(SETTING_OPENGL_SCENE_CACHE).empty())
|
||||
set_bool(SETTING_OPENGL_SCENE_CACHE, true);
|
||||
|
||||
if (get(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES).empty())
|
||||
set_bool(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES, true);
|
||||
|
||||
// The getter already defaults, parses and clamps; write back what it resolves to.
|
||||
set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(get_plugin_pages_visible_count()));
|
||||
|
||||
set(SETTING_SPEED_DIAL_RECENT_COUNT, std::to_string(get_speed_dial_recent_count()));
|
||||
|
||||
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
|
||||
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
||||
|
||||
@@ -1692,22 +1684,6 @@ int AppConfig::get_plugin_pages_visible_count() const
|
||||
return std::clamp(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX);
|
||||
}
|
||||
|
||||
int AppConfig::get_speed_dial_recent_count() const
|
||||
{
|
||||
std::string value = get(SETTING_SPEED_DIAL_RECENT_COUNT);
|
||||
if (value.empty())
|
||||
return SPEED_DIAL_RECENT_COUNT_DEFAULT;
|
||||
|
||||
int recent_count = SPEED_DIAL_RECENT_COUNT_DEFAULT;
|
||||
try {
|
||||
recent_count = std::stoi(value);
|
||||
}
|
||||
catch (...) {
|
||||
return SPEED_DIAL_RECENT_COUNT_DEFAULT;
|
||||
}
|
||||
return std::clamp(recent_count, SPEED_DIAL_RECENT_COUNT_MIN, SPEED_DIAL_RECENT_COUNT_MAX);
|
||||
}
|
||||
|
||||
std::vector<std::string> AppConfig::get_skipped_network_versions() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
@@ -33,8 +33,6 @@ using namespace nlohmann;
|
||||
#define SETTING_OPENGL_AA_SAMPLES "opengl_antialiasing_samples"
|
||||
#define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled"
|
||||
#define SETTING_OPENGL_FPS_CAP "opengl_fps_cap"
|
||||
#define SETTING_OPENGL_SCENE_CACHE "opengl_scene_cache"
|
||||
#define SETTING_OPENGL_SKIP_IDENTICAL_FRAMES "opengl_skip_identical_frames"
|
||||
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
|
||||
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
|
||||
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
|
||||
@@ -48,11 +46,6 @@ using namespace nlohmann;
|
||||
#define PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT 5
|
||||
#define PLUGIN_PAGES_VISIBLE_COUNT_MAX 10
|
||||
|
||||
#define SETTING_SPEED_DIAL_RECENT_COUNT "speed_dial_recent_count"
|
||||
#define SPEED_DIAL_RECENT_COUNT_MIN 0
|
||||
#define SPEED_DIAL_RECENT_COUNT_DEFAULT 5
|
||||
#define SPEED_DIAL_RECENT_COUNT_MAX 10
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||
#else
|
||||
@@ -401,9 +394,6 @@ public:
|
||||
// dropdown on the last tab.
|
||||
int get_plugin_pages_visible_count() const;
|
||||
|
||||
// Number of recently launched actions shown at the top of the Speed Dial; 0 hides them.
|
||||
int get_speed_dial_recent_count() const;
|
||||
|
||||
std::vector<std::string> get_skipped_network_versions() const;
|
||||
void add_skipped_network_version(const std::string& version);
|
||||
bool is_network_version_skipped(const std::string& version) const;
|
||||
|
||||
@@ -776,9 +776,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
|
||||
{
|
||||
// Get stored option value.
|
||||
const ConfigOption *raw_opt = this->option(opt_key);
|
||||
// Mirror the single-arg overload — assert() is a no-op under NDEBUG.
|
||||
if (raw_opt == nullptr)
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): \"" + opt_key + "\" is not defined");
|
||||
assert(raw_opt != nullptr);
|
||||
if (raw_opt->type() != coFloatOrPercent)
|
||||
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
|
||||
// Compute absolute value.
|
||||
|
||||
@@ -710,7 +710,6 @@ private:
|
||||
|
||||
// Always check gcode placeholders when building in debug mode.
|
||||
#if !defined(NDEBUG)
|
||||
#undef ORCA_CHECK_GCODE_PLACEHOLDERS
|
||||
#define ORCA_CHECK_GCODE_PLACEHOLDERS 1
|
||||
#endif
|
||||
|
||||
|
||||
@@ -107,12 +107,6 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT
|
||||
// normalize_fdm_2 clearing enable_prime_tower. Its mixed-filament case is not modelled.
|
||||
const bool need_wipe_tower = smooth_timelapse || wrapping;
|
||||
|
||||
// Fewer than two filaments cannot make a tool change, so only wrapping detection or smooth
|
||||
// timelapse print a tower then. The flush volume is no proof of one: it is read from the
|
||||
// matrix of every configured slot, nonzero even when a single one of them is used.
|
||||
if (filaments_cnt < 2 && !need_wipe_tower)
|
||||
return footprint;
|
||||
|
||||
// A tower printed for one of the reasons above has no tool change to purge for; both
|
||||
// planners give it the idle depth below and nothing more.
|
||||
const size_t purge_count = filaments_cnt > 1 ? (dual_nozzle ? filaments_cnt : filaments_cnt - 1) : 0;
|
||||
@@ -157,6 +151,14 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT
|
||||
purges[longest_ramming].filament_change_length = float(float_at("filament_change_length", filament_ids[longest_ramming], 0.) * double(nozzles.size() - 1));
|
||||
}
|
||||
|
||||
// Both wall types decide this together: over-reserving only wastes bed area, but reporting
|
||||
// no tower for one that is built collapses the validation hull to a point.
|
||||
// A tool change is a reason on its own (see the base commit); Type1 already reserves
|
||||
// per filament, Type2 has only the volume, which can resolve to zero.
|
||||
const bool has_purge = type1 ? !purges.empty() : volume > EPSILON;
|
||||
if (!has_purge && filaments_cnt < 2 && !need_wipe_tower)
|
||||
return footprint;
|
||||
|
||||
const double min_depth = WipeTower::get_limit_depth_by_height(float(max_object_height));
|
||||
const float perimeter_width = float(nozzle_diameter) * 1.25f; // Width_To_Nozzle_Ratio
|
||||
// With nothing to purge, plan_tower_new sizes the tower for wrapping detection or the
|
||||
@@ -169,10 +171,7 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT
|
||||
if (!purges.empty())
|
||||
side = WipeTower::estimate_rib_tower_bbox_side(purges, float(width), float(layer_height), float(nozzle_diameter), float(extra_spacing), float(rib_width), float(extra_rib_length), float(max_object_height));
|
||||
else {
|
||||
// Type2 squares the tower from its purge volume; Type1 with no purge list (a lone
|
||||
// filament kept for timelapse or wrapping) sizes for the idle depth.
|
||||
const bool has_purge = !type1 && volume > EPSILON;
|
||||
const double square = has_purge ? std::sqrt(volume / layer_height * extra_spacing) : idle_depth;
|
||||
const double square = has_purge ? std::sqrt(volume / layer_height * extra_spacing) : idle_depth;
|
||||
side = WipeTower::rib_footprint_side(float(square), float(square), float(rib_width), float(extra_rib_length), float(max_object_height));
|
||||
}
|
||||
footprint.width = footprint.depth = side;
|
||||
|
||||
@@ -120,6 +120,9 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
fill_no_overlap
|
||||
);
|
||||
|
||||
if (this->layer()->lower_layer != nullptr)
|
||||
// Cummulative sum of polygons over all the regions.
|
||||
g.lower_slices = &this->layer()->lower_layer->lslices;
|
||||
if (this->layer()->upper_layer != NULL)
|
||||
g.upper_slices = &this->layer()->upper_layer->lslices;
|
||||
|
||||
@@ -132,13 +135,6 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges);
|
||||
g.solid_infill_flow = this->flow(frSolidInfill);
|
||||
|
||||
// Cumulative sum of polygons over all the regions, less what the lower layer could not print.
|
||||
ExPolygons lower_slices;
|
||||
if (this->layer()->lower_layer != nullptr) {
|
||||
lower_slices = g.printable_slices(this->layer()->lower_layer->lslices);
|
||||
g.lower_slices = &lower_slices;
|
||||
}
|
||||
|
||||
if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode)
|
||||
g.process_arachne();
|
||||
else
|
||||
|
||||
@@ -2885,21 +2885,6 @@ bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||
return true;
|
||||
}
|
||||
|
||||
// ORCA: Arachne drops features below min_feature_size, classic builds nothing thinner than a third of the
|
||||
// nozzle. Both describe the layer below, a union of regions sharing neither nozzle nor generator, so every
|
||||
// ambiguity resolves low: it may keep a sliver that was never printed, but it never drops one that was.
|
||||
ExPolygons PerimeterGenerator::printable_slices(const ExPolygons &slices) const
|
||||
{
|
||||
double min_width = *std::min_element(print_config->nozzle_diameter.values.begin(),
|
||||
print_config->nozzle_diameter.values.end()) / 3.;
|
||||
if (object_config->wall_generator.value == PerimeterGeneratorType::Arachne) {
|
||||
const double min_feature_size = Arachne::make_paths_params(layer_id, *object_config, *print_config).min_feature_size;
|
||||
// Spiral vase can put a classic layer under an Arachne one, so there both limits apply.
|
||||
min_width = print_config->spiral_mode ? std::min(min_width, min_feature_size) : min_feature_size;
|
||||
}
|
||||
return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
|
||||
}
|
||||
|
||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||
{
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||
|
||||
@@ -149,8 +149,6 @@ public:
|
||||
//BBS
|
||||
double smaller_width_ext_mm3_per_mm() const { return m_ext_mm3_per_mm_smaller_width; }
|
||||
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
|
||||
// ORCA: the slices less the slivers the wall generator prints nothing for, so they never count as support.
|
||||
ExPolygons printable_slices(const ExPolygons &slices) const;
|
||||
|
||||
private:
|
||||
std::vector<Polygons> generate_lower_polygons_series(float width);
|
||||
|
||||
+3
-14
@@ -11,23 +11,12 @@ namespace Slic3r {
|
||||
float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int extruder_id, int filament_idx)
|
||||
{
|
||||
const double general_suggested_min_speed = 100.0;
|
||||
// Read defensively — CLI callers may hand us a config missing optional keys.
|
||||
auto vector_at = [&config](const char *key, int idx) -> double {
|
||||
if (const auto *o = config.option<ConfigOptionFloats>(key)) return o->get_at(idx);
|
||||
const ConfigOptionDef *d = config.def() ? config.def()->get(key) : nullptr;
|
||||
return (d && d->default_value) ? d->get_default_value<ConfigOptionFloats>()->get_at(idx) : 0.0;
|
||||
};
|
||||
auto nullable_at = [&config](const char *key, int idx) -> double {
|
||||
if (const auto *o = config.option<ConfigOptionFloatsNullable>(key)) return o->get_at(idx);
|
||||
const ConfigOptionDef *d = config.def() ? config.def()->get(key) : nullptr;
|
||||
return (d && d->default_value) ? d->get_default_value<ConfigOptionFloatsNullable>()->get_at(idx) : 0.0;
|
||||
};
|
||||
double filament_max_volumetric_speed = vector_at("filament_max_volumetric_speed", filament_idx);
|
||||
double filament_max_volumetric_speed = config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(filament_idx);
|
||||
// todo multi_extruders:
|
||||
const float nozzle_diameter = vector_at("nozzle_diameter", extruder_id);
|
||||
const float nozzle_diameter = config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(extruder_id);
|
||||
if (line_width <= 0.) line_width = Flow::auto_extrusion_width(frPerimeter, nozzle_diameter);
|
||||
Flow pattern_line = Flow(line_width, layer_height, nozzle_diameter);
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, nullable_at("outer_wall_speed", extruder_id)),
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->get_at(extruder_id)),
|
||||
filament_max_volumetric_speed / pattern_line.mm3_per_mm());
|
||||
|
||||
return std::floor(pa_speed);
|
||||
|
||||
@@ -14,6 +14,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/2DBed.hpp
|
||||
GUI/3DBed.cpp
|
||||
GUI/3DBed.hpp
|
||||
GUI/Widgets/StaticGroup.cpp
|
||||
GUI/Widgets/StaticGroup.hpp
|
||||
GUI/3DScene.cpp
|
||||
GUI/3DScene.hpp
|
||||
GUI/Widgets/FilamentLoad.cpp
|
||||
@@ -127,8 +129,6 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SpeedDialDialog.hpp
|
||||
GUI/ActionRegistry.cpp
|
||||
GUI/ActionRegistry.hpp
|
||||
GUI/NativeCommands.cpp
|
||||
GUI/NativeCommands.hpp
|
||||
GUI/PluginsConfigDialog.cpp
|
||||
GUI/PluginsConfigDialog.hpp
|
||||
GUI/ProcessRunner.cpp
|
||||
@@ -469,8 +469,6 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SavePresetDialog.hpp
|
||||
GUI/SceneRaycaster.cpp
|
||||
GUI/SceneRaycaster.hpp
|
||||
GUI/SceneCache.cpp
|
||||
GUI/SceneCache.hpp
|
||||
GUI/PartSkipCommon.hpp
|
||||
GUI/PartSkipDialog.cpp
|
||||
GUI/PartSkipDialog.hpp
|
||||
@@ -478,8 +476,6 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/SkipPartCanvas.hpp
|
||||
GUI/Search.cpp
|
||||
GUI/Search.hpp
|
||||
GUI/SettingsIndex.cpp
|
||||
GUI/SettingsIndex.hpp
|
||||
GUI/Selection.cpp
|
||||
GUI/Selection.hpp
|
||||
GUI/SelectMachine.cpp
|
||||
|
||||
@@ -3,48 +3,20 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "NativeCommands.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "SettingsIndex.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
|
||||
#include <libslic3r/AppConfig.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
|
||||
#include <wx/thread.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <exception>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
std::vector<std::string> cap_favourites(const std::vector<std::string>& ids, size_t limit)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
out.reserve(std::min(ids.size(), limit));
|
||||
for (const auto& id : ids) {
|
||||
if (out.size() >= limit)
|
||||
break;
|
||||
if (std::find(out.begin(), out.end(), id) == out.end())
|
||||
out.push_back(id);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kConfigSection = "speed_dial";
|
||||
@@ -56,9 +28,14 @@ nlohmann::json parse_config_json(const std::string& value, nlohmann::json fallba
|
||||
}
|
||||
|
||||
nlohmann::json read_section(const char* key, nlohmann::json fallback)
|
||||
{ return parse_config_json(wxGetApp().app_config->get(kConfigSection, key), std::move(fallback)); }
|
||||
{
|
||||
return parse_config_json(wxGetApp().app_config->get(kConfigSection, key), std::move(fallback));
|
||||
}
|
||||
|
||||
void write_section(const char* key, const nlohmann::json& j) { wxGetApp().app_config->set(kConfigSection, key, j.dump()); }
|
||||
void write_section(const char* key, const nlohmann::json& j)
|
||||
{
|
||||
wxGetApp().app_config->set(kConfigSection, key, j.dump());
|
||||
}
|
||||
|
||||
std::vector<std::string> read_string_array(const char* key)
|
||||
{
|
||||
@@ -76,7 +53,7 @@ double frecency_score(int count, long long last, long long now)
|
||||
if (count <= 0)
|
||||
return 0.0;
|
||||
constexpr double HALF_LIFE_DAYS = 30.0;
|
||||
double age = std::max(0.0, double(now - last) / 86400.0);
|
||||
double age = std::max(0.0, double(now - last) / 86400.0);
|
||||
return count * std::pow(2.0, -age / HALF_LIFE_DAYS);
|
||||
}
|
||||
|
||||
@@ -102,25 +79,26 @@ struct PluginScriptAction : AppAction
|
||||
// The id an action for (plugin_key, capability) would have - lets refresh_capability
|
||||
// remove a gone capability without materialising the action.
|
||||
static std::string id_for(const std::string& plugin_key, const std::string& capability)
|
||||
{ return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key); }
|
||||
{
|
||||
return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key);
|
||||
}
|
||||
|
||||
PluginScriptAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
|
||||
: AppAction(kIdPrefix,
|
||||
capability_in.empty() ? plugin_key_in : capability_in, // title
|
||||
plugin_key_in, // source_key
|
||||
std::move(source_name))
|
||||
, plugin_key(std::move(plugin_key_in))
|
||||
, capability(std::move(capability_in))
|
||||
capability_in.empty() ? plugin_key_in : capability_in, // title
|
||||
plugin_key_in, // source_key
|
||||
std::move(source_name)),
|
||||
plugin_key(std::move(plugin_key_in)), capability(std::move(capability_in))
|
||||
{}
|
||||
|
||||
AppActionRunResult run(const std::string& /*param*/) const override
|
||||
AppActionRunResult run() const override
|
||||
{
|
||||
std::string error;
|
||||
const ExecutionResult result = PluginManager::instance().run_script_capability(plugin_key, capability, error);
|
||||
if (!error.empty())
|
||||
return {AppActionRunResult::Level::Error, from_u8(error)};
|
||||
|
||||
const bool skipped = result.status == PluginResult::Skipped;
|
||||
const bool skipped = result.status == PluginResult::Skipped;
|
||||
const wxString fallback = skipped ? _L("Script plugin skipped.") : _L("Script plugin finished.");
|
||||
return {skipped ? AppActionRunResult::Level::Info : AppActionRunResult::Level::Success,
|
||||
result.message.empty() ? fallback : from_u8(result.message)};
|
||||
@@ -129,7 +107,8 @@ struct PluginScriptAction : AppAction
|
||||
|
||||
// Builds an action for a capability, or nullptr if it is not a currently-loaded,
|
||||
// enabled script capability.
|
||||
std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std::string& capability, const std::string& source_name)
|
||||
std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std::string& capability,
|
||||
const std::string& source_name)
|
||||
{
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
if (!manager.is_plugin_loaded(plugin_key))
|
||||
@@ -140,151 +119,8 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
|
||||
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
|
||||
}
|
||||
|
||||
// ---- built-in command actions (the speed dial "commands" section) ------
|
||||
|
||||
constexpr const char* kSettingPrefix = "orca_setting";
|
||||
constexpr const char* kPlateGotoPrefix = "orca_plate_goto";
|
||||
constexpr const char* kRecentProjectPrefix = "orca_recent_project";
|
||||
|
||||
// Display context for a setting action's eyebrow, e.g. the "Process" in "Process : Quality : Layers".
|
||||
// Keyed by the option's preset type so the palette reads like the settings sidebar tabs.
|
||||
std::string setting_type_context(Preset::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Preset::TYPE_FILAMENT:
|
||||
case Preset::TYPE_SLA_MATERIAL: return _u8L("Filament");
|
||||
case Preset::TYPE_PRINTER: return _u8L("Printer");
|
||||
case Preset::TYPE_PRINT:
|
||||
case Preset::TYPE_SLA_PRINT:
|
||||
default: return _u8L("Process");
|
||||
}
|
||||
}
|
||||
|
||||
// Stable, non-localized mode token for the webview, which maps it to a badge ("Developer" etc.).
|
||||
const char* mode_key(ConfigOptionMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case comAdvanced: return "advanced";
|
||||
case comExpert: return "expert";
|
||||
case comDevelop: return "develop";
|
||||
default: return "simple";
|
||||
}
|
||||
}
|
||||
|
||||
// A config setting exposed as a first-class action: selecting it jumps the sidebar to the option.
|
||||
// The id is keyed by opt_key+type (NOT the display label), so renaming/localizing never re-keys
|
||||
// the action; title/group/source are purely for display + search. run() performs the jump, and
|
||||
// the generic registry run() bumps stats so a jump shows up in "recents" like any other action.
|
||||
struct SettingAction : AppAction
|
||||
{
|
||||
std::string opt_key;
|
||||
Preset::Type type;
|
||||
std::wstring category; // English category, forwarded to jump_to_option (it localizes)
|
||||
|
||||
static std::string id_for(const std::string& opt_key, Preset::Type type)
|
||||
{ return std::string(kSettingPrefix) + ":" + opt_key + ":" + std::to_string(int(type)); }
|
||||
|
||||
SettingAction(std::string opt_key_in,
|
||||
Preset::Type type_in,
|
||||
std::string title,
|
||||
std::string group,
|
||||
std::wstring category_in,
|
||||
std::string source_name,
|
||||
ConfigOptionMode mode_in)
|
||||
: AppAction(AppActionId{id_for(opt_key_in, type_in)}, std::move(title), kOrcaSourceKey, std::move(source_name))
|
||||
, opt_key(std::move(opt_key_in))
|
||||
, type(type_in)
|
||||
, category(std::move(category_in))
|
||||
{
|
||||
// A setting is a single-phase command: activating it jumps the sidebar to the option
|
||||
// (like the sidebar's own settings search), then the dial closes. run() performs the jump.
|
||||
this->kind = AppActionKind::Command;
|
||||
this->group = std::move(group);
|
||||
this->required_mode = mode_in;
|
||||
}
|
||||
|
||||
AppActionRunResult run(const std::string& /*param*/) const override
|
||||
{
|
||||
wxGetApp().sidebar().jump_to_option(opt_key, type, category);
|
||||
return {AppActionRunResult::Level::Success};
|
||||
}
|
||||
};
|
||||
|
||||
// Seed one action's persisted state (favourite flag + frecency counters) from an already-parsed
|
||||
// stats blob and capped favourite list. Shared by the dynamic materialisers.
|
||||
void seed_from(const nlohmann::json& stats, const std::vector<std::string>& favs, const std::string& id, AppAction& a)
|
||||
{
|
||||
a.favourite = std::find(favs.begin(), favs.end(), id) != favs.end();
|
||||
if (auto it = stats.find(id); it != stats.end() && it->is_object()) {
|
||||
a.count = it->value("count", 0);
|
||||
a.last = it->value("last", 0LL);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop actions whose id starts with `prefix` but that were not seen in this pass (a stale materialisation).
|
||||
void drop_stale(std::unordered_map<std::string, std::shared_ptr<AppAction>>& actions, const char* prefix,
|
||||
const std::unordered_set<std::string>& seen)
|
||||
{
|
||||
for (auto it = actions.begin(); it != actions.end();) {
|
||||
if (it->first.rfind(prefix, 0) == 0 && !seen.count(it->first))
|
||||
it = actions.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// A dynamic "Go to Plate N" action, one per live plate, rebuilt on every snapshot() (so a
|
||||
// rename/move immediately shows up). id is keyed by plate index, NOT the display title, so
|
||||
// renaming a plate never re-keys it - the same contract as SettingAction. A pinned "Go to
|
||||
// Plate N" whose plate is deleted simply stops resolving (visibleFavourites drops dead pins).
|
||||
struct PlateAction : AppAction
|
||||
{
|
||||
int plate_index;
|
||||
|
||||
static std::string id_for(int index) { return AppAction::compose_id(kPlateGotoPrefix, std::to_string(index), kOrcaSourceKey); }
|
||||
|
||||
PlateAction(int index, std::string title, std::string source_name)
|
||||
: AppAction(AppActionId{id_for(index)}, std::move(title), kOrcaSourceKey, std::move(source_name)), plate_index(index)
|
||||
{
|
||||
this->kind = AppActionKind::Command;
|
||||
this->group = _u8L("Plate");
|
||||
}
|
||||
|
||||
AppActionRunResult run(const std::string& /*param*/) const override
|
||||
{ return NativeCommands::run("plate_goto", std::to_string(plate_index)); }
|
||||
};
|
||||
|
||||
// A dynamic "Open recent project <name>" action, one per recent project file, rebuilt on every
|
||||
// snapshot() (like PlateAction) so the list always reflects the current recents. The id is keyed
|
||||
// by the file PATH, NOT the display title - the same contract as SettingAction/PlateAction, so a
|
||||
// rename of a project (or a reordered recents list) never re-keys the action. A pinned recent whose
|
||||
// file is deleted simply stops resolving (visibleFavourites drops dead pins). run() loads the
|
||||
// project through MainFrame::open_recent_project so the existing missing-file handling is reused.
|
||||
struct RecentProjectAction : AppAction
|
||||
{
|
||||
std::string file_path;
|
||||
|
||||
static std::string id_for(const std::string& path) { return AppAction::compose_id(kRecentProjectPrefix, path, kOrcaSourceKey); }
|
||||
|
||||
RecentProjectAction(std::string path, std::string title, std::string source)
|
||||
: AppAction(AppActionId{id_for(path)}, std::move(title), kOrcaSourceKey, std::move(source)), file_path(std::move(path))
|
||||
{
|
||||
this->kind = AppActionKind::Command;
|
||||
this->group = _u8L("Recent Projects");
|
||||
}
|
||||
|
||||
AppActionRunResult run(const std::string& /*param*/) const override
|
||||
{
|
||||
if (MainFrame* mf = wxGetApp().mainframe; mf)
|
||||
mf->open_recent_project(size_t(-1), wxString::FromUTF8(file_path));
|
||||
return {AppActionRunResult::Level::Success};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
ActionRegistry::~ActionRegistry() = default;
|
||||
|
||||
void ActionRegistry::init()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
@@ -315,12 +151,18 @@ void ActionRegistry::init()
|
||||
// Subscribe before enumerating so a concurrent load cannot land between the initial
|
||||
// snapshot and callback registration. Duplicate notifications are safe: upsert is by
|
||||
// id and the m_actions scan in refresh_source is idempotent.
|
||||
manager.subscribe_on_load_callback([on_source](const std::string& key) { on_source(key, ActionChange::Added); });
|
||||
manager.subscribe_on_unload_callback([on_source](const std::string& key) { on_source(key, ActionChange::Removed); });
|
||||
manager.subscribe_on_load_callback(
|
||||
[on_source](const std::string& key) { on_source(key, ActionChange::Added); });
|
||||
manager.subscribe_on_unload_callback(
|
||||
[on_source](const std::string& key) { on_source(key, ActionChange::Removed); });
|
||||
manager.subscribe_on_capability_load_callback(
|
||||
[on_capability](const PluginCapabilityId& capability) { on_capability(capability, ActionChange::Added); });
|
||||
[on_capability](const PluginCapabilityId& capability) {
|
||||
on_capability(capability, ActionChange::Added);
|
||||
});
|
||||
manager.subscribe_on_capability_unload_callback(
|
||||
[on_capability](const PluginCapabilityId& capability) { on_capability(capability, ActionChange::Removed); });
|
||||
[on_capability](const PluginCapabilityId& capability) {
|
||||
on_capability(capability, ActionChange::Removed);
|
||||
});
|
||||
|
||||
// enumerate current script capabilities
|
||||
std::unordered_map<std::string, std::string> source_names;
|
||||
@@ -331,40 +173,12 @@ void ActionRegistry::init()
|
||||
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Script)) {
|
||||
if (!capability)
|
||||
continue;
|
||||
const std::string& key = capability->audit_plugin_key();
|
||||
auto it = source_names.find(key);
|
||||
const std::string& key = capability->audit_plugin_key();
|
||||
auto it = source_names.find(key);
|
||||
const std::string& source_name = it == source_names.end() ? key : it->second;
|
||||
if (auto action = make_action(key, capability->name(), source_name))
|
||||
upsert(std::move(action));
|
||||
}
|
||||
|
||||
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
|
||||
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
|
||||
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
|
||||
// NativeCommands; the registry only stores and dispatches the result.
|
||||
for (const NativeCommand& c : NativeCommands::catalog())
|
||||
upsert(NativeCommands::make_action(c));
|
||||
}
|
||||
|
||||
void ActionRegistry::relocalize_builtins()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
if (!m_started)
|
||||
return;
|
||||
|
||||
// Drop only the built-in commands; plugins and the dynamically materialised families are
|
||||
// either unlocalized or rebuilt per snapshot. remove() only erases the map entry, and upsert()
|
||||
// re-seeds favourites/stats from config, so key-based ids keep their pinned state.
|
||||
std::vector<std::string> stale;
|
||||
for (const auto& [id, action] : m_actions)
|
||||
if (action->source_key() == kOrcaSourceKey && action->kind == AppActionKind::Command)
|
||||
stale.push_back(id);
|
||||
for (const std::string& id : stale)
|
||||
remove(id);
|
||||
|
||||
NativeCommands::rebuild_catalog();
|
||||
for (const NativeCommand& c : NativeCommands::catalog())
|
||||
upsert(NativeCommands::make_action(c));
|
||||
}
|
||||
|
||||
void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange change)
|
||||
@@ -384,7 +198,7 @@ void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange
|
||||
if (change == ActionChange::Removed)
|
||||
return;
|
||||
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
const std::string source_name = find_loaded_source_name(manager, plugin_key);
|
||||
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Script)) {
|
||||
if (!capability)
|
||||
@@ -394,7 +208,8 @@ void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange
|
||||
}
|
||||
}
|
||||
|
||||
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
|
||||
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability,
|
||||
ActionChange change)
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
|
||||
@@ -418,7 +233,7 @@ void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
|
||||
return;
|
||||
|
||||
seed_state(*action);
|
||||
std::string id = action->id();
|
||||
std::string id = action->id();
|
||||
std::shared_ptr<AppAction> stored = std::move(action);
|
||||
m_actions.insert_or_assign(std::move(id), std::move(stored));
|
||||
}
|
||||
@@ -431,13 +246,11 @@ void ActionRegistry::remove(const std::string& id)
|
||||
|
||||
void ActionRegistry::seed_state(AppAction& a) const
|
||||
{
|
||||
// Favourites carry the quick-launch order, so the persisted list is the source of truth
|
||||
// (not re-derived from the frecency sort). Cap it so stale configs can't exceed kFavLimit.
|
||||
auto favs = favourite_ids();
|
||||
auto favs = read_string_array("favourite_actions");
|
||||
a.favourite = std::find(favs.begin(), favs.end(), a.id()) != favs.end();
|
||||
|
||||
nlohmann::json stats = read_section("stats", nlohmann::json::object());
|
||||
auto it = stats.find(a.id());
|
||||
auto it = stats.find(a.id());
|
||||
if (it != stats.end() && it->is_object()) {
|
||||
a.count = it->value("count", 0);
|
||||
a.last = it->value("last", 0LL);
|
||||
@@ -447,14 +260,6 @@ void ActionRegistry::seed_state(AppAction& a) const
|
||||
}
|
||||
}
|
||||
|
||||
void ActionRegistry::load_persisted(nlohmann::json& stats, std::vector<std::string>& favs) const
|
||||
{
|
||||
stats = read_section("stats", nlohmann::json::object());
|
||||
if (!stats.is_object())
|
||||
stats = nlohmann::json::object();
|
||||
favs = favourite_ids();
|
||||
}
|
||||
|
||||
// ---- read surface -----------------------------------------------------------
|
||||
|
||||
const AppAction* ActionRegistry::by_id(const std::string& id) const
|
||||
@@ -464,11 +269,14 @@ const AppAction* ActionRegistry::by_id(const std::string& id) const
|
||||
return it == m_actions.end() ? nullptr : it->second.get();
|
||||
}
|
||||
|
||||
AppAction* ActionRegistry::find(const std::string& id) { return const_cast<AppAction*>(by_id(id)); }
|
||||
AppAction* ActionRegistry::find(const std::string& id)
|
||||
{
|
||||
return const_cast<AppAction*>(by_id(id));
|
||||
}
|
||||
|
||||
// ---- dispatch + write-through ----------------------------------------------
|
||||
|
||||
AppActionRunResult ActionRegistry::run(const std::string& id, const std::string& param)
|
||||
AppActionRunResult ActionRegistry::run(const std::string& id)
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
auto it = m_actions.find(id);
|
||||
@@ -478,13 +286,13 @@ AppActionRunResult ActionRegistry::run(const std::string& id, const std::string&
|
||||
// nested event loop; a queued source refresh can erase the entry while the
|
||||
// keep-alive preserves the action until run returns.
|
||||
std::shared_ptr<AppAction> keep = it->second;
|
||||
AppActionRunResult o = keep->run(param);
|
||||
AppActionRunResult o = keep->run();
|
||||
if (o.level == AppActionRunResult::Level::Busy)
|
||||
return o;
|
||||
|
||||
// Bump stats (write-through). Re-read to avoid clobbering a concurrent field.
|
||||
nlohmann::json stats = read_section("stats", nlohmann::json::object());
|
||||
if (!stats.is_object()) // corrupt (valid-JSON, non-object) value degrades to empty
|
||||
if (!stats.is_object()) // corrupt (valid-JSON, non-object) value degrades to empty
|
||||
stats = nlohmann::json::object();
|
||||
nlohmann::json& e = stats[id];
|
||||
if (!e.is_object())
|
||||
@@ -492,37 +300,22 @@ AppActionRunResult ActionRegistry::run(const std::string& id, const std::string&
|
||||
e["count"] = e.value("count", 0) + 1;
|
||||
e["last"] = (long long) std::time(nullptr);
|
||||
write_section("stats", stats);
|
||||
if (AppAction* live = find(id)) {
|
||||
live->count = e["count"];
|
||||
live->last = e["last"];
|
||||
}
|
||||
if (AppAction* live = find(id)) { live->count = e["count"]; live->last = e["last"]; }
|
||||
return o;
|
||||
}
|
||||
|
||||
bool ActionRegistry::set_favourite(const std::string& id, bool on)
|
||||
void ActionRegistry::set_favourite(const std::string& id, bool on)
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
// Start from the capped, deduped list so a persisted config can never be written back larger.
|
||||
auto favs = favourite_ids();
|
||||
auto favs = read_string_array("favourite_actions");
|
||||
auto it = std::find(favs.begin(), favs.end(), id);
|
||||
if (on && it == favs.end()) {
|
||||
if (favs.size() >= kFavLimit)
|
||||
return false; // bar is full - the caller surfaces a hint
|
||||
if (on && it == favs.end())
|
||||
favs.push_back(id);
|
||||
}
|
||||
if (!on && it != favs.end())
|
||||
favs.erase(it);
|
||||
write_section("favourite_actions", nlohmann::json(favs));
|
||||
if (AppAction* live = find(id))
|
||||
live->favourite = on;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> ActionRegistry::favourite_ids() const
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
// Enforce the cap + dedupe on read so the persisted order can never grow past kFavLimit, even from an older config.
|
||||
return cap_favourites(read_string_array("favourite_actions"), kFavLimit);
|
||||
}
|
||||
|
||||
void ActionRegistry::reorder_favourites(const std::vector<std::string>& ids)
|
||||
@@ -532,184 +325,16 @@ void ActionRegistry::reorder_favourites(const std::vector<std::string>& ids)
|
||||
std::vector<std::string> next;
|
||||
// keep the requested order, but only ids that are actually favourites (guard a bad payload)
|
||||
for (const auto& id : ids)
|
||||
if (std::find(cur.begin(), cur.end(), id) != cur.end() && std::find(next.begin(), next.end(), id) == next.end())
|
||||
if (std::find(cur.begin(), cur.end(), id) != cur.end() &&
|
||||
std::find(next.begin(), next.end(), id) == next.end())
|
||||
next.push_back(id);
|
||||
// why: don't drop favourites the page omitted (e.g. pins with no live action hidden from the bar)
|
||||
for (const auto& id : cur)
|
||||
if (std::find(next.begin(), next.end(), id) == next.end())
|
||||
next.push_back(id);
|
||||
// never write the bar back larger than the quick-launch slots
|
||||
next = cap_favourites(next, kFavLimit);
|
||||
write_section("favourite_actions", nlohmann::json(next));
|
||||
}
|
||||
|
||||
void ActionRegistry::materialize_setting_actions()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
|
||||
// Reuse the Sidebar's live settings index: it's the only catalog whose group/category map is
|
||||
// populated (Tab::add_key feeds it at build time), and it already mirrors the current
|
||||
// configs/printer-technology. Use the all-modes view so the Speed Dial lists every setting,
|
||||
// including those above the user's current mode, and can prompt to switch before jumping.
|
||||
const std::vector<Search::Option>& options = wxGetApp().sidebar().settings_index().all_options();
|
||||
|
||||
// Load the persisted per-action state ONCE (not per-option) so a re-materialised setting keeps
|
||||
// its recency/favourite; mirroring seed_state but amortised over the whole option set.
|
||||
nlohmann::json stats;
|
||||
std::vector<std::string> favs;
|
||||
load_persisted(stats, favs);
|
||||
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const Search::Option& opt : options) {
|
||||
// The row's live state drives both the hidden filter and the title (labels can change at
|
||||
// runtime, e.g. brim_width -> "Brim ear radius"). Hidden rows are skipped, not marked seen.
|
||||
Tab* tab = wxGetApp().get_tab(opt.type);
|
||||
Tab::SettingRowState row;
|
||||
if (tab)
|
||||
row = tab->setting_row_state(opt.opt_key());
|
||||
if (!row.visible)
|
||||
continue;
|
||||
|
||||
const std::string id = SettingAction::id_for(opt.opt_key(), opt.type);
|
||||
seen.insert(id);
|
||||
|
||||
// The page draws Line::label; the descriptive ConfigOptionDef name stays a search-only alias
|
||||
// ("overhang reversal" still finds "Reverse on even").
|
||||
const std::string search_label = boost::nowide::narrow(opt.label_local.empty() ? opt.label : opt.label_local);
|
||||
std::string title = into_u8(Search::resolve_setting_title(from_u8(opt.display_label), row.label, row.multi));
|
||||
if (title.empty())
|
||||
title = search_label;
|
||||
|
||||
// Eyebrow/source = the full settings path "Process : Quality : Layers" (localized). The JS
|
||||
// renders group || source and searches source + " " + group, so putting the whole path in
|
||||
// source both displays it and makes it matchable by any segment (e.g. a "quality" query).
|
||||
std::wstring path = boost::nowide::widen(setting_type_context(opt.type));
|
||||
if (!opt.category_local.empty())
|
||||
path += L" : " + opt.category_local;
|
||||
if (!opt.group_local.empty())
|
||||
path += L" : " + opt.group_local;
|
||||
|
||||
// title = the label the settings row draws; group stays empty so the source path (above) is
|
||||
// the single display/search breadcrumb rather than being duplicated.
|
||||
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, title, std::string(), opt.category,
|
||||
boost::nowide::narrow(path), opt.mode);
|
||||
if (title != search_label)
|
||||
action->full_label = search_label;
|
||||
|
||||
// Tile pictogram = the icon of the setting's own group header (e.g. Advanced -> param_advanced),
|
||||
// the one shown next to it in the page. Fall back to the page/category icon for groups
|
||||
// without one. Keys are the English titles the GUI registers.
|
||||
action->icon = opt.group_icon;
|
||||
if (action->icon.empty() && !opt.category.empty() && tab) {
|
||||
const auto& icons = tab->get_category_icon_map();
|
||||
auto it = icons.find(wxString(opt.category));
|
||||
if (it != icons.end())
|
||||
action->icon = it->second;
|
||||
}
|
||||
|
||||
// Footer description + wiki affordance; only settings whose row declared a wiki path have one.
|
||||
action->tooltip = opt.tooltip;
|
||||
if (!opt.wiki_path.empty())
|
||||
action->help_url = into_u8(OptionsGroup::get_url(opt.wiki_path));
|
||||
|
||||
seed_from(stats, favs, id, *action);
|
||||
auto const action_id = action->id();
|
||||
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
|
||||
m_actions.insert_or_assign(action_id, app_action);
|
||||
}
|
||||
|
||||
// Drop SettingActions whose option no longer exists in the current configs (e.g. the printer
|
||||
// technology / UI mode changed). Non-setting actions are untouched.
|
||||
drop_stale(m_actions, kSettingPrefix, seen);
|
||||
}
|
||||
|
||||
void ActionRegistry::materialize_plate_actions()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
|
||||
// Plates are a filament (FFF) feature: SLA has a single plate and no plate UI, and gcode-only
|
||||
// mode has no editable project - so no "Go to Plate N" actions are offered there.
|
||||
Plater* plater = wxTheApp ? wxGetApp().plater() : nullptr;
|
||||
if (!plater || plater->printer_technology() != ptFFF || plater->only_gcode_mode()) {
|
||||
// Drop any stale plate actions (e.g. the printer technology switched to SLA).
|
||||
drop_stale(m_actions, kPlateGotoPrefix, {});
|
||||
return;
|
||||
}
|
||||
|
||||
// Persisted per-action state, read ONCE (mirrors materialize_setting_actions) so a relisted
|
||||
// "Go to Plate N" keeps its recency/favourite when the plate is renamed - the id is index-keyed.
|
||||
nlohmann::json stats;
|
||||
std::vector<std::string> favs;
|
||||
load_persisted(stats, favs);
|
||||
|
||||
const std::vector<PartPlate*>& list = plater->get_partplate_list().get_plate_list();
|
||||
std::unordered_set<std::string> seen;
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
PartPlate* plate = list[i];
|
||||
if (!plate)
|
||||
continue;
|
||||
const std::string id = PlateAction::id_for(int(i));
|
||||
seen.insert(id);
|
||||
|
||||
// "Go to Plate N" + " (name)" when the plate is named, matching the object-list label.
|
||||
std::string title(_u8L("Go to Plate"));
|
||||
title += " " + std::to_string(i + 1);
|
||||
const std::string name = plate->get_plate_name();
|
||||
if (!name.empty())
|
||||
title += " (" + name + ")";
|
||||
|
||||
auto action = std::make_unique<PlateAction>(int(i), title, kOrcaSourceName);
|
||||
seed_from(stats, favs, id, *action);
|
||||
auto const action_id = action->id();
|
||||
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
|
||||
m_actions.insert_or_assign(action_id, app_action);
|
||||
}
|
||||
|
||||
// Drop plate actions whose index no longer exists (a plate was deleted / moved to the front).
|
||||
drop_stale(m_actions, kPlateGotoPrefix, seen);
|
||||
}
|
||||
|
||||
void ActionRegistry::materialize_recent_project_actions()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
|
||||
// Persisted per-action state, read ONCE (mirrors materialize_plate_actions) so a relisted recent
|
||||
// project keeps its recency/favourite when the recents list reorders - the id is path-keyed.
|
||||
nlohmann::json stats;
|
||||
std::vector<std::string> favs;
|
||||
load_persisted(stats, favs);
|
||||
|
||||
// app_config stores recents oldest-first; the palette shows newest-first.
|
||||
std::vector<std::string> recents = wxGetApp().app_config->get_recent_projects();
|
||||
std::reverse(recents.begin(), recents.end());
|
||||
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const std::string& path : recents) {
|
||||
// Skip projects whose file is gone; the stale id is dropped below.
|
||||
boost::system::error_code ec;
|
||||
if (path.empty() || !boost::filesystem::exists(boost::filesystem::path(path), ec))
|
||||
continue;
|
||||
|
||||
const std::string id = RecentProjectAction::id_for(path);
|
||||
seen.insert(id);
|
||||
|
||||
// Title = file basename; source/eyebrow = the full path so search can match either.
|
||||
boost::filesystem::path p(path);
|
||||
std::string title = p.filename().string();
|
||||
if (title.empty())
|
||||
title = path;
|
||||
|
||||
auto action = std::make_unique<RecentProjectAction>(path, std::move(title), path);
|
||||
seed_from(stats, favs, id, *action);
|
||||
auto const action_id = action->id();
|
||||
auto const app_action = std::shared_ptr<AppAction>(std::move(action));
|
||||
m_actions.insert_or_assign(action_id, app_action);
|
||||
}
|
||||
|
||||
// Drop recent-project actions whose file no longer exists / was removed from the recents list.
|
||||
drop_stale(m_actions, kRecentProjectPrefix, seen);
|
||||
}
|
||||
|
||||
bool ActionRegistry::should_ask(const std::string& id) const
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
@@ -726,31 +351,11 @@ void ActionRegistry::suppress_ask(const std::string& id)
|
||||
write_section("ask_suppressed", nlohmann::json(arr));
|
||||
}
|
||||
|
||||
bool ActionRegistry::tooltip_expanded() const
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
const nlohmann::json j = read_section("tooltip_expanded", nlohmann::json(true));
|
||||
return j.is_boolean() ? j.get<bool>() : true;
|
||||
}
|
||||
|
||||
void ActionRegistry::set_tooltip_expanded(bool expanded)
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
write_section("tooltip_expanded", nlohmann::json(expanded));
|
||||
}
|
||||
|
||||
// ---- snapshot ---------------------------------------------------------------
|
||||
|
||||
nlohmann::json ActionRegistry::snapshot()
|
||||
nlohmann::json ActionRegistry::snapshot() const
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
// Settings and plates are first-class actions; make sure the current visible option set and the
|
||||
// live plate list are materialised before we serialise the pool (tabs_list is built by the time
|
||||
// the palette opens).
|
||||
materialize_setting_actions();
|
||||
materialize_plate_actions();
|
||||
materialize_recent_project_actions();
|
||||
|
||||
std::vector<const AppAction*> sorted;
|
||||
sorted.reserve(m_actions.size());
|
||||
for (const auto& entry : m_actions)
|
||||
@@ -769,85 +374,17 @@ nlohmann::json ActionRegistry::snapshot()
|
||||
return a->id() < b->id();
|
||||
});
|
||||
|
||||
auto action_to_json = [](const AppAction* a) {
|
||||
return nlohmann::json({{"id", a->id()},
|
||||
{"title", a->title()},
|
||||
{"full_label", a->full_label},
|
||||
{"source", a->source_name()},
|
||||
{"group", a->group},
|
||||
{"kind", a->kind == AppActionKind::Plugin ? "plugin" : "command"},
|
||||
{"input", a->input},
|
||||
{"icon", a->icon},
|
||||
{"mode", mode_key(a->required_mode)},
|
||||
{"desc", a->tooltip},
|
||||
{"wiki", !a->help_url.empty()}});
|
||||
};
|
||||
|
||||
nlohmann::json actions = nlohmann::json::array();
|
||||
for (const AppAction* a : sorted)
|
||||
actions.push_back(action_to_json(a));
|
||||
|
||||
actions.push_back({{"id", a->id()},
|
||||
{"title", a->title()},
|
||||
{"source", a->source_name()},
|
||||
{"shortcut", ""}});
|
||||
// why: favourites is the ORDERED pin list - it must come from favourite_actions
|
||||
// as stored, not be re-derived from the frecency-sorted actions (that would
|
||||
// reorder the favourites bar). Drop pins with no live action (an option hidden by the
|
||||
// current mode, an unloaded plugin, a gone plate/project) and persist the pruned list, so
|
||||
// invisible pins can't silently fill the quick-launch cap. Order is preserved.
|
||||
std::vector<std::string> favs = favourite_ids();
|
||||
std::vector<std::string> live_favs;
|
||||
live_favs.reserve(favs.size());
|
||||
for (const auto& id : favs)
|
||||
if (m_actions.count(id))
|
||||
live_favs.push_back(id);
|
||||
if (live_favs.size() != favs.size())
|
||||
write_section("favourite_actions", nlohmann::json(live_favs));
|
||||
nlohmann::json favourites(live_favs);
|
||||
|
||||
// Recent = the last-N launched actions by recency (only actions with a run history). N is a
|
||||
// user preference; 0 hides recents without affecting the frecency order below.
|
||||
const size_t recent_limit = size_t(wxGetApp().app_config->get_speed_dial_recent_count());
|
||||
std::vector<const AppAction*> recent;
|
||||
for (const auto& entry : m_actions)
|
||||
if (entry.second->last > 0)
|
||||
recent.push_back(entry.second.get());
|
||||
std::sort(recent.begin(), recent.end(), [](const AppAction* a, const AppAction* b) {
|
||||
if (a->last != b->last)
|
||||
return a->last > b->last;
|
||||
return a->id() < b->id();
|
||||
});
|
||||
if (recent.size() > recent_limit)
|
||||
recent.resize(recent_limit);
|
||||
nlohmann::json recent_json = nlohmann::json::array();
|
||||
for (const AppAction* a : recent)
|
||||
recent_json.push_back(action_to_json(a));
|
||||
|
||||
return {{"actions", std::move(actions)},
|
||||
{"favourites", std::move(favourites)},
|
||||
{"recent", std::move(recent_json)},
|
||||
{"user_mode", mode_key(wxGetApp().get_mode())},
|
||||
{"tooltip_expanded", tooltip_expanded()}};
|
||||
}
|
||||
|
||||
// ---- tab options (enumerate the MainFrame notebook's current pages) ----------
|
||||
|
||||
nlohmann::json ActionRegistry::tab_options() const
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
nlohmann::json out = nlohmann::json::array();
|
||||
if (!wxTheApp || wxGetApp().is_closing())
|
||||
return out;
|
||||
MainFrame* mf = wxGetApp().mainframe;
|
||||
if (!mf || !mf->m_tabpanel)
|
||||
return out;
|
||||
Notebook* notebook = mf->m_tabpanel;
|
||||
for (size_t i = 0; i < notebook->GetPageCount(); ++i) {
|
||||
const wxString id = notebook->GetPageName(i);
|
||||
if (id.empty())
|
||||
continue;
|
||||
out.push_back({{"id", id.ToStdString()},
|
||||
{"title", notebook->GetPageLabel(i).ToStdString()},
|
||||
{"icon", notebook->GetPageIcon(i)}});
|
||||
}
|
||||
return out;
|
||||
// reorder the favourites bar). The page (js) filters out ids with no live action itself.
|
||||
nlohmann::json favourites(read_string_array("favourite_actions"));
|
||||
return {{"actions", std::move(actions)}, {"favourites", std::move(favourites)}};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <libslic3r/Config.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/thread.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -21,25 +18,14 @@ namespace Slic3r { namespace GUI {
|
||||
// How a source's action set changed. Drives the registry's refresh handlers.
|
||||
enum class ActionChange { Added, Removed };
|
||||
|
||||
// What kind of runnable thing an action is. Drives the run-confirm gate (plugins ask, commands don't).
|
||||
enum class AppActionKind { Plugin, Command };
|
||||
|
||||
// Result of running an AppAction, in the action layer's own vocabulary. Concrete
|
||||
// actions translate their runner-specific result into this generic shape.
|
||||
struct AppActionRunResult
|
||||
{
|
||||
enum class Level { Success, Info, Error, Busy };
|
||||
|
||||
Level level = Level::Info;
|
||||
wxString message; // empty = "nothing worth showing"
|
||||
};
|
||||
|
||||
// Tag carrying a precomputed action id, used by the explicit-id ctor below. It exists so the
|
||||
// id ctor and the compose-from-prefix ctor are NOT both reachable from a `const char*` first
|
||||
// argument (which would make calls like AppAction("orca_command", ...) ambiguous).
|
||||
struct AppActionId
|
||||
{
|
||||
std::string id;
|
||||
Level level = Level::Info;
|
||||
wxString message; // empty = "nothing worth showing"
|
||||
};
|
||||
|
||||
// A speed-dial action: identity + user-state seeded from config + how to run itself.
|
||||
@@ -66,35 +52,12 @@ struct AppAction
|
||||
}
|
||||
|
||||
// seeded from AppConfig for the snapshot / sort:
|
||||
bool favourite = false;
|
||||
int count = 0;
|
||||
long long last = 0; // epoch seconds
|
||||
|
||||
// Speed Dial presentation: Plugin keeps group empty (the UI falls back to the
|
||||
// source name); Command sets a section label (e.g. "Commands", "Mode").
|
||||
AppActionKind kind = AppActionKind::Plugin;
|
||||
std::string group;
|
||||
// Second-phase input descriptor for the palette: "percent" (jump to layer by a 0-100
|
||||
// value) or "tab" (pick a notebook tab). Empty = run immediately on activation.
|
||||
std::string input;
|
||||
// Tile pictogram: SVG base name under resources/images; empty renders a blank tile (commands
|
||||
// without a GUI icon, plugins). Set from NativeCommands / the setting's category icon.
|
||||
std::string icon;
|
||||
// Settings mode required to edit this action (SettingActions only). The palette prompts before
|
||||
// running an action whose mode is above the user's current mode. comSimple for everything else.
|
||||
ConfigOptionMode required_mode = comSimple;
|
||||
// Description shown in the Speed Dial's footer strip (SettingActions: the localized tooltip).
|
||||
std::string tooltip;
|
||||
// Search-only alias when the title differs from the descriptive ConfigOptionDef name (e.g. title
|
||||
// "Reverse on even", full_label "Overhang reversal"). Empty when the two agree.
|
||||
std::string full_label;
|
||||
// Full wiki URL, when the action has one (SettingActions whose row declared a label_path).
|
||||
std::string help_url;
|
||||
bool favourite = false;
|
||||
int count = 0;
|
||||
long long last = 0; // epoch seconds
|
||||
|
||||
virtual ~AppAction() = default;
|
||||
// Re-resolves + runs (UI thread). `param` carries an optional per-run argument for
|
||||
// commands (e.g. a layer percentage); plugins ignore it.
|
||||
virtual AppActionRunResult run(const std::string& param = {}) const = 0;
|
||||
virtual AppActionRunResult run() const = 0; // re-resolves + runs (UI thread)
|
||||
|
||||
protected:
|
||||
// The definition is constructor-set and immutable. Refreshes replace an action
|
||||
@@ -102,17 +65,10 @@ protected:
|
||||
// why: source_key (not the display name) carries identity, so renaming the source's
|
||||
// display name leaves the id - and its persisted stats/favourite - intact.
|
||||
AppAction(std::string_view prefix, std::string title, std::string source_key, std::string source_name)
|
||||
: m_id(compose_id(prefix, title, source_key))
|
||||
, m_title(std::move(title))
|
||||
, m_source_key(std::move(source_key))
|
||||
, m_source_name(std::move(source_name))
|
||||
{}
|
||||
|
||||
// Explicit-id ctor: for actions whose id must NOT be derived from the display title
|
||||
// (e.g. a setting action keyed by opt_key+type, so a rename/localization never re-keys it).
|
||||
AppAction(AppActionId id, std::string title, std::string source_key, std::string source_name)
|
||||
: m_id(std::move(id.id)), m_title(std::move(title)), m_source_key(std::move(source_key)), m_source_name(std::move(source_name))
|
||||
{}
|
||||
: m_id(compose_id(prefix, title, source_key)),
|
||||
m_title(std::move(title)),
|
||||
m_source_key(std::move(source_key)),
|
||||
m_source_name(std::move(source_name)) {}
|
||||
|
||||
private:
|
||||
std::string m_id; // <prefix>:<title>:<source_key> - stable identity + AppConfig key
|
||||
@@ -121,53 +77,26 @@ private:
|
||||
std::string m_source_name; // display name of the action's source
|
||||
};
|
||||
|
||||
// Stable identity/display name of the built-in ("OrcaSlicer") action source. Shared by the native
|
||||
// command catalog and the dynamically materialised setting/plate/recent actions so every built-in
|
||||
// action re-keys together.
|
||||
inline constexpr const char* kOrcaSourceKey = "orca";
|
||||
inline constexpr const char* kOrcaSourceName = "OrcaSlicer";
|
||||
|
||||
// True when a setting at `setting_mode` cannot be edited in `current_mode` and the UI must switch
|
||||
// first. Developer settings are handled as a separate prompt by the Speed Dial.
|
||||
inline bool requires_mode_switch(ConfigOptionMode setting_mode, ConfigOptionMode current_mode)
|
||||
{
|
||||
return setting_mode > current_mode;
|
||||
}
|
||||
|
||||
// Cap + dedupe a persisted favourite-id list, preserving first-occurrence order. A stale or
|
||||
// hand-edited config must never grow the quick-launch bar past `limit`, and a duplicated id must collapse to its first pin.
|
||||
std::vector<std::string> cap_favourites(const std::vector<std::string>& ids, size_t limit);
|
||||
|
||||
// Self-contained sink and single owner of runnable actions for the app session.
|
||||
//
|
||||
// Workflow:
|
||||
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the current script
|
||||
// capabilities into actions, then materialises the static built-ins from the NativeCommands catalog.
|
||||
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the
|
||||
// current script capabilities into actions.
|
||||
// 2. Loader load/unload callbacks route through refresh_source()/refresh_capability(),
|
||||
// which upsert()/remove() actions. The registry keeps the only action list and
|
||||
// restores persisted user state as actions arrive.
|
||||
// 3. Dynamic built-in families (settings, plates, recent projects) are re-materialised at the top of
|
||||
// snapshot(), because their membership follows live state (the current configs, plate list, recents).
|
||||
// 4. Consumers use by_id(), snapshot(), and run() without knowing the source.
|
||||
// 3. Consumers use by_id(), snapshot(), and run() without knowing the source.
|
||||
//
|
||||
// note: the static catalog lives in NativeCommands; the registry owns the pool, persistence and
|
||||
// dispatch, and materialises the dynamic families inline rather than behind a source interface.
|
||||
// note: there is exactly one source (script plugins), so it lives inline here rather
|
||||
// than behind a polymorphic source interface.
|
||||
class ActionRegistry
|
||||
{
|
||||
public:
|
||||
~ActionRegistry();
|
||||
|
||||
// Subscribes to the plugin loader and enumerates its current actions. Call once
|
||||
// on the UI thread after the plugin system is up; wires the initial list and live
|
||||
// updates together.
|
||||
void init();
|
||||
|
||||
// Rebuilds the built-in command actions in the current UI locale. The command catalog copies
|
||||
// translated titles/groups at construction, so after a live language switch the stored titles
|
||||
// are stale until this runs. Ids are key-based and upsert re-seeds persisted state, so
|
||||
// favourites/run history survive. UI thread only. No-op before init().
|
||||
void relocalize_builtins();
|
||||
|
||||
// Takes ownership, seeds persisted state, then inserts the action or replaces
|
||||
// the action with the same id. A null action is ignored.
|
||||
void upsert(std::unique_ptr<AppAction> action);
|
||||
@@ -176,72 +105,31 @@ public:
|
||||
void remove(const std::string& id);
|
||||
|
||||
// Always-clean read surface. UI thread only.
|
||||
const AppAction* by_id(const std::string& id) const;
|
||||
|
||||
// Hard cap on the favourites bar: the numbered quick-launch slots (Alt/Option+1..9, 0).
|
||||
static constexpr size_t kFavLimit = 10;
|
||||
const AppAction* by_id(const std::string& id) const;
|
||||
|
||||
// Dispatch + write-through (registry is the only thing that touches AppConfig).
|
||||
AppActionRunResult run(const std::string& id, const std::string& param = {}); // runs + bumps stats
|
||||
// Pin/unpin. Returns false when `on` would exceed kFavLimit (the bar is full) so the
|
||||
// caller can surface a "favourites are full" hint instead of silently dropping the pin.
|
||||
bool set_favourite(const std::string& id, bool on);
|
||||
void reorder_favourites(const std::vector<std::string>& ids); // persist a new bar order
|
||||
|
||||
// Ordered pinned list (the source of truth), capped at kFavLimit and deduped, matching the
|
||||
// visible bar the palette renders.
|
||||
std::vector<std::string> favourite_ids() const;
|
||||
AppActionRunResult run(const std::string& id); // runs + bumps stats
|
||||
void set_favourite(const std::string& id, bool on);
|
||||
void reorder_favourites(const std::vector<std::string>& ids); // persist a new bar order
|
||||
|
||||
// Run-confirm gate, keyed by action id (per-action "don't ask again").
|
||||
bool should_ask(const std::string& id) const;
|
||||
void suppress_ask(const std::string& id);
|
||||
|
||||
// Footer expand/collapse preference. Global (applies to every action) and persisted; absent
|
||||
// means expanded, so a fresh config picks the richer default with no migration.
|
||||
bool tooltip_expanded() const;
|
||||
void set_tooltip_expanded(bool expanded);
|
||||
|
||||
// Flat, frecency-sorted snapshot for the webview:
|
||||
// {actions:[...], favourites:[...], recent:[...]} (recent = last-N launched by recency).
|
||||
nlohmann::json snapshot();
|
||||
|
||||
// "Go to tab..." Speed Dial helper: enumerate the MainFrame notebook's current pages
|
||||
// as [{id,title,icon},...], using the page's real label (not the compact-blanked button text).
|
||||
// Live by construction - built-in tabs (Home/Prepare/Preview/Device/Project/Calibration) and
|
||||
// plugin tabs (plugin.<key>.<name>) are all Notebook pages, so a page appears/disappears with
|
||||
// the notebook. Plugin tabs hidden in the overflow menu (many plugins) aren't separate pages and
|
||||
// are not listed. Call on the UI thread; null-safe.
|
||||
nlohmann::json tab_options() const;
|
||||
// Flat, frecency-sorted snapshot for the webview: {actions:[...], favourites:[...]}.
|
||||
nlohmann::json snapshot() const;
|
||||
|
||||
private:
|
||||
void seed_state(AppAction& a) const; // favourite/stats from config
|
||||
AppAction* find(const std::string& id);
|
||||
|
||||
// Read the persisted stats blob + capped favourite list once for a materialisation pass.
|
||||
void load_persisted(nlohmann::json& stats, std::vector<std::string>& favs) const;
|
||||
|
||||
// (Re)materialise the current visible config settings as SettingActions from the live
|
||||
// searcher (respecting printer-tech + user-mode + visibility filtering), removing stale ones.
|
||||
// Called at the top of snapshot() so the palette always reflects the current configs.
|
||||
void materialize_setting_actions();
|
||||
|
||||
// (Re)materialise one "Go to Plate N" action per live plate, so the palette lists every plate
|
||||
// directly on each spawn (no second-phase picker). FFF-editor only; SLA/gcode modes have no
|
||||
// plate UI, so nothing is materialised and stale ids are dropped. Called at the top of snapshot().
|
||||
void materialize_plate_actions();
|
||||
|
||||
// (Re)materialise one "Open recent project <name>" action per recent project file, so the
|
||||
// palette lists every recent project and can load it by clicking. Keyed by file path (stable);
|
||||
// files that no longer exist are skipped and their stale ids dropped. Called at the top of snapshot().
|
||||
void materialize_recent_project_actions();
|
||||
void seed_state(AppAction& a) const; // favourite/stats from config
|
||||
AppAction* find(const std::string& id);
|
||||
|
||||
// Loader callbacks (marshalled to the UI thread) land here. refresh_source rebuilds
|
||||
// one plugin's whole action set; refresh_capability touches a single capability.
|
||||
void refresh_source(const std::string& plugin_key, ActionChange change);
|
||||
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
|
||||
|
||||
bool m_started = false; // init() runs exactly once; guards double-subscription
|
||||
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
|
||||
bool m_started = false; // init() runs exactly once; guards double-subscription
|
||||
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -403,7 +403,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// bind to their default mode; the other modes stay in the toolbar flyout. Registered here
|
||||
// where select_tool is in scope; the closures run at key-press time (members are live by then).
|
||||
auto sk_key = [this, select_tool](int ch, DesignSketchTool::Mode m, const wxString& h) {
|
||||
m_keys_sketch[ch] = [select_tool, m, h] { select_tool(m, h); };
|
||||
m_keys_sketch[ch] = [this, select_tool, m, h] { select_tool(m, h); };
|
||||
};
|
||||
sk_key('L', DesignSketchTool::Mode::Line, _L("Line — click start, then end"));
|
||||
sk_key('R', DesignSketchTool::Mode::CornerRect, _L("Rectangle — click two opposite corners"));
|
||||
@@ -1445,16 +1445,16 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// Choose FIRST, then open: open_tool() titles the card from m_dressup_type, so setting
|
||||
// it afterwards left the header reading "Fillet 1" over a chamfer. Nothing in the
|
||||
// opener resets the combo, so this order is safe.
|
||||
m_verb_actions["btn:dress#0"] = [this, open_feature] {
|
||||
m_verb_actions["btn:dress#0"] = [this, open_feature, k_dress] {
|
||||
if (m_dressup_type) m_dressup_type->SetSelection(0); open_feature(k_dress); };
|
||||
m_verb_actions["btn:dress#1"] = [this, open_feature] {
|
||||
m_verb_actions["btn:dress#1"] = [this, open_feature, k_dress] {
|
||||
if (m_dressup_type) m_dressup_type->SetSelection(1); open_feature(k_dress); };
|
||||
for (int op = 0; op < 3; ++op)
|
||||
m_verb_actions["btn:bool#" + std::to_string(op)] = [this, open_feature, op] {
|
||||
m_verb_actions["btn:bool#" + std::to_string(op)] = [this, open_feature, k_bool, op] {
|
||||
open_feature(k_bool);
|
||||
if (m_bool_op) { m_bool_op->SetSelection(op); refresh_preview(); } };
|
||||
for (int t = 0; t < 2; ++t)
|
||||
m_verb_actions["btn:pat#" + std::to_string(t)] = [this, open_feature, t] {
|
||||
m_verb_actions["btn:pat#" + std::to_string(t)] = [this, open_feature, k_pat, t] {
|
||||
open_feature(k_pat); if (m_pattern_type) m_pattern_type->SetSelection(t); };
|
||||
|
||||
auto* b_poly = icon_btn("design_polygon", _L("Polygon"));
|
||||
@@ -6625,7 +6625,7 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
mate_ghost = false;
|
||||
};
|
||||
menu.Bind(wxEVT_MENU_HIGHLIGHT,
|
||||
[this, cs_a, cs_b, opts, &mate_ghost, drop_ghost](wxMenuEvent& e) {
|
||||
[this, cs_a, cs_b, opts, mate_base, &mate_ghost, drop_ghost](wxMenuEvent& e) {
|
||||
const int i = e.GetMenuId() - (mate_base + 1);
|
||||
// Off the palette (a verb row, the header, or nothing) — a stale ghost from the row
|
||||
// you just left is worse than none, so it goes as soon as the cursor does.
|
||||
@@ -6635,7 +6635,7 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
m_viewport->repaint_now(); // synchronous: the popup owns the loop, a queued repaint is never serviced
|
||||
});
|
||||
|
||||
menu.Bind(wxEVT_MENU, [this, cs_a, cs_b, opts, drop_ghost](wxCommandEvent& e) {
|
||||
menu.Bind(wxEVT_MENU, [this, cs_a, cs_b, opts, mate_base, drop_ghost](wxCommandEvent& e) {
|
||||
const int i = e.GetId() - (mate_base + 1);
|
||||
if (i < 0 || i >= int(opts.size()) || !opts[i].viable) return;
|
||||
drop_ghost(); // the real bodies are about to become the ghost's pose
|
||||
@@ -6666,7 +6666,7 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
// status message on screen, and clicking one created nothing at all. The whole mate palette
|
||||
// enumerated perfectly and fired nothing. Restricting the range keeps each half to its own ids
|
||||
// regardless of bind order.
|
||||
menu.Bind(wxEVT_MENU_HIGHLIGHT, [this, &bound](wxMenuEvent& e) {
|
||||
menu.Bind(wxEVT_MENU_HIGHLIGHT, [this, &bound, base](wxMenuEvent& e) {
|
||||
const int i = e.GetMenuId() - base;
|
||||
if (i < 0 || i >= int(bound.size()) || bound[i] == nullptr || bound[i]->hint == nullptr)
|
||||
return;
|
||||
@@ -6674,7 +6674,7 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
set_status(wxGetTranslation(wxString::FromUTF8(bound[i]->hint)));
|
||||
m_status->Update(); // the popup owns the loop; without this the line repaints late
|
||||
}, base, base + 499); // 499: the mate section starts at base + 500 (see mate_base)
|
||||
menu.Bind(wxEVT_MENU, [this, &bound](wxCommandEvent& e) {
|
||||
menu.Bind(wxEVT_MENU, [this, &bound, base](wxCommandEvent& e) {
|
||||
const int i = e.GetId() - base;
|
||||
if (i >= 0 && i < int(bound.size()) && bound[i])
|
||||
run_offer_action(bound[i]->action);
|
||||
@@ -7935,7 +7935,7 @@ void DesignPanel::apply_live_constraint(SketchConstraintType type)
|
||||
constraint_reject_text(plan.reason, type), int(sel.size())));
|
||||
return;
|
||||
case ConstraintPlan::Kind::AskValue:
|
||||
m_viewport->open_inline_value(plan.prefill, [plan, commit](double v) {
|
||||
m_viewport->open_inline_value(plan.prefill, [this, plan, commit](double v) {
|
||||
commit(plan.defs, v);
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -121,7 +121,7 @@ void SketchInlineEditor::do_commit()
|
||||
// just sits there and the user has no idea what it wants. Say so in the title line and
|
||||
// keep editing.
|
||||
ux_trace("refused", m_title, std::string("typed=") + m_buf);
|
||||
m_err = (m_buf[0] == '\0') ? _u8L("Enter a number") : _u8L("Not a number");
|
||||
m_err = (m_buf[0] == '\0') ? into_u8(_L("Enter a number")) : into_u8(_L("Not a number"));
|
||||
m_focus_pending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
|
||||
top_sizer->Add(m_text_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
|
||||
top_sizer->Add(m_switch_liveview_retry, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
|
||||
|
||||
m_switch_liveview_retry->Bind(wxEVT_TOGGLEBUTTON, [](wxCommandEvent &e) {
|
||||
m_switch_liveview_retry->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &e) {
|
||||
wxGetApp().app_config->set("liveview", "auto_retry", e.IsChecked());
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
@@ -997,17 +997,13 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename;
|
||||
}
|
||||
}
|
||||
void GCodeViewer::SequentialView::render_marker(const bool has_render_path, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type)
|
||||
void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type)
|
||||
{
|
||||
if (has_render_path && m_show_marker)
|
||||
if (has_render_path && m_show_marker) {
|
||||
// marker.set_world_offset(current_offset);
|
||||
marker.render(canvas_width, canvas_height, view_type);
|
||||
}
|
||||
|
||||
void GCodeViewer::SequentialView::render_overlay(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type)
|
||||
{
|
||||
if (has_render_path && m_show_marker)
|
||||
marker.render_position_window(viewer, canvas_width, canvas_height, view_type);
|
||||
}
|
||||
|
||||
//float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height();
|
||||
// BBS
|
||||
@@ -1622,7 +1618,7 @@ void GCodeViewer::reset()
|
||||
}
|
||||
|
||||
//BBS: GUI refactor: add canvas width and height
|
||||
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
|
||||
void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin)
|
||||
{
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
render_shells(canvas_width, canvas_height);
|
||||
@@ -1632,20 +1628,6 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height)
|
||||
|
||||
render_toolpaths();
|
||||
|
||||
auto current = m_viewer.get_view_visible_range();
|
||||
auto endpoints = m_viewer.get_view_full_range();
|
||||
m_sequential_view.m_show_marker = m_sequential_view.m_show_marker || (current.back() != endpoints.back() && !m_no_render_path);
|
||||
const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex();
|
||||
m_sequential_view.marker.set_world_position(libvgcode::convert(curr_vertex.position));
|
||||
m_sequential_view.marker.set_z_offset(m_z_offset + 0.5f);
|
||||
m_sequential_view.render_marker(!m_no_render_path, canvas_width, sequential_view_height(canvas_height), m_viewer.get_view_type());
|
||||
}
|
||||
|
||||
void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_margin)
|
||||
{
|
||||
if (m_viewer.get_extrusion_roles().empty())
|
||||
return;
|
||||
|
||||
float legend_height = 0.0f;
|
||||
render_legend(legend_height, canvas_width, canvas_height, right_margin);
|
||||
|
||||
@@ -1654,7 +1636,16 @@ void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_
|
||||
m_user_mode = wxGetApp().get_mode();
|
||||
}
|
||||
|
||||
m_sequential_view.render_overlay(!m_no_render_path, legend_height, &m_viewer, m_viewer.get_current_vertex().gcode_id, canvas_width, sequential_view_height(canvas_height), right_margin * m_scale, m_viewer.get_view_type());
|
||||
//BBS fixed bottom_margin for space to render horiz slider
|
||||
int bottom_margin = SLIDER_BOTTOM_MARGIN * GCODE_VIEWER_SLIDER_SCALE;
|
||||
auto current = m_viewer.get_view_visible_range();
|
||||
auto endpoints = m_viewer.get_view_full_range();
|
||||
m_sequential_view.m_show_marker = m_sequential_view.m_show_marker || (current.back() != endpoints.back() && !m_no_render_path);
|
||||
const libvgcode::PathVertex& curr_vertex = m_viewer.get_current_vertex();
|
||||
m_sequential_view.marker.set_world_position(libvgcode::convert(curr_vertex.position));
|
||||
m_sequential_view.marker.set_z_offset(m_z_offset + 0.5f);
|
||||
// BBS fixed buttom margin. m_moves_slider.pos_y
|
||||
m_sequential_view.render(!m_no_render_path, legend_height, &m_viewer, m_viewer.get_current_vertex().gcode_id, canvas_width, canvas_height - bottom_margin * m_scale, right_margin * m_scale, m_viewer.get_view_type());
|
||||
|
||||
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
|
||||
if (is_legend_shown()) {
|
||||
@@ -1695,14 +1686,6 @@ void GCodeViewer::render_overlay(int canvas_width, int canvas_height, int right_
|
||||
render_slider(canvas_width, canvas_height);
|
||||
}
|
||||
|
||||
int GCodeViewer::sequential_view_height(int canvas_height) const
|
||||
{
|
||||
//BBS fixed bottom_margin for space to render horiz slider
|
||||
const int bottom_margin = SLIDER_BOTTOM_MARGIN * GCODE_VIEWER_SLIDER_SCALE;
|
||||
// BBS fixed buttom margin. m_moves_slider.pos_y
|
||||
return canvas_height - bottom_margin * m_scale;
|
||||
}
|
||||
|
||||
#define ENABLE_CALIBRATION_THUMBNAIL_OUTPUT 0
|
||||
#if ENABLE_CALIBRATION_THUMBNAIL_OUTPUT
|
||||
static void debug_calibration_output_thumbnail(const ThumbnailData& thumbnail_data)
|
||||
|
||||
@@ -153,10 +153,7 @@ public:
|
||||
GCodeWindow gcode_window;
|
||||
float m_scale = 1.0;
|
||||
bool m_show_marker = false;
|
||||
// The tool marker at the current move, drawn in 3D.
|
||||
void render_marker(const bool has_render_path, int canvas_width, int canvas_height, const libvgcode::EViewType& view_type);
|
||||
// The marker's position window and the G-code window, both ImGui.
|
||||
void render_overlay(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type);
|
||||
void render(const bool has_render_path, float legend_height, const libvgcode::Viewer* viewer, uint32_t gcode_id, int canvas_width, int canvas_height, int right_margin, const libvgcode::EViewType& view_type);
|
||||
};
|
||||
struct ExtruderFilament
|
||||
{
|
||||
@@ -275,10 +272,7 @@ public:
|
||||
//BBS: add all plates filament statistics
|
||||
void render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show = true) const;
|
||||
//BBS: GUI refactor: add canvas width and height
|
||||
// Shells, toolpaths and the sequential marker, drawn in 3D.
|
||||
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);
|
||||
void render(int canvas_width, int canvas_height, int right_margin);
|
||||
//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);
|
||||
@@ -368,8 +362,6 @@ public:
|
||||
private:
|
||||
//BBS: always load shell at preview
|
||||
//void load_shells(const Print& print);
|
||||
// Canvas height minus the room the horizontal slider takes.
|
||||
int sequential_view_height(int canvas_height) const;
|
||||
void render_toolpaths();
|
||||
void render_shells(int canvas_width, int canvas_height);
|
||||
|
||||
|
||||
+167
-340
@@ -69,7 +69,6 @@
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
@@ -371,6 +370,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D&
|
||||
|
||||
void GLCanvas3D::LayersEditing::render_overlay(GLCanvas3D& canvas)
|
||||
{
|
||||
render_variable_layer_height_dialog(canvas);
|
||||
render_active_object_annotations(canvas);
|
||||
render_profile(canvas);
|
||||
}
|
||||
@@ -1042,6 +1042,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_ORIENT_PARTPLATE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_OPEN_SPEED_DIAL, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
||||
@@ -1191,7 +1192,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
||||
#endif // ENABLE_RETINA_GL
|
||||
}
|
||||
m_timer_set_color.Bind(wxEVT_TIMER, &GLCanvas3D::on_set_color_timer, this);
|
||||
m_fps_overlay_timer.Bind(wxEVT_TIMER, &GLCanvas3D::on_fps_overlay_timer, this);
|
||||
load_arrange_settings();
|
||||
|
||||
m_selection.set_volumes(&m_volumes.volumes);
|
||||
@@ -1208,7 +1208,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
||||
GLCanvas3D::~GLCanvas3D()
|
||||
{
|
||||
if (_set_current()) {
|
||||
m_scene_cache.reset();
|
||||
if (m_fxaa_texture_id != 0) {
|
||||
glsafe(::glDeleteTextures(1, &m_fxaa_texture_id));
|
||||
m_fxaa_texture_id = 0;
|
||||
@@ -1356,7 +1355,6 @@ void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) {
|
||||
m_gizmos.set_icon_dirty();
|
||||
}
|
||||
}
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
const float GLCanvas3D::get_scale() const
|
||||
@@ -1941,14 +1939,7 @@ bool GLCanvas3D::make_current_for_postinit() {
|
||||
return _set_current();
|
||||
}
|
||||
|
||||
// Redraws the scene and presents it.
|
||||
void GLCanvas3D::render(bool only_init)
|
||||
{
|
||||
m_presented_signature.reset();
|
||||
_render_frame(true, only_init);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
|
||||
{
|
||||
if (m_in_render) {
|
||||
// if called recursively, return
|
||||
@@ -2051,33 +2042,121 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
|
||||
}
|
||||
}
|
||||
|
||||
const bool overlay_tick = m_fps_overlay_tick;
|
||||
m_fps_overlay_tick = false;
|
||||
// draw scene
|
||||
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.
|
||||
m_shadow_map_valid = false;
|
||||
_render_background();
|
||||
|
||||
// An overlay-only frame reuses the last scene pass. The overlay is rebuilt either way, and drawn
|
||||
// below once it is known whether the frame differs from the one on screen.
|
||||
const bool reuse_scene = !scene_dirty && _can_reuse_cached_scene(camera);
|
||||
if (!reuse_scene) {
|
||||
_render_scene(camera, cnv_size);
|
||||
if (!overlay_tick)
|
||||
m_render_stats.increment_scene_fps_counter();
|
||||
//BBS add partplater rendering logic
|
||||
bool only_current = false, only_body = false, no_partplate = false;
|
||||
bool show_grid = true;
|
||||
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||
if (!m_main_toolbar.is_enabled()) {
|
||||
//only_body = true;
|
||||
only_current = true;
|
||||
}
|
||||
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
|
||||
no_partplate = true;
|
||||
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
|
||||
show_grid = false;
|
||||
if (m_axes_at_bed_center)
|
||||
// Design tab: the plate grid is generated from the plate's front-left corner, so it
|
||||
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
|
||||
// on the origin is rendered in its place (see _render_cad_grid).
|
||||
show_grid = false;
|
||||
|
||||
/* view3D render*/
|
||||
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
||||
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
|
||||
// floating would read as a rendering fault rather than a deliberate view option.
|
||||
if (!no_partplate && m_show_bed)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
|
||||
if (!no_partplate && m_show_bed) //BBS: add outline logic
|
||||
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
|
||||
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
|
||||
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
|
||||
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
|
||||
//BBS: add outline logic
|
||||
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
|
||||
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_sla_slices();
|
||||
_render_selection();
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
/* preview render */
|
||||
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_sla_slices();
|
||||
_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);
|
||||
// BBS: GUI refactor: add canvas size as parameters
|
||||
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
|
||||
}
|
||||
/* assemble render*/
|
||||
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
||||
//BBS: add outline logic
|
||||
//if (m_show_world_axes) {
|
||||
// m_axes.render();
|
||||
//}
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_selection();
|
||||
//_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
|
||||
_render_plane();
|
||||
//BBS: add outline logic insteadof selection under assemble view
|
||||
//_render_selection();
|
||||
// BBS: add outline logic
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview)
|
||||
// BBS: GUI refactor: add canvas size as parameters
|
||||
_render_gcode_overlay(cnv_size.get_width(), cnv_size.get_height());
|
||||
_render_sequential_clearance();
|
||||
#if ENABLE_RENDER_SELECTION_CENTER
|
||||
_render_selection_center();
|
||||
#endif // ENABLE_RENDER_SELECTION_CENTER
|
||||
|
||||
// sidebar hints need to be rendered before the gizmos because the depth buffer
|
||||
// could be invalidated by the following gizmo render methods
|
||||
_render_selection_sidebar_hints();
|
||||
_render_current_gizmo();
|
||||
|
||||
#if ENABLE_RAYCAST_PICKING_DEBUG
|
||||
if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
|
||||
m_scene_raycaster.render_hit(camera);
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
#if ENABLE_SHOW_CAMERA_TARGET
|
||||
_render_camera_target();
|
||||
#endif // ENABLE_SHOW_CAMERA_TARGET
|
||||
|
||||
if (m_picking_enabled && m_rectangle_selection.is_dragging())
|
||||
m_rectangle_selection.render(*this);
|
||||
|
||||
if (_is_ssao_enabled())
|
||||
_render_ssao_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||
|
||||
if (_is_fxaa_enabled())
|
||||
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||
|
||||
// Design tab: interactive 2D sketch overlay, drawn over the scene but
|
||||
// beneath the UI overlays (toolbars, labels).
|
||||
#ifdef SLIC3R_CAD
|
||||
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
|
||||
m_design_sketch_tool->render(*this);
|
||||
#endif
|
||||
|
||||
// draw overlays
|
||||
_render_overlays();
|
||||
|
||||
const int current_fps = m_render_stats.get_fps_and_reset_if_needed();
|
||||
if (_is_fps_overlay_enabled()) {
|
||||
if (_is_fps_overlay_enabled())
|
||||
_render_fps_overlay(current_fps);
|
||||
// The timer requests an overlay-only frame a second from now. A frame it requested
|
||||
// re-arms it only while a count is above zero.
|
||||
if (!overlay_tick || current_fps > 0 || m_render_stats.get_scene_fps() > 0)
|
||||
m_fps_overlay_timer.StartOnce(1000);
|
||||
}
|
||||
|
||||
if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
|
||||
ImGui::ShowMetricsWindow();
|
||||
@@ -2163,149 +2242,14 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
|
||||
wxGetApp().plater()->get_dailytips()->render();
|
||||
}
|
||||
|
||||
ImDrawData* draw_data = wxGetApp().imgui()->end_frame();
|
||||
|
||||
std::optional<size_t> signature;
|
||||
if (_is_frame_skipping_enabled())
|
||||
signature = _overlay_signature(draw_data);
|
||||
|
||||
if (reuse_scene) {
|
||||
// A reused scene under an unchanged overlay is the frame already on screen.
|
||||
if (signature.has_value() && signature == m_presented_signature)
|
||||
return;
|
||||
m_scene_cache.render(m_background);
|
||||
}
|
||||
|
||||
_render_overlay_toolbars();
|
||||
|
||||
wxGetApp().imgui()->render(draw_data);
|
||||
wxGetApp().imgui()->render();
|
||||
|
||||
// On Wayland, eglSwapBuffers blocks when the canvas is hidden or
|
||||
// occluded. Skip the swap to avoid stalling the render loop.
|
||||
if (m_canvas->IsShownOnScreen()) {
|
||||
m_canvas->SwapBuffers();
|
||||
if (!overlay_tick)
|
||||
m_render_stats.increment_fps_counter();
|
||||
m_presented_signature = signature;
|
||||
m_render_stats.increment_fps_counter();
|
||||
}
|
||||
else
|
||||
m_presented_signature.reset();
|
||||
}
|
||||
|
||||
// Everything drawn into the 3D scene, from the clear to the post processing passes, ending in the
|
||||
// capture an overlay-only frame reuses.
|
||||
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.
|
||||
m_shadow_map_valid = false;
|
||||
_render_background();
|
||||
|
||||
//BBS add partplater rendering logic
|
||||
bool only_current = false, only_body = false, no_partplate = false;
|
||||
bool show_grid = true;
|
||||
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||
if (!m_main_toolbar.is_enabled()) {
|
||||
//only_body = true;
|
||||
only_current = true;
|
||||
}
|
||||
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
|
||||
no_partplate = true;
|
||||
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
|
||||
show_grid = false;
|
||||
if (m_axes_at_bed_center)
|
||||
// Design tab: the plate grid is generated from the plate's front-left corner, so it
|
||||
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
|
||||
// on the origin is rendered in its place (see _render_cad_grid).
|
||||
show_grid = false;
|
||||
|
||||
/* view3D render*/
|
||||
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
||||
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
|
||||
// floating would read as a rendering fault rather than a deliberate view option.
|
||||
if (!no_partplate && m_show_bed)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
|
||||
if (!no_partplate && m_show_bed) //BBS: add outline logic
|
||||
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
|
||||
if (m_axes_at_bed_center && m_show_bed && !no_partplate)
|
||||
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
|
||||
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
|
||||
//BBS: add outline logic
|
||||
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
|
||||
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_sla_slices();
|
||||
_render_selection();
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
/* preview render */
|
||||
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_sla_slices();
|
||||
_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);
|
||||
// BBS: GUI refactor: add canvas size as parameters
|
||||
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
|
||||
}
|
||||
/* assemble render*/
|
||||
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
||||
//BBS: add outline logic
|
||||
//if (m_show_world_axes) {
|
||||
// m_axes.render();
|
||||
//}
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
||||
_render_selection();
|
||||
//_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
|
||||
_render_plane();
|
||||
//BBS: add outline logic insteadof selection under assemble view
|
||||
//_render_selection();
|
||||
// BBS: add outline logic
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
||||
_render_wireframe_overlay();
|
||||
}
|
||||
|
||||
_render_sequential_clearance();
|
||||
#if ENABLE_RENDER_SELECTION_CENTER
|
||||
_render_selection_center();
|
||||
#endif // ENABLE_RENDER_SELECTION_CENTER
|
||||
// sidebar hints need to be rendered before the gizmos because the depth buffer
|
||||
// could be invalidated by the following gizmo render methods
|
||||
_render_selection_sidebar_hints();
|
||||
_render_current_gizmo();
|
||||
|
||||
#if ENABLE_RAYCAST_PICKING_DEBUG
|
||||
if (m_picking_enabled && !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging())
|
||||
m_scene_raycaster.render_hit(camera);
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
#if ENABLE_SHOW_CAMERA_TARGET
|
||||
_render_camera_target();
|
||||
#endif // ENABLE_SHOW_CAMERA_TARGET
|
||||
|
||||
if (m_picking_enabled && m_rectangle_selection.is_dragging())
|
||||
m_rectangle_selection.render(*this);
|
||||
|
||||
if (_is_ssao_enabled())
|
||||
_render_ssao_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||
|
||||
if (_is_fxaa_enabled())
|
||||
_render_fxaa_pass(static_cast<unsigned int>(cnv_size.get_width()), static_cast<unsigned int>(cnv_size.get_height()));
|
||||
|
||||
// Design tab: interactive 2D sketch overlay, drawn over the scene but
|
||||
// beneath the UI overlays (toolbars, labels).
|
||||
#ifdef SLIC3R_CAD
|
||||
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
|
||||
m_design_sketch_tool->render(*this);
|
||||
#endif
|
||||
|
||||
_capture_scene_cache(camera);
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
|
||||
@@ -3283,30 +3227,29 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
// Toolbar states, notifications and ImGui's own layout settling only touch the overlay.
|
||||
m_overlay_dirty |= m_main_toolbar.update_items_state();
|
||||
m_dirty |= m_main_toolbar.update_items_state();
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
m_overlay_dirty |= m_assemble_view_toolbar.update_items_state();
|
||||
m_dirty |= m_assemble_view_toolbar.update_items_state();
|
||||
// BBS
|
||||
//m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
|
||||
m_overlay_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
|
||||
m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
|
||||
// apply() DRAINS the 3D-mouse queue, so only the canvas actually on screen may call it: a
|
||||
// hidden canvas renders nothing, so the motion it swallowed moves the shared camera without
|
||||
// ever being drawn and the next visible frame jumps several states at once.
|
||||
bool mouse3d_controller_applied = _is_shown_on_screen()
|
||||
&& wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
|
||||
m_dirty |= mouse3d_controller_applied;
|
||||
m_overlay_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
|
||||
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
|
||||
auto gizmo = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().get_current();
|
||||
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
// ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
|
||||
bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
|
||||
m_overlay_dirty |= imgui_requires_extra_frame;
|
||||
m_dirty |= imgui_requires_extra_frame;
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
m_dirty |= GLTexture::Compressor::has_compressed_texture_to_refresh();
|
||||
|
||||
if (!m_dirty && !m_overlay_dirty)
|
||||
if (!m_dirty)
|
||||
return;
|
||||
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
@@ -3331,24 +3274,19 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
||||
m_last_frame_start_time = now;
|
||||
}
|
||||
|
||||
// Read and cleared before the render; a request made during it is left for the next frame.
|
||||
const bool scene_dirty = m_dirty;
|
||||
m_dirty = false;
|
||||
m_overlay_dirty = false;
|
||||
_refresh_if_shown_on_screen(scene_dirty);
|
||||
_refresh_if_shown_on_screen();
|
||||
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
|
||||
#else
|
||||
if (m_extra_frame_requested || mouse3d_controller_applied) {
|
||||
m_dirty = true;
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
m_extra_frame_requested = false;
|
||||
evt.RequestMore();
|
||||
}
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
else if (imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
|
||||
// ImGui settling a window or fading a tooltip.
|
||||
m_overlay_dirty = true;
|
||||
evt.RequestMore();
|
||||
}
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
else
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_char(wxKeyEvent& evt)
|
||||
@@ -3644,6 +3582,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
||||
break;
|
||||
}
|
||||
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
|
||||
case ' ': {
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D)
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_OPEN_SPEED_DIAL));
|
||||
break;
|
||||
}
|
||||
case 'A':
|
||||
case 'a':
|
||||
{
|
||||
@@ -4187,12 +4130,6 @@ void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
|
||||
m_timer_set_color.Stop();
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_fps_overlay_timer(wxTimerEvent& evt)
|
||||
{
|
||||
m_fps_overlay_tick = true;
|
||||
_set_overlay_as_dirty();
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
void GLCanvas3D::schedule_extra_frame(int milliseconds)
|
||||
{
|
||||
@@ -4370,18 +4307,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
// Hovering an ImGui window only changes the overlay.
|
||||
const bool overlay_only = evt.Moving() && !m_mouse.dragging;
|
||||
// ImGui takes a press or a release only inside a frame. Motion is rendered from on_idle().
|
||||
if (evt.ButtonDown() || evt.ButtonUp() || evt.ButtonDClick())
|
||||
_render_frame(true);
|
||||
render();
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
||||
if (overlay_only)
|
||||
_set_overlay_as_dirty();
|
||||
else
|
||||
m_dirty = true;
|
||||
m_dirty = true;
|
||||
// do not return if dragging or tooltip not empty to allow for tooltip update
|
||||
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
|
||||
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving()))
|
||||
@@ -4524,9 +4454,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
wxGetApp().obj_list()->selection_changed();
|
||||
}
|
||||
|
||||
// A gizmo that acts on a click or a drag may not request a frame itself.
|
||||
if (!evt.Moving())
|
||||
m_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5018,7 +4945,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
if (m_selection.is_empty())
|
||||
m_gizmos.reset_all_states();
|
||||
|
||||
_set_overlay_as_dirty();
|
||||
m_dirty = true;
|
||||
}
|
||||
else
|
||||
evt.Skip();
|
||||
@@ -5068,7 +4995,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
|
||||
void GLCanvas3D::on_paint(wxPaintEvent& evt)
|
||||
{
|
||||
m_presented_signature.reset();
|
||||
if (m_initialized) {
|
||||
#ifdef __WXMSW__
|
||||
// Idle events are not dispatched during the Windows resize modal loop,
|
||||
@@ -5622,11 +5548,7 @@ void GLCanvas3D::update_gizmos_on_off_state()
|
||||
|
||||
void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on)
|
||||
{
|
||||
const std::string field = focus_on ? opt_key : "";
|
||||
// The gizmo panels report this on every build.
|
||||
if (m_sidebar_field == field)
|
||||
return;
|
||||
m_sidebar_field = field;
|
||||
m_sidebar_field = focus_on ? opt_key : "";
|
||||
|
||||
//BBS: this event was sent from gizmo now, no need to clear gizmo
|
||||
//if (!m_sidebar_field.empty())
|
||||
@@ -6724,9 +6646,6 @@ void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const
|
||||
//if (thumbnail_params.transparent_background)
|
||||
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
|
||||
|
||||
// Puts the canvas viewport back in place of the thumbnail one set above.
|
||||
wxGetApp().plater()->get_camera().apply_viewport();
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
||||
@@ -6977,6 +6896,9 @@ void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
debug_output_thumbnail(thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
|
||||
// restore the default framebuffer size to avoid flickering on the 3D scene
|
||||
//wxGetApp().plater()->get_camera().apply_viewport();
|
||||
}
|
||||
|
||||
//BBS: GUI refractor
|
||||
@@ -7525,15 +7447,15 @@ void GLCanvas3D::_update_camera_zoom(double zoom)
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::_refresh_if_shown_on_screen(bool scene_dirty)
|
||||
void GLCanvas3D::_refresh_if_shown_on_screen()
|
||||
{
|
||||
if (_is_shown_on_screen()) {
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
||||
|
||||
// Because of performance problems on macOS, where PaintEvents are not delivered
|
||||
// frequently enough, we render here directly when we can.
|
||||
_render_frame(scene_dirty);
|
||||
// frequently enough, we call render() here directly when we can.
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7925,36 +7847,6 @@ bool GLCanvas3D::_is_fps_overlay_enabled() const
|
||||
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_scene_cache_enabled() const
|
||||
{
|
||||
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SCENE_CACHE);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_scene_cacheable() const
|
||||
{
|
||||
if (!_is_scene_cache_enabled())
|
||||
return false;
|
||||
|
||||
#ifdef SLIC3R_CAD
|
||||
// The Design tab draws its sketch overlay, ImGui included, inside the scene.
|
||||
if (m_design_sketch_tool != nullptr && m_design_sketch_tool->has_display())
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// The scene follows the cursor during a drag, under a gizmo that draws at the cursor, and while
|
||||
// the cursor is on the layer height bar, where the object shader draws a band at its height.
|
||||
const GLGizmoBase* gizmo = m_gizmos.get_current();
|
||||
const bool cursor_on_layers_bar = is_layers_editing_enabled() &&
|
||||
m_layers_editing.bar_rect_contains(*this, (float)m_mouse.position.x(), (float)m_mouse.position.y());
|
||||
return !m_mouse.dragging && !m_gizmos.is_dragging() && !m_rectangle_selection.is_dragging() &&
|
||||
(gizmo == nullptr || !gizmo->render_follows_cursor()) && !cursor_on_layers_bar;
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_is_frame_skipping_enabled() const
|
||||
{
|
||||
return wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_SKIP_IDENTICAL_FRAMES);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_fps_overlay(int fps) const
|
||||
{
|
||||
if (fps < 0)
|
||||
@@ -7975,8 +7867,6 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoInputs);
|
||||
imgui.text(std::string("FPS: ") + std::to_string(fps));
|
||||
// The subset of those frames that redrew the scene rather than reusing the cached one.
|
||||
imgui.text(std::string("3D: ") + std::to_string(m_render_stats.get_scene_fps()));
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
@@ -7989,7 +7879,23 @@ void GLCanvas3D::_render_fxaa_pass(unsigned int width, unsigned int height)
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
GLTexture::copy_from_framebuffer(m_fxaa_texture_id, m_fxaa_texture_size, width, height, GL_LINEAR);
|
||||
if (m_fxaa_texture_id == 0) {
|
||||
glsafe(::glGenTextures(1, &m_fxaa_texture_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
}
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_fxaa_texture_id));
|
||||
if (m_fxaa_texture_size[0] != width || m_fxaa_texture_size[1] != height) {
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
m_fxaa_texture_size = { width, height };
|
||||
}
|
||||
|
||||
glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height));
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
@@ -8165,51 +8071,6 @@ void GLCanvas3D::_render_ssao_pass(unsigned int width, unsigned int height)
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
}
|
||||
|
||||
SceneCache::Key GLCanvas3D::_scene_cache_key(const Camera& camera) const
|
||||
{
|
||||
SceneCache::Key key;
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
key.size = { { (unsigned int)viewport[2], (unsigned int)viewport[3] } };
|
||||
key.view_matrix = camera.get_view_matrix();
|
||||
key.projection_matrix = camera.get_projection_matrix();
|
||||
// Hover reaches the scene only through the sinking contour a hovered volume draws over
|
||||
// itself (GLVolumeCollection::render()), the plate icons (PartPlate::render_icons()) and the
|
||||
// open gizmo's grabbers.
|
||||
for (size_t i = 0; i < m_volumes.volumes.size(); ++i) {
|
||||
const GLVolume& volume = *m_volumes.volumes[i];
|
||||
if (volume.hover != GLVolume::HS_None && volume.is_sinking() && !volume.is_below_printbed())
|
||||
key.sinking_hover_volume_idxs.emplace_back((int)i);
|
||||
}
|
||||
for (int id : m_hover_plate_idxs) {
|
||||
if (id % PartPlate::GRABBER_COUNT != 0)
|
||||
key.hover_plate_icon_idxs.emplace_back(id);
|
||||
}
|
||||
const GLGizmoBase* gizmo = m_gizmos.get_current();
|
||||
key.gizmo_hover_id = gizmo != nullptr ? gizmo->get_hover_id() : -1;
|
||||
key.render_preview = m_render_preview;
|
||||
return key;
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_can_reuse_cached_scene(const Camera& camera) const
|
||||
{
|
||||
return _is_scene_cacheable() && m_scene_cache.matches(_scene_cache_key(camera));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_capture_scene_cache(const Camera& camera)
|
||||
{
|
||||
if (!_is_scene_cache_enabled()) {
|
||||
m_scene_cache.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_is_scene_cacheable()) {
|
||||
m_scene_cache.invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
m_scene_cache.capture(_scene_cache_key(camera));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_background()
|
||||
{
|
||||
bool use_error_color = false;
|
||||
@@ -8937,12 +8798,7 @@ void GLCanvas3D::_render_wireframe_overlay()
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
|
||||
{
|
||||
m_gcode_viewer.render_scene(canvas_width, canvas_height);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_gcode_overlay(int canvas_width, int canvas_height)
|
||||
{
|
||||
m_gcode_viewer.render_overlay(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE);
|
||||
m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN * GCODE_VIEWER_SLIDER_SCALE);
|
||||
IMSlider *layers_slider = m_gcode_viewer.get_layers_slider();
|
||||
IMSlider *moves_slider = m_gcode_viewer.get_moves_slider();
|
||||
|
||||
@@ -9074,14 +8930,20 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale()
|
||||
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
|
||||
}
|
||||
|
||||
// The ImGui half of the overlay, drawn by ImGui at the end of the frame.
|
||||
void GLCanvas3D::_render_overlays()
|
||||
{
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
_check_and_update_toolbar_icon_scale();
|
||||
|
||||
_render_assemble_control();
|
||||
_render_assemble_info();
|
||||
|
||||
_render_separator_toolbar_right();
|
||||
_render_separator_toolbar_left();
|
||||
_render_main_toolbar();
|
||||
_render_collapse_toolbar();
|
||||
_render_assemble_view_toolbar();
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
_render_imgui_select_plate_toolbar();
|
||||
_render_return_toolbar();
|
||||
@@ -9089,13 +8951,12 @@ void GLCanvas3D::_render_overlays()
|
||||
//_render_view_toolbar();
|
||||
_render_paint_toolbar();
|
||||
|
||||
// The options window of a pressed toolbar item (arrange).
|
||||
m_main_toolbar.render_item_windows(*this);
|
||||
|
||||
m_gizmos.render_overlay_input_window();
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
//move gizmos behind of main
|
||||
_render_gizmos_overlay();
|
||||
|
||||
if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f)
|
||||
m_layers_editing.render_variable_layer_height_dialog(*this);
|
||||
m_layers_editing.render_overlay(*this);
|
||||
|
||||
auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
auto curr_print_seq = curr_plate->get_real_print_seq();
|
||||
@@ -9122,40 +8983,6 @@ void GLCanvas3D::_render_overlays()
|
||||
_render_3d_navigator();
|
||||
|
||||
_render_canvas_toolbar();
|
||||
|
||||
// Recorded by the scene pass, which a reused frame skips.
|
||||
wxGetApp().plater()->get_partplate_list().render_hover_tooltip();
|
||||
}
|
||||
|
||||
// The GL half of the overlay.
|
||||
void GLCanvas3D::_render_overlay_toolbars()
|
||||
{
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
_render_separator_toolbar_right();
|
||||
_render_separator_toolbar_left();
|
||||
_render_main_toolbar();
|
||||
_render_collapse_toolbar();
|
||||
_render_assemble_view_toolbar();
|
||||
//BBS: GUI refactor: GLToolbar
|
||||
//move gizmos behind of main
|
||||
_render_gizmos_overlay();
|
||||
|
||||
if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f)
|
||||
m_layers_editing.render_overlay(*this);
|
||||
}
|
||||
|
||||
size_t GLCanvas3D::_overlay_signature(const ImDrawData* draw_data) const
|
||||
{
|
||||
// The recorded ImGui geometry plus the state of the toolbars and the gizmo bar, which draw
|
||||
// outside ImGui.
|
||||
size_t hash = ImGuiWrapper::draw_data_signature(draw_data);
|
||||
for (size_t state_hash : { m_main_toolbar.get_state_hash(), m_separator_toolbar.get_state_hash(),
|
||||
m_assemble_view_toolbar.get_state_hash(),
|
||||
wxGetApp().plater()->get_collapse_toolbar().get_state_hash(),
|
||||
m_gizmos.get_overlay_state_hash() })
|
||||
boost::hash_combine(hash, state_hash);
|
||||
return hash;
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_style_editor()
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "GLToolbar.hpp"
|
||||
#include "Event.hpp"
|
||||
@@ -18,7 +17,6 @@
|
||||
#include "GCodeViewer.hpp"
|
||||
#include "Camera.hpp"
|
||||
#include "SceneRaycaster.hpp"
|
||||
#include "SceneCache.hpp"
|
||||
#include "IMToolbar.hpp"
|
||||
#include "slic3r/GUI/3DBed.hpp"
|
||||
#include "libslic3r/Slicing.hpp"
|
||||
@@ -35,7 +33,6 @@ class wxTimerEvent;
|
||||
class wxPaintEvent;
|
||||
class wxGLCanvas;
|
||||
class wxGLContext;
|
||||
struct ImDrawData;
|
||||
|
||||
// Support for Retina OpenGL on Mac OS.
|
||||
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support, enable it as well.
|
||||
@@ -172,6 +169,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_ORIENT_PARTPLATE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_OPEN_SPEED_DIAL, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent);
|
||||
@@ -405,23 +403,16 @@ class GLCanvas3D
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_measuring_start;
|
||||
int m_fps_out = -1;
|
||||
int m_fps_running = 0;
|
||||
// Frames that redrew the 3D scene rather than reusing the cached one.
|
||||
int m_scene_fps_out = 0;
|
||||
int m_scene_fps_running = 0;
|
||||
public:
|
||||
void increment_fps_counter() { ++m_fps_running; }
|
||||
void increment_scene_fps_counter() { ++m_scene_fps_running; }
|
||||
int get_fps() { return m_fps_out; }
|
||||
int get_scene_fps() const { return m_scene_fps_out; }
|
||||
int get_fps_and_reset_if_needed() {
|
||||
auto cur_time = std::chrono::high_resolution_clock::now();
|
||||
int elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(cur_time-m_measuring_start).count();
|
||||
if (elapsed_ms > 1000 || m_fps_out == -1) {
|
||||
m_measuring_start = cur_time;
|
||||
m_fps_out = int (1000. * m_fps_running / elapsed_ms);
|
||||
m_scene_fps_out = int (1000. * m_scene_fps_running / elapsed_ms);
|
||||
m_fps_running = 0;
|
||||
m_scene_fps_running = 0;
|
||||
}
|
||||
return m_fps_out;
|
||||
}
|
||||
@@ -543,10 +534,6 @@ private:
|
||||
bool m_in_render;
|
||||
wxTimer m_timer;
|
||||
wxTimer m_timer_set_color;
|
||||
// Armed by each frame that draws the FPS overlay; its tick requests an overlay-only frame.
|
||||
wxTimer m_fps_overlay_timer;
|
||||
// True during the frame the timer requested, which is not counted.
|
||||
bool m_fps_overlay_tick{ false };
|
||||
LayersEditing m_layers_editing;
|
||||
Mouse m_mouse;
|
||||
GLGizmosManager m_gizmos;
|
||||
@@ -614,8 +601,6 @@ private:
|
||||
|
||||
// Screen is only refreshed from the OnIdle handler if it is dirty.
|
||||
bool m_dirty;
|
||||
// A frame is needed, and only for the overlay.
|
||||
bool m_overlay_dirty{ false };
|
||||
bool m_initialized;
|
||||
//BBS: add flag to controll rendering
|
||||
bool m_render_preview{ true };
|
||||
@@ -771,11 +756,6 @@ public:
|
||||
unsigned int m_ssao_color_texture_id{ 0 };
|
||||
unsigned int m_ssao_depth_texture_id{ 0 };
|
||||
std::array<unsigned int, 2> m_ssao_texture_size{ { 0, 0 } };
|
||||
// The last scene pass, for frames that only rebuild the overlay.
|
||||
SceneCache m_scene_cache;
|
||||
// Signature of the overlay on screen; empty after render(), a paint request or a frame drawn but
|
||||
// not shown, so the next frame is presented regardless.
|
||||
std::optional<size_t> m_presented_signature;
|
||||
GLModel m_plate_shadow_mask;
|
||||
std::string m_plate_shadow_mask_key;
|
||||
// Depth-based shadow map used to cast object shadows onto other objects and themselves.
|
||||
@@ -1098,7 +1078,6 @@ public:
|
||||
void on_timer(wxTimerEvent& evt);
|
||||
void on_render_timer(wxTimerEvent& evt);
|
||||
void on_set_color_timer(wxTimerEvent& evt);
|
||||
void on_fps_overlay_timer(wxTimerEvent& evt);
|
||||
void on_mouse(wxMouseEvent& evt);
|
||||
void on_gesture(wxGestureEvent& evt);
|
||||
void on_paint(wxPaintEvent& evt);
|
||||
@@ -1296,7 +1275,7 @@ private:
|
||||
void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor);
|
||||
void _update_camera_zoom(double zoom);
|
||||
|
||||
void _refresh_if_shown_on_screen(bool scene_dirty = true);
|
||||
void _refresh_if_shown_on_screen();
|
||||
|
||||
void _picking_pass();
|
||||
void _rectangular_selection_picking_pass();
|
||||
@@ -1304,21 +1283,9 @@ private:
|
||||
bool _is_ssao_enabled() const;
|
||||
int _get_effective_fps_cap() const;
|
||||
bool _is_fps_overlay_enabled() const;
|
||||
bool _is_scene_cache_enabled() const;
|
||||
bool _is_scene_cacheable() const;
|
||||
bool _is_frame_skipping_enabled() const;
|
||||
void _render_fps_overlay(int fps) const;
|
||||
void _render_fxaa_pass(unsigned int width, unsigned int height);
|
||||
void _render_ssao_pass(unsigned int width, unsigned int height);
|
||||
// scene_dirty is false only for a frame that its requester knows to be overlay-only.
|
||||
void _render_frame(bool scene_dirty, bool only_init = false);
|
||||
void _render_scene(const Camera& camera, const Size& cnv_size);
|
||||
// Request a frame that only rebuilds the overlay.
|
||||
void _set_overlay_as_dirty() { m_overlay_dirty = true; }
|
||||
// These read the hover state _picking_pass() sets.
|
||||
SceneCache::Key _scene_cache_key(const Camera& camera) const;
|
||||
bool _can_reuse_cached_scene(const Camera& camera) const;
|
||||
void _capture_scene_cache(const Camera& camera);
|
||||
void _render_background();
|
||||
void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes);
|
||||
// Build the light-space depth shadow map (consumed by gouraud/phong for object & self shadows)
|
||||
@@ -1335,7 +1302,6 @@ private:
|
||||
void _render_wireframe_overlay();
|
||||
//BBS: GUI refactor: add canvas size as parameters
|
||||
void _render_gcode(int canvas_width, int canvas_height);
|
||||
void _render_gcode_overlay(int canvas_width, int canvas_height);
|
||||
//BBS: render a plane for assemble
|
||||
void _render_plane() const;
|
||||
void _render_selection();
|
||||
@@ -1345,8 +1311,6 @@ private:
|
||||
#endif // ENABLE_RENDER_SELECTION_CENTER
|
||||
void _check_and_update_toolbar_icon_scale();
|
||||
void _render_overlays();
|
||||
void _render_overlay_toolbars();
|
||||
size_t _overlay_signature(const ImDrawData* draw_data) const;
|
||||
void _render_style_editor();
|
||||
void _render_volumes_for_picking(const Camera& camera) const;
|
||||
void _render_current_gizmo() const;
|
||||
|
||||
@@ -707,29 +707,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
void GLTexture::copy_from_framebuffer(unsigned int& tex_id, std::array<unsigned int, 2>& tex_size, unsigned int width, unsigned int height, int filter)
|
||||
{
|
||||
if (tex_id == 0) {
|
||||
glsafe(::glGenTextures(1, &tex_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
}
|
||||
else
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
|
||||
|
||||
if (tex_size[0] != width || tex_size[1] != height) {
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
tex_size = { width, height };
|
||||
}
|
||||
|
||||
// Copying from the default framebuffer resolves its multisampling.
|
||||
glsafe(::glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
}
|
||||
|
||||
static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h)
|
||||
{
|
||||
auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; };
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
|
||||
#include <wx/colour.h>
|
||||
@@ -133,9 +132,6 @@ namespace GUI {
|
||||
|
||||
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
|
||||
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
|
||||
// Copies the bound read framebuffer into an RGBA texture, creating it on first use and
|
||||
// reallocating it when the size changes.
|
||||
static void copy_from_framebuffer(unsigned int& tex_id, std::array<unsigned int, 2>& tex_size, unsigned int width, unsigned int height, int filter);
|
||||
|
||||
private:
|
||||
bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/dcmemory.h>
|
||||
@@ -201,10 +199,7 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b
|
||||
};
|
||||
|
||||
GLTexture::render_sub_texture(tex_id, left, right, bottom, top, uvs(tex_width, tex_height, icon_size));
|
||||
}
|
||||
|
||||
void GLToolbarItem::render_window(float left, float right, float bottom, float top) const
|
||||
{
|
||||
if (is_pressed())
|
||||
{
|
||||
if ((m_last_action_type == Left) && m_data.left.can_render())
|
||||
@@ -220,6 +215,13 @@ void GLToolbarItem::render_image(unsigned int tex_id, float left, float right, f
|
||||
//GLTexture::Quad_UVs image_uvs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
|
||||
|
||||
GLTexture::render_sub_texture(tex_id, left, right, bottom, top, image_uvs);
|
||||
|
||||
if (is_pressed()) {
|
||||
if ((m_last_action_type == Left) && m_data.left.can_render())
|
||||
m_data.left.render_callback(left, right, bottom, top);
|
||||
else if ((m_last_action_type == Right) && m_data.right.can_render())
|
||||
m_data.right.render_callback(left, right, bottom, top);
|
||||
}
|
||||
}
|
||||
|
||||
BackgroundTexture::Metadata::Metadata()
|
||||
@@ -542,35 +544,11 @@ void GLToolbar::render(const GLCanvas3D& parent,GLToolbarItem::EType type)
|
||||
switch (m_layout.type)
|
||||
{
|
||||
default:
|
||||
case Layout::Horizontal: { render_horizontal(parent, type, true); break; }
|
||||
case Layout::Vertical: { render_vertical(parent, true); break; }
|
||||
case Layout::Horizontal: { render_horizontal(parent,type); break; }
|
||||
case Layout::Vertical: { render_vertical(parent); break; }
|
||||
}
|
||||
}
|
||||
|
||||
void GLToolbar::render_item_windows(const GLCanvas3D& parent)
|
||||
{
|
||||
if (!m_enabled || m_items.empty())
|
||||
return;
|
||||
|
||||
switch (m_layout.type)
|
||||
{
|
||||
default:
|
||||
case Layout::Horizontal: { render_horizontal(parent, GLToolbarItem::Action, false); break; }
|
||||
case Layout::Vertical: { render_vertical(parent, false); break; }
|
||||
}
|
||||
}
|
||||
|
||||
size_t GLToolbar::get_state_hash() const
|
||||
{
|
||||
size_t hash = 0;
|
||||
boost::hash_combine(hash, m_enabled);
|
||||
for (const GLToolbarItem* item : m_items) {
|
||||
boost::hash_combine(hash, (int)item->get_state());
|
||||
boost::hash_combine(hash, item->is_visible());
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
|
||||
{
|
||||
if (!m_enabled)
|
||||
@@ -1376,7 +1354,7 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
|
||||
}
|
||||
}
|
||||
|
||||
void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType type, bool draw_icons)
|
||||
void GLToolbar::render_horizontal(const GLCanvas3D& parent,GLToolbarItem::EType type)
|
||||
{
|
||||
const Size cnv_size = parent.get_canvas_size();
|
||||
const float cnv_w = (float)cnv_size.get_width();
|
||||
@@ -1407,8 +1385,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType
|
||||
right = left + width * 0.5;
|
||||
const float bottom = top - height;
|
||||
|
||||
if (draw_icons)
|
||||
render_background(left, top, right, bottom, border_w, border_h);
|
||||
render_background(left, top, right, bottom, border_w, border_h);
|
||||
|
||||
left += border_w;
|
||||
top -= border_h;
|
||||
@@ -1423,9 +1400,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType
|
||||
else {
|
||||
//BBS GUI refactor
|
||||
item->render_left_pos = left;
|
||||
if (!draw_icons)
|
||||
item->render_window(left, left + icons_size_x, top - icons_size_y, top);
|
||||
else if (!item->is_action_with_text_image()) {
|
||||
if (!item->is_action_with_text_image()) {
|
||||
unsigned int tex_id = m_icons_texture.get_id();
|
||||
int tex_width = m_icons_texture.get_width();
|
||||
int tex_height = m_icons_texture.get_height();
|
||||
@@ -1437,8 +1412,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType
|
||||
if (item->is_action_with_text())
|
||||
{
|
||||
float scaled_text_size = item->get_extra_size_ratio() * icons_size_x;
|
||||
if (draw_icons)
|
||||
item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_size, top - icons_size_y, top);
|
||||
item->render_text(left + icons_size_x, left + icons_size_x + scaled_text_size, top - icons_size_y, top);
|
||||
left += scaled_text_size;
|
||||
}
|
||||
left += icon_stride;
|
||||
@@ -1446,7 +1420,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent, GLToolbarItem::EType
|
||||
}
|
||||
}
|
||||
|
||||
void GLToolbar::render_vertical(const GLCanvas3D& parent, bool draw_icons)
|
||||
void GLToolbar::render_vertical(const GLCanvas3D& parent)
|
||||
{
|
||||
const Size cnv_size = parent.get_canvas_size();
|
||||
const float cnv_w = (float)cnv_size.get_width();
|
||||
@@ -1475,8 +1449,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent, bool draw_icons)
|
||||
const float right = left + width;
|
||||
const float bottom = top - height;
|
||||
|
||||
if (draw_icons)
|
||||
render_background(left, top, right, bottom, border_w, border_h);
|
||||
render_background(left, top, right, bottom, border_w, border_h);
|
||||
|
||||
left += border_w;
|
||||
top -= border_h;
|
||||
@@ -1489,11 +1462,6 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent, bool draw_icons)
|
||||
if (item->is_separator())
|
||||
top -= separator_stride;
|
||||
else {
|
||||
if (!draw_icons) {
|
||||
item->render_window(left, left + icons_size_x, top - icons_size_y, top);
|
||||
top -= icon_stride;
|
||||
continue;
|
||||
}
|
||||
unsigned int tex_id;
|
||||
int tex_width, tex_height;
|
||||
if (item->is_action_with_text_image()) {
|
||||
|
||||
@@ -231,8 +231,6 @@ public:
|
||||
int generate_image_texture();
|
||||
|
||||
void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const;
|
||||
// The ImGui window a pressed item shows, given the icon's rectangle.
|
||||
void render_window(float left, float right, float bottom, float top) const;
|
||||
void render_image(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const;
|
||||
private:
|
||||
void set_visible(bool visible) { m_data.visible = visible; }
|
||||
@@ -412,10 +410,6 @@ public:
|
||||
bool update_items_state();
|
||||
|
||||
void render(const GLCanvas3D& parent,GLToolbarItem::EType type = GLToolbarItem::Action);
|
||||
// The ImGui windows of pressed items, built with the same layout as render().
|
||||
void render_item_windows(const GLCanvas3D& parent);
|
||||
// Hash of the state render() draws from: enabled, and each item's state and visibility.
|
||||
size_t get_state_hash() const;
|
||||
void render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item);
|
||||
|
||||
bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent);
|
||||
@@ -444,8 +438,8 @@ private:
|
||||
int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const;
|
||||
|
||||
void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const;
|
||||
void render_horizontal(const GLCanvas3D &parent, GLToolbarItem::EType type, bool draw_icons);
|
||||
void render_vertical(const GLCanvas3D& parent, bool draw_icons);
|
||||
void render_horizontal(const GLCanvas3D &parent, GLToolbarItem::EType type);
|
||||
void render_vertical(const GLCanvas3D& parent);
|
||||
|
||||
bool generate_icons_texture();
|
||||
|
||||
|
||||
+10
-100
@@ -2624,10 +2624,6 @@ void GUI_App::init_app_config()
|
||||
}
|
||||
#endif // _WIN32
|
||||
}
|
||||
// Speed Dial opens on a bare Space from any page by default. Seed the flag so Preferences and the
|
||||
// MainFrame shortcut read the same value; an existing config (true or false) is left untouched.
|
||||
if (app_config->get("enable_speed_dial").empty())
|
||||
app_config->set_bool("enable_speed_dial", true);
|
||||
set_logging_level(Slic3r::level_string_to_boost(app_config->get("log_severity_level")));
|
||||
|
||||
}
|
||||
@@ -4607,12 +4603,6 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI enter";
|
||||
m_is_recreating_gui = true;
|
||||
|
||||
// The palette injects its translated strings once, at creation; drop the cached dialog so the
|
||||
// next open rebuilds it in the current locale (and can't outlive the old mainframe).
|
||||
if (m_speed_dial_dialog) {
|
||||
m_speed_dial_dialog->Destroy();
|
||||
m_speed_dial_dialog = nullptr;
|
||||
}
|
||||
|
||||
mainframe->shutdown();
|
||||
ProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
|
||||
@@ -8181,22 +8171,6 @@ void GUI_App::save_mode(const /*ConfigOptionMode*/int mode)
|
||||
update_mode();
|
||||
}
|
||||
|
||||
void GUI_App::set_mode(ConfigOptionMode mode)
|
||||
{
|
||||
const bool was_developer = app_config->get_bool("developer_mode");
|
||||
if (was_developer)
|
||||
app_config->set_bool("developer_mode", false);
|
||||
save_mode(mode);
|
||||
if (was_developer)
|
||||
app_config->save();
|
||||
}
|
||||
|
||||
void GUI_App::enable_developer_mode()
|
||||
{
|
||||
app_config->set_bool("developer_mode", true);
|
||||
update_mode();
|
||||
}
|
||||
|
||||
// Update view mode according to selected menu
|
||||
void GUI_App::update_mode()
|
||||
{
|
||||
@@ -8345,65 +8319,6 @@ void GUI_App::open_plugins_dialog(size_t open_on_tab, const std::string& highlig
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::refresh_plugins()
|
||||
{
|
||||
// The metadata refresh blocks on disc discovery and a cloud round-trip, so run it on a worker
|
||||
// and report completion through the notification manager -- the speed dial needs no dialog.
|
||||
std::thread([]() {
|
||||
wxString error;
|
||||
try {
|
||||
refresh_plugin_metadata_blocking(/*fetch_cloud=*/true);
|
||||
} catch (const std::exception& ex) {
|
||||
error = from_u8(ex.what());
|
||||
} catch (...) {
|
||||
error = "Unknown error"; // plain literal: wx translation isn't safe off the UI thread
|
||||
}
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
wxTheApp->CallAfter([error]() {
|
||||
if (wxGetApp().is_closing())
|
||||
return;
|
||||
Plater* plater = wxGetApp().plater();
|
||||
if (plater == nullptr)
|
||||
return;
|
||||
if (error.IsEmpty())
|
||||
plater->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
||||
into_u8(_L("Plugins refreshed.")));
|
||||
else
|
||||
plater->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::ErrorNotificationLevel,
|
||||
into_u8(wxString::Format(_L("Failed to refresh plugins: %s"), error)));
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void GUI_App::install_local_plugin()
|
||||
{
|
||||
if (mainframe == nullptr)
|
||||
return;
|
||||
|
||||
wxFileDialog dialog(mainframe, _L("Select plugin package"), wxEmptyString, wxEmptyString, _L("Plugin files (*.py;*.whl)|*.py;*.whl"),
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
wxString message;
|
||||
const bool ok = install_local_plugin_package(boost::filesystem::path(dialog.GetPath().ToUTF8().data()), mainframe, message);
|
||||
if (message.IsEmpty())
|
||||
return; // user cancelled the overwrite prompt
|
||||
|
||||
Plater* plater = this->plater();
|
||||
if (plater == nullptr)
|
||||
return;
|
||||
plater->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
ok ? NotificationManager::NotificationLevel::RegularNotificationLevel : NotificationManager::NotificationLevel::ErrorNotificationLevel,
|
||||
into_u8(message));
|
||||
}
|
||||
|
||||
void GUI_App::open_terminal_dialog()
|
||||
{
|
||||
// Reached from the plugins dialog's webview ("open_terminal" command), i.e. from
|
||||
@@ -8467,14 +8382,12 @@ void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::strin
|
||||
|
||||
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
// Render settings the canvas reads every frame; a change needs one redraw to show.
|
||||
static constexpr const char* opengl_render_setting_keys[] = {
|
||||
SETTING_OPENGL_FXAA_ENABLED, SETTING_OPENGL_FPS_CAP, SETTING_OPENGL_SHOW_FPS_OVERLAY, SETTING_OPENGL_SCENE_CACHE,
|
||||
SETTING_OPENGL_SKIP_IDENTICAL_FRAMES
|
||||
};
|
||||
std::vector<std::string> previous_opengl_render_settings;
|
||||
for (const char* key : opengl_render_setting_keys)
|
||||
previous_opengl_render_settings.emplace_back(app_config->get(key));
|
||||
static constexpr const char* opengl_fxaa_setting_key = "opengl_fxaa_enabled";
|
||||
static constexpr const char* opengl_fps_cap_setting_key = "opengl_fps_cap";
|
||||
static constexpr const char* opengl_show_fps_overlay_setting_key = "opengl_show_fps_overlay";
|
||||
const std::string previous_opengl_fxaa = app_config->get(opengl_fxaa_setting_key);
|
||||
const std::string previous_opengl_fps_cap = app_config->get(opengl_fps_cap_setting_key);
|
||||
const std::string previous_opengl_show_fps_overlay = app_config->get(opengl_show_fps_overlay_setting_key);
|
||||
|
||||
bool need_recreate_gui = false;
|
||||
std::string pending_language;
|
||||
@@ -8514,10 +8427,10 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
|
||||
}
|
||||
}
|
||||
|
||||
bool opengl_render_settings_changed = false;
|
||||
for (size_t i = 0; i < previous_opengl_render_settings.size(); ++i)
|
||||
opengl_render_settings_changed |= app_config->get(opengl_render_setting_keys[i]) != previous_opengl_render_settings[i];
|
||||
if (opengl_render_settings_changed && !need_recreate_gui && this->plater_ != nullptr) {
|
||||
const bool opengl_fxaa_changed = app_config->get(opengl_fxaa_setting_key) != previous_opengl_fxaa;
|
||||
const bool opengl_fps_cap_changed = app_config->get(opengl_fps_cap_setting_key) != previous_opengl_fps_cap;
|
||||
const bool opengl_show_fps_overlay_changed = app_config->get(opengl_show_fps_overlay_setting_key) != previous_opengl_show_fps_overlay;
|
||||
if ((opengl_fxaa_changed || opengl_fps_cap_changed || opengl_show_fps_overlay_changed) && !need_recreate_gui && this->plater_ != nullptr) {
|
||||
this->plater_->set_current_canvas_as_dirty();
|
||||
this->plater_->get_current_canvas3D()->force_set_focus();
|
||||
}
|
||||
@@ -8531,9 +8444,6 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
|
||||
this->plater_->get_current_canvas3D()->force_set_focus();
|
||||
return;
|
||||
}
|
||||
// Built-in Speed Dial command titles are copied from the catalog at init and don't follow a
|
||||
// live locale switch; rebuild them in the new language before the GUI (and palette) rebuilds.
|
||||
m_action_registry.relocalize_builtins();
|
||||
}
|
||||
|
||||
if (need_recreate_gui)
|
||||
|
||||
@@ -599,11 +599,6 @@ public:
|
||||
std::string get_saved_mode_str();
|
||||
std::string get_mode_str();
|
||||
void save_mode(const /*ConfigOptionMode*/int mode) ;
|
||||
// Switch to `mode` from the Speed Dial: a developer-mode override hides the saved mode
|
||||
// (get_mode returns comDevelop), so clear it first and persist the choice.
|
||||
void set_mode(ConfigOptionMode mode);
|
||||
// Turn the developer-mode override on and refresh the UI (used before jumping to a Developer setting).
|
||||
void enable_developer_mode();
|
||||
void update_mode();
|
||||
void update_internal_development();
|
||||
void show_ip_address_enter_dialog(wxString title = wxEmptyString);
|
||||
@@ -642,9 +637,6 @@ public:
|
||||
void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
void open_presetbundledialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
void open_plugins_dialog(size_t open_on_tab = 0, const std::string& highlight_option = std::string());
|
||||
// Dialog-free plugin actions used by the speed dial: they never require the Plugins dialog to be open.
|
||||
void refresh_plugins();
|
||||
void install_local_plugin();
|
||||
void open_terminal_dialog();
|
||||
void open_speed_dial();
|
||||
ActionRegistry& action_registry() { return m_action_registry; }
|
||||
|
||||
@@ -578,131 +578,113 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
||||
return sub_menu;
|
||||
}
|
||||
|
||||
// Orca: handy models shipped under <resources>/handy_models. Defining everything in one table keeps
|
||||
// the menu label, the files to load and the per-model behavior in a single place. Labels are wrapped
|
||||
// in L() so they are picked up for translation. Shared with the command palette.
|
||||
const std::vector<MenuFactory::HandyModel>& MenuFactory::handy_models()
|
||||
{
|
||||
static const std::vector<HandyModel> models = {
|
||||
{"orca_cube", L("Orca Cube"), {"OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{"orcasliced_combo", L("OrcaSliced Combo"), {"OrcaSliced.3mf", "OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{"orca_badge", L("Orca Badge"), {"OrcaBadge.3mf"}},
|
||||
{"orca_tolerance_test", L("Orca Tolerance Test"), {"OrcaToleranceTest.drc"}},
|
||||
{"3dbenchy", L("3DBenchy"), {"3DBenchy.drc"}},
|
||||
{"cali_cat", L("Cali Cat"), {"calicat.drc"}},
|
||||
{"autodesk_fdm_test", L("Autodesk FDM Test"), {"ksr_fdmtest_v4.drc"}},
|
||||
{"voron_cube", L("Voron Cube"), {"Voron_Design_Cube_v7.drc"}},
|
||||
{"stanford_bunny", L("Stanford Bunny"), {"Stanford_Bunny.drc"}},
|
||||
{"orca_string_hell", L("Orca String Hell"), {"Orca_stringhell.drc"}, false, true},
|
||||
};
|
||||
return models;
|
||||
}
|
||||
|
||||
void MenuFactory::load_handy_model(std::size_t index)
|
||||
{
|
||||
const std::vector<HandyModel>& models = handy_models();
|
||||
if (index >= models.size())
|
||||
return;
|
||||
const HandyModel& model = models[index];
|
||||
|
||||
std::vector<boost::filesystem::path> input_files;
|
||||
input_files.reserve(model.file_names.size());
|
||||
for (const auto& file_name : model.file_names)
|
||||
input_files.push_back((boost::filesystem::path(Slic3r::resources_dir()) / "handy_models" / file_name));
|
||||
|
||||
Plater* pl = plater();
|
||||
if (!pl)
|
||||
return;
|
||||
pl->load_files(input_files, LoadStrategy::LoadModel);
|
||||
if (model.arrange_after_import) {
|
||||
pl->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
pl->arrange();
|
||||
}
|
||||
|
||||
// Suggest to change settings for stringhell
|
||||
// This serves as mini tutorial for new users
|
||||
if (model.is_stringhell) {
|
||||
wxGetApp().CallAfter([=] {
|
||||
DynamicPrintConfig* m_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
|
||||
bool is_only_one_wall_top = m_config->opt_bool("only_one_wall_top");
|
||||
auto min_width_top_surface = m_config->option<ConfigOptionFloatOrPercent>("min_width_top_surface")->value;
|
||||
if (is_only_one_wall_top && min_width_top_surface > 0) {
|
||||
wxString msg_text = _L("This model features text embossment on the top surface. For optimal results, it is "
|
||||
"advisable to set the 'One Wall Threshold (min_width_top_surface)' "
|
||||
"to 0 for the 'Only One Wall on Top Surfaces' to work best.\n"
|
||||
"Yes - Change these settings automatically\n"
|
||||
"No - Do not change these settings for me");
|
||||
|
||||
MessageDialog dialog(wxGetApp().plater(), msg_text, _L("Suggestion"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dialog.ShowModal() == wxID_YES) {
|
||||
m_config->set_key_value("min_width_top_surface", new ConfigOptionFloatOrPercent(0, false));
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty();
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config();
|
||||
}
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: add submenu for adding handy models
|
||||
wxMenu* MenuFactory::append_submenu_add_handy_model(wxMenu* menu, ModelVolumeType type) {
|
||||
auto sub_menu = new wxMenu;
|
||||
|
||||
const std::vector<HandyModel>& models = handy_models();
|
||||
for (std::size_t i = 0; i < models.size(); ++i) {
|
||||
append_menu_item(sub_menu, wxID_ANY, _(models[i].label), "",
|
||||
[i](wxCommandEvent&) { MenuFactory::load_handy_model(i); }, "", menu);
|
||||
// Orca: handy models shipped under <resources>/handy_models. Defining everything in one table
|
||||
// keeps the menu label, the files to load and the per-model behavior in a single place and
|
||||
// avoids repeating the label strings (and the value-vs-pointer comparison pitfalls that come
|
||||
// with that). Labels are wrapped in L() so they are picked up for translation.
|
||||
struct HandyModel
|
||||
{
|
||||
const char* label;
|
||||
std::vector<std::string> file_names;
|
||||
bool arrange_after_import = false;
|
||||
bool is_stringhell = false;
|
||||
};
|
||||
static const std::vector<HandyModel> handy_models = {
|
||||
{L("Orca Cube"), {"OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{L("OrcaSliced Combo"), {"OrcaSliced.3mf", "OrcaCube_v2.drc", "OrcaPlug_v2.drc"}, true},
|
||||
{L("Orca Badge"), {"OrcaBadge.3mf"}},
|
||||
{L("Orca Tolerance Test"), {"OrcaToleranceTest.drc"}},
|
||||
{L("3DBenchy"), {"3DBenchy.drc"}},
|
||||
{L("Cali Cat"), {"calicat.drc"}},
|
||||
{L("Autodesk FDM Test"), {"ksr_fdmtest_v4.drc"}},
|
||||
{L("Voron Cube"), {"Voron_Design_Cube_v7.drc"}},
|
||||
{L("Stanford Bunny"), {"Stanford_Bunny.drc"}},
|
||||
{L("Orca String Hell"), {"Orca_stringhell.drc"}, false, true},
|
||||
};
|
||||
|
||||
for (const auto& model : handy_models) {
|
||||
append_menu_item(
|
||||
sub_menu, wxID_ANY, _(model.label), "",
|
||||
[&model](wxCommandEvent&) {
|
||||
std::vector<boost::filesystem::path> input_files;
|
||||
input_files.reserve(model.file_names.size());
|
||||
for (const auto& file_name : model.file_names)
|
||||
input_files.push_back((boost::filesystem::path(Slic3r::resources_dir()) / "handy_models" / file_name));
|
||||
|
||||
plater()->load_files(input_files, LoadStrategy::LoadModel);
|
||||
if (model.arrange_after_import) {
|
||||
plater()->set_prepare_state(Job::PREPARE_STATE_MENU);
|
||||
plater()->arrange();
|
||||
}
|
||||
|
||||
// Suggest to change settings for stringhell
|
||||
// This serves as mini tutorial for new users
|
||||
if (model.is_stringhell) {
|
||||
wxGetApp().CallAfter([=] {
|
||||
DynamicPrintConfig* m_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
|
||||
bool is_only_one_wall_top = m_config->opt_bool("only_one_wall_top");
|
||||
auto min_width_top_surface = m_config->option<ConfigOptionFloatOrPercent>("min_width_top_surface")->value;
|
||||
if (is_only_one_wall_top && min_width_top_surface > 0) {
|
||||
wxString msg_text = _L("This model features text embossment on the top surface. For optimal results, it is "
|
||||
"advisable to set the 'One Wall Threshold (min_width_top_surface)' "
|
||||
"to 0 for the 'Only One Wall on Top Surfaces' to work best.\n"
|
||||
"Yes - Change these settings automatically\n"
|
||||
"No - Do not change these settings for me");
|
||||
|
||||
MessageDialog dialog(wxGetApp().plater(), msg_text, _L("Suggestion"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dialog.ShowModal() == wxID_YES) {
|
||||
m_config->set_key_value("min_width_top_surface", new ConfigOptionFloatOrPercent(0, false));
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty();
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config();
|
||||
}
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
"", menu);
|
||||
}
|
||||
|
||||
|
||||
return sub_menu;
|
||||
}
|
||||
|
||||
// Create a Text/SVG volume through the matching gizmo. `type == INVALID` means "create a new object".
|
||||
// Shared by the add menu and the command palette.
|
||||
static void add_volume_with_gizmo(GLGizmosManager::EType gizmo_type, ModelVolumeType type)
|
||||
{
|
||||
Plater* pl = plater();
|
||||
if (!pl)
|
||||
return;
|
||||
const GLCanvas3D* canvas = pl->canvas3D();
|
||||
if (!canvas)
|
||||
return;
|
||||
GLGizmoBase* gizmo_base = canvas->get_gizmos_manager().get_gizmo(gizmo_type);
|
||||
if (!gizmo_base)
|
||||
return;
|
||||
|
||||
ModelVolumeType volume_type = type;
|
||||
// no selected object means create new object
|
||||
if (volume_type == ModelVolumeType::INVALID)
|
||||
volume_type = ModelVolumeType::MODEL_PART;
|
||||
|
||||
auto screen_position = canvas->get_popup_menu_position();
|
||||
if (gizmo_type == GLGizmosManager::Emboss) {
|
||||
auto* emboss = dynamic_cast<GLGizmoEmboss*>(gizmo_base);
|
||||
if (emboss == nullptr)
|
||||
return;
|
||||
if (screen_position.has_value())
|
||||
emboss->create_volume(volume_type, *screen_position);
|
||||
else
|
||||
emboss->create_volume(volume_type);
|
||||
} else if (gizmo_type == GLGizmosManager::Svg) {
|
||||
auto* svg = dynamic_cast<GLGizmoSVG*>(gizmo_base);
|
||||
if (svg == nullptr)
|
||||
return;
|
||||
if (screen_position.has_value())
|
||||
svg->create_volume(volume_type, *screen_position);
|
||||
else
|
||||
svg->create_volume(volume_type);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuFactory::add_text_volume(ModelVolumeType type) { add_volume_with_gizmo(GLGizmosManager::Emboss, type); }
|
||||
void MenuFactory::add_svg_volume(ModelVolumeType type) { add_volume_with_gizmo(GLGizmosManager::Svg, type); }
|
||||
|
||||
static void append_menu_itemm_add_(const wxString& name, GLGizmosManager::EType gizmo_type, wxMenu *menu, ModelVolumeType type, bool is_submenu_item) {
|
||||
auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) { add_volume_with_gizmo(gizmo_type, type); };
|
||||
auto add_ = [type, gizmo_type](const wxCommandEvent & /*unnamed*/) {
|
||||
const GLCanvas3D *canvas = plater()->canvas3D();
|
||||
const GLGizmosManager &mng = canvas->get_gizmos_manager();
|
||||
GLGizmoBase *gizmo_base = mng.get_gizmo(gizmo_type);
|
||||
|
||||
ModelVolumeType volume_type = type;
|
||||
// no selected object means create new object
|
||||
if (volume_type == ModelVolumeType::INVALID)
|
||||
volume_type = ModelVolumeType::MODEL_PART;
|
||||
|
||||
auto screen_position = canvas->get_popup_menu_position();
|
||||
if (gizmo_type == GLGizmosManager::Emboss) {
|
||||
auto emboss = dynamic_cast<GLGizmoEmboss *>(gizmo_base);
|
||||
assert(emboss != nullptr);
|
||||
if (emboss == nullptr) return;
|
||||
if (screen_position.has_value()) {
|
||||
emboss->create_volume(volume_type, *screen_position);
|
||||
} else {
|
||||
emboss->create_volume(volume_type);
|
||||
}
|
||||
} else if (gizmo_type == GLGizmosManager::Svg) {
|
||||
auto svg = dynamic_cast<GLGizmoSVG *>(gizmo_base);
|
||||
assert(svg != nullptr);
|
||||
if (svg == nullptr) return;
|
||||
if (screen_position.has_value()) {
|
||||
svg->create_volume(volume_type, *screen_position);
|
||||
} else {
|
||||
svg->create_volume(volume_type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER ||
|
||||
type == ModelVolumeType::INVALID // cannot use gizmo without selected object
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
|
||||
@@ -52,23 +51,6 @@ public:
|
||||
static std::vector<wxBitmap> get_text_volume_bitmaps();
|
||||
static std::vector<wxBitmap> get_svg_volume_bitmaps();
|
||||
|
||||
// Orca: handy models shipped under <resources>/handy_models. The menu and the command palette
|
||||
// share this table so the model list and its per-model behavior live in one place.
|
||||
struct HandyModel
|
||||
{
|
||||
const char* key;
|
||||
const char* label;
|
||||
std::vector<std::string> file_names;
|
||||
bool arrange_after_import = false;
|
||||
bool is_stringhell = false;
|
||||
};
|
||||
static const std::vector<HandyModel>& handy_models();
|
||||
static void load_handy_model(std::size_t index);
|
||||
|
||||
// Add a Text/SVG volume through the Emboss/SVG gizmo. Shared by the add menu and the palette.
|
||||
static void add_text_volume(ModelVolumeType type);
|
||||
static void add_svg_volume(ModelVolumeType type);
|
||||
|
||||
MenuFactory();
|
||||
~MenuFactory() = default;
|
||||
|
||||
|
||||
@@ -478,8 +478,6 @@ int get_dpi_for_window(const wxWindow *window);
|
||||
#ifdef __WXOSX__
|
||||
void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||
void staticbox_remove_margin(wxStaticBox* sb);
|
||||
// Clip a top-level window (and its webview) to a rounded rect with a native layer.
|
||||
void set_window_corner_radius(wxWindow* win, int radius);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK__
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/sysctl.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <wx/osx/cocoa/dataview.h>
|
||||
#import "GUI_Utils.hpp"
|
||||
|
||||
@@ -22,22 +21,6 @@ void staticbox_remove_margin(wxStaticBox* sb) {
|
||||
[nativeBox setBorderWidth:0];
|
||||
}
|
||||
|
||||
// wxOSX SetShape only clears the window background; it cannot clip to a region. Clipping the
|
||||
// window's view layer to a rounded rect is what actually rounds the opaque webview inside.
|
||||
void set_window_corner_radius(wxWindow* win, int radius) {
|
||||
if (!win)
|
||||
return;
|
||||
NSView* view = (NSView*)win->GetHandle();
|
||||
if (!view)
|
||||
return;
|
||||
NSWindow* window = [view window];
|
||||
[window setOpaque:NO];
|
||||
[window setBackgroundColor:[NSColor clearColor]];
|
||||
[view setWantsLayer:YES];
|
||||
[[view layer] setCornerRadius:radius];
|
||||
[[view layer] setMasksToBounds:YES];
|
||||
}
|
||||
|
||||
bool is_debugger_present()
|
||||
// Returns true if the current process is being debugged (either
|
||||
// running under the debugger or has a debugger attached post facto).
|
||||
|
||||
@@ -69,7 +69,6 @@ bool GLGizmoAssembly::on_is_activable() const
|
||||
|
||||
void GLGizmoAssembly::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
render_dimensioning_if_scene_reused();
|
||||
static std::optional<Measure::SurfaceFeature> last_feature;
|
||||
static EMode last_mode = EMode::FeatureSelection;
|
||||
static SelectedFeatures last_selected_features;
|
||||
|
||||
@@ -179,8 +179,6 @@ public:
|
||||
bool is_selectable() const { return on_is_selectable(); }
|
||||
CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
|
||||
virtual bool wants_enter_leave_snapshots() const { return false; }
|
||||
// True when what on_render() draws would change if the cursor moved.
|
||||
virtual bool render_follows_cursor() const { return false; }
|
||||
virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
|
||||
virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
|
||||
virtual std::string get_action_snapshot_name() const;
|
||||
|
||||
@@ -167,8 +167,6 @@ protected:
|
||||
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
// The preview ear is drawn only while the cursor is on the model.
|
||||
bool render_follows_cursor() const override { return render_hover_point.has_value(); }
|
||||
//bool on_is_selectable() const override;
|
||||
virtual CommonGizmosDataID on_get_requirements() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
|
||||
@@ -563,30 +563,8 @@ void GLGizmoMeasure::init_plane_glmodel(GripperType gripper_type, const Measure:
|
||||
}
|
||||
}
|
||||
|
||||
bool GLGizmoMeasure::render_follows_cursor() const
|
||||
{
|
||||
// The two raycasts on_render() starts with, without their side effects.
|
||||
if (m_editing_distance)
|
||||
return false;
|
||||
|
||||
const Vec2d mouse_position = m_parent.get_local_mouse_position();
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
Vec3f hit = Vec3f::Zero();
|
||||
Vec3f normal = Vec3f::Zero();
|
||||
for (const auto& item : m_gripper_id_raycast_map) {
|
||||
if (item.second->get_id() > 0 && item.second->get_raycaster()->closest_hit(mouse_position, item.second->get_transform(), camera, hit, normal))
|
||||
return true;
|
||||
}
|
||||
for (const auto& item : m_mesh_raycaster_map) {
|
||||
if (item.second->get_raycaster()->unproject_on_mesh(mouse_position, item.second->get_transform(), camera, hit, normal))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoMeasure::on_render()
|
||||
{
|
||||
m_rendered_this_frame = true;
|
||||
#if ENABLE_MEASURE_GIZMO_DEBUG
|
||||
render_debug_dialog();
|
||||
#endif // ENABLE_MEASURE_GIZMO_DEBUG
|
||||
@@ -723,36 +701,35 @@ void GLGizmoMeasure::on_render()
|
||||
reset_gripper_pick(GripperType::UNDEFINE, true);
|
||||
|
||||
m_curr_feature = curr_feature;
|
||||
// The selected features are drawn below whether or not one is hovered.
|
||||
if (m_curr_feature.has_value()) {
|
||||
m_curr_feature->volume = m_last_hit_volume;
|
||||
m_curr_feature->world_tran = m_mesh_raycaster_map[m_last_hit_volume]->get_transform();
|
||||
if (!m_curr_feature.has_value())
|
||||
return;
|
||||
m_curr_feature->volume = m_last_hit_volume;
|
||||
m_curr_feature->world_tran = m_mesh_raycaster_map[m_last_hit_volume]->get_transform();
|
||||
|
||||
switch (m_curr_feature->get_type()) {
|
||||
default: { assert(false); break; }
|
||||
case Measure::SurfaceFeatureType::Point:
|
||||
{
|
||||
m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared<PickRaycaster>(POINT_ID, *m_sphere.mesh_raycaster);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Edge:
|
||||
{
|
||||
m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared<PickRaycaster>(EDGE_ID, *m_cylinder.mesh_raycaster);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Circle: {
|
||||
m_curr_circle.last_circle_feature = nullptr;
|
||||
m_curr_circle.inv_zoom = 0;
|
||||
init_circle_glmodel(GripperType::CIRCLE, *m_curr_feature, m_curr_circle,inv_zoom);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Plane: {
|
||||
update_world_plane_features(m_curr_measuring.get(), *m_curr_feature);
|
||||
m_curr_plane.plane_idx = -1;
|
||||
init_plane_glmodel(GripperType::PLANE, *m_curr_feature, m_curr_plane);
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (m_curr_feature->get_type()) {
|
||||
default: { assert(false); break; }
|
||||
case Measure::SurfaceFeatureType::Point:
|
||||
{
|
||||
m_gripper_id_raycast_map[GripperType::POINT] = std::make_shared<PickRaycaster>(POINT_ID, *m_sphere.mesh_raycaster);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Edge:
|
||||
{
|
||||
m_gripper_id_raycast_map[GripperType::EDGE] = std::make_shared<PickRaycaster>(EDGE_ID, *m_cylinder.mesh_raycaster);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Circle: {
|
||||
m_curr_circle.last_circle_feature = nullptr;
|
||||
m_curr_circle.inv_zoom = 0;
|
||||
init_circle_glmodel(GripperType::CIRCLE, *m_curr_feature, m_curr_circle,inv_zoom);
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Plane: {
|
||||
update_world_plane_features(m_curr_measuring.get(), *m_curr_feature);
|
||||
m_curr_plane.plane_idx = -1;
|
||||
init_plane_glmodel(GripperType::PLANE, *m_curr_feature, m_curr_plane);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2159,18 +2136,8 @@ void GLGizmoMeasure::init_render_input_window()
|
||||
m_same_model_object = is_two_volume_in_same_model_object();
|
||||
}
|
||||
|
||||
void GLGizmoMeasure::render_dimensioning_if_scene_reused()
|
||||
{
|
||||
// The labels are ImGui and live one frame; the lines drawn with them land under the cached
|
||||
// scene, which is drawn after the overlay is built.
|
||||
if (!m_rendered_this_frame)
|
||||
render_dimensioning();
|
||||
m_rendered_this_frame = false;
|
||||
}
|
||||
|
||||
void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
render_dimensioning_if_scene_reused();
|
||||
static std::optional<Measure::SurfaceFeature> last_feature;
|
||||
static EMode last_mode = EMode::FeatureSelection;
|
||||
static SelectedFeatures last_selected_features;
|
||||
|
||||
@@ -228,9 +228,6 @@ protected:
|
||||
void restore_scene_raycasters_state();
|
||||
|
||||
void render_dimensioning();
|
||||
// Builds the dimension labels on a frame that reused the cached scene and so skipped on_render().
|
||||
void render_dimensioning_if_scene_reused();
|
||||
bool m_rendered_this_frame{ false };
|
||||
|
||||
#if ENABLE_MEASURE_GIZMO_DEBUG
|
||||
void render_debug_dialog();
|
||||
@@ -258,9 +255,6 @@ protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
// The hover is resolved inside on_render(), against the mesh and against the grippers of the
|
||||
// selected features, which sit off the mesh.
|
||||
bool render_follows_cursor() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
|
||||
|
||||
@@ -131,12 +131,15 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Transform3d> GLGizmoPainterBase::mesh_trafo_matrices() const
|
||||
void GLGizmoPainterBase::render_cursor()
|
||||
{
|
||||
// First check that the mouse pointer is on an object.
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (mv->is_model_part())
|
||||
@@ -151,26 +154,6 @@ std::vector<Transform3d> GLGizmoPainterBase::mesh_trafo_matrices() const
|
||||
}
|
||||
}
|
||||
}
|
||||
return trafo_matrices;
|
||||
}
|
||||
|
||||
bool GLGizmoPainterBase::render_follows_cursor() const
|
||||
{
|
||||
// The brush is drawn only where the cursor meets the model. update_raycast_cache() keeps the
|
||||
// answer for render_cursor().
|
||||
if (m_c->selection_info() == nullptr || m_c->selection_info()->model_object() == nullptr)
|
||||
return false;
|
||||
update_raycast_cache(m_parent.get_local_mouse_position(), wxGetApp().plater()->get_camera(), mesh_trafo_matrices());
|
||||
return m_rr.mesh_id != -1;
|
||||
}
|
||||
|
||||
void GLGizmoPainterBase::render_cursor()
|
||||
{
|
||||
// First check that the mouse pointer is on an object.
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
const std::vector<Transform3d> trafo_matrices = mesh_trafo_matrices();
|
||||
// Raycast and return if there's no hit.
|
||||
update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices);
|
||||
if (m_rr.mesh_id == -1)
|
||||
|
||||
@@ -318,8 +318,6 @@ private:
|
||||
std::vector<ProjectedHeightRange> get_projected_height_range(const Vec2d& mouse_position, double resolution, const std::vector<const ModelVolume*>& part_volumes, const std::vector<Transform3d>& trafo_matrices) const;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
// World transforms of the model parts, in mo->volumes order.
|
||||
std::vector<Transform3d> mesh_trafo_matrices() const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
const Camera& camera,
|
||||
const std::vector<Transform3d>& trafo_matrices) const;
|
||||
@@ -372,7 +370,6 @@ protected:
|
||||
virtual PainterGizmoType get_painter_type() const = 0;
|
||||
|
||||
bool on_is_activable() const override;
|
||||
bool render_follows_cursor() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive& ar) const override {}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#endif
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#define L(s) Slic3r::GUI::I18N::translate((s)).c_str()
|
||||
#define UL(s) Slic3r::GUI::I18N::translate_utf8((s)).c_str()
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -373,15 +374,15 @@ void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
|
||||
};
|
||||
|
||||
if (m_sp.is_pocket && has_sel) {
|
||||
if (btn(_u8L("Pocket (Cut)").c_str(), ok)) apply_pocket();
|
||||
if (btn(L("Pocket (Cut)"), ok)) apply_pocket();
|
||||
} else if (is_revolve) {
|
||||
if (btn(_u8L("Revolve").c_str(), ok)) apply_revolve();
|
||||
if (btn(L("Revolve"), ok)) apply_revolve();
|
||||
} else {
|
||||
if (btn(_u8L("Extrude").c_str(), ok)) apply_extrude();
|
||||
if (btn(L("Extrude"), ok)) apply_extrude();
|
||||
}
|
||||
|
||||
if (ImGui::Button(_u8L("Clear All").c_str(), {-1,0})) clear_all();
|
||||
if (ImGui::Button(_u8L("Close").c_str(), {-1,0})) m_parent.reset_all_gizmos();
|
||||
if (ImGui::Button(L("Clear All"), {-1,0})) clear_all();
|
||||
if (ImGui::Button(L("Close"), {-1,0})) m_parent.reset_all_gizmos();
|
||||
|
||||
GizmoImguiEnd();
|
||||
ImGuiWrapper::pop_toolbar_style();
|
||||
|
||||
@@ -410,10 +410,6 @@ void ObjectClipper::set_position_by_ratio(double pos, bool keep_normal, bool ver
|
||||
|
||||
void ObjectClipper::set_range_and_pos(const Vec3d& cpl_normal, double cpl_offset, double pos)
|
||||
{
|
||||
// Called every frame by GLGizmoCut3D::on_render(), usually with the plane already set.
|
||||
if (m_clp && *m_clp == ClippingPlane(cpl_normal, cpl_offset) && m_clp_ratio == pos)
|
||||
return;
|
||||
|
||||
m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset));
|
||||
m_clp_ratio = pos;
|
||||
get_pool()->get_canvas()->set_as_dirty();
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -236,10 +234,8 @@ bool GLGizmosManager::init()
|
||||
#ifdef SLIC3R_CAD
|
||||
// Registered last: Primitive and Sketch are the final entries before Undefined, so
|
||||
// omitting them leaves every preceding m_gizmos index (indexed by EType) untouched.
|
||||
if (wxGetApp().is_enable_cad_feature()) {
|
||||
m_gizmos.emplace_back(new GLGizmoPrimitive(m_parent, m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg", static_cast<unsigned int>(Primitive)));
|
||||
m_gizmos.emplace_back(new GLGizmoSketch(m_parent, m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg", static_cast<unsigned int>(Sketch)));
|
||||
}
|
||||
m_gizmos.emplace_back(new GLGizmoPrimitive(m_parent, m_is_dark ? "toolbar_modifier_cube_dark.svg" : "toolbar_modifier_cube.svg", static_cast<unsigned int>(Primitive)));
|
||||
m_gizmos.emplace_back(new GLGizmoSketch(m_parent, m_is_dark ? "toolbar_sketch_dark.svg" : "toolbar_sketch.svg", static_cast<unsigned int>(Sketch)));
|
||||
#endif
|
||||
//m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++));
|
||||
//m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++));
|
||||
@@ -633,7 +629,6 @@ void GLGizmosManager::render_painter_assemble_view() const
|
||||
m_assemble_view_data->model_objects_clipper()->render_cut();
|
||||
}
|
||||
|
||||
// The icon bar, drawn with GL.
|
||||
void GLGizmosManager::render_overlay()
|
||||
{
|
||||
if (!m_enabled)
|
||||
@@ -642,27 +637,7 @@ void GLGizmosManager::render_overlay()
|
||||
if (m_icons_texture_dirty)
|
||||
generate_icons_texture();
|
||||
|
||||
do_render_overlay(true);
|
||||
}
|
||||
|
||||
// The open gizmo's settings panel, ImGui.
|
||||
void GLGizmosManager::render_overlay_input_window()
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
do_render_overlay(false);
|
||||
}
|
||||
|
||||
size_t GLGizmosManager::get_overlay_state_hash() const
|
||||
{
|
||||
size_t hash = 0;
|
||||
boost::hash_combine(hash, m_enabled);
|
||||
boost::hash_combine(hash, (int)m_hover);
|
||||
boost::hash_combine(hash, (int)m_current);
|
||||
boost::hash_combine(hash, (int)m_highlight.first);
|
||||
boost::hash_combine(hash, m_highlight.second);
|
||||
return hash;
|
||||
do_render_overlay();
|
||||
}
|
||||
|
||||
std::string GLGizmosManager::get_tooltip() const
|
||||
@@ -1245,9 +1220,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t
|
||||
|
||||
//BBS: GUI refactor: GLToolbar&&Gizmo adjust
|
||||
//when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-top
|
||||
// draw_icons selects the icon bar (GL) or the open gizmo's input window (ImGui), placed by the same
|
||||
// layout walk.
|
||||
void GLGizmosManager::do_render_overlay(bool draw_icons) const
|
||||
void GLGizmosManager::do_render_overlay() const
|
||||
{
|
||||
const std::vector<size_t> selectable_idxs = get_selectable_idxs();
|
||||
if (selectable_idxs.empty())
|
||||
@@ -1286,8 +1259,7 @@ void GLGizmosManager::do_render_overlay(bool draw_icons) const
|
||||
}
|
||||
float top_y = 1.0f;
|
||||
|
||||
if (draw_icons)
|
||||
render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h);
|
||||
render_background(top_x, top_y, top_x + width, top_y - height, border_w, border_h);
|
||||
|
||||
top_x += border_w;
|
||||
top_y -= border_h;
|
||||
@@ -1324,8 +1296,7 @@ void GLGizmosManager::do_render_overlay(bool draw_icons) const
|
||||
const float v_top = v_offset + sprite_id * dv;
|
||||
const float v_bottom = v_top + dv - v_offset;
|
||||
|
||||
if (draw_icons)
|
||||
GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + icons_size_x, top_y - icons_size_y, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
if (idx == m_current
|
||||
// Orca: Show Svg dialog at the same place as emboss gizmo
|
||||
|| (m_current == Svg && idx == Emboss)) {
|
||||
@@ -1333,8 +1304,7 @@ void GLGizmosManager::do_render_overlay(bool draw_icons) const
|
||||
//render_input_window uses a different coordination(imgui)
|
||||
//1. no need to scale by camera zoom, set {0,0} at left-up corner for imgui
|
||||
//gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
|
||||
if (!draw_icons)
|
||||
m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h);
|
||||
m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h);
|
||||
|
||||
is_render_current = true;
|
||||
}
|
||||
@@ -1342,7 +1312,7 @@ void GLGizmosManager::do_render_overlay(bool draw_icons) const
|
||||
}
|
||||
|
||||
// BBS simplify gizmo is not a selected gizmo and need to render input window
|
||||
if (!draw_icons && !is_render_current && m_current != Undefined) {
|
||||
if (!is_render_current && m_current != Undefined) {
|
||||
m_gizmos[m_current]->render_input_window(0.5 * cnv_w + 0.5f * top_x * cnv_w, get_scaled_total_height(), cnv_h);
|
||||
}
|
||||
}
|
||||
@@ -1374,8 +1344,7 @@ GLGizmoBase* GLGizmosManager::get_current() const
|
||||
|
||||
GLGizmoBase* GLGizmosManager::get_gizmo(GLGizmosManager::EType type) const
|
||||
{
|
||||
// m_gizmos ends before the enum does when the CAD gizmos are not registered.
|
||||
return type < m_gizmos.size() ? m_gizmos[type].get() : nullptr;
|
||||
return ((type == Undefined) || m_gizmos.empty()) ? nullptr : m_gizmos[type].get();
|
||||
}
|
||||
|
||||
GLGizmosManager::EType GLGizmosManager::get_gizmo_from_name(const std::string& gizmo_name) const
|
||||
|
||||
@@ -296,9 +296,6 @@ public:
|
||||
void render_painter_assemble_view() const;
|
||||
|
||||
void render_overlay();
|
||||
void render_overlay_input_window();
|
||||
// Hash of the state render_overlay() draws from: enabled, hover, current and highlight.
|
||||
size_t get_overlay_state_hash() const;
|
||||
|
||||
void render_arrow(const GLCanvas3D& parent, EType highlighted_type) const;
|
||||
|
||||
@@ -332,7 +329,7 @@ private:
|
||||
|
||||
void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const;
|
||||
|
||||
void do_render_overlay(bool draw_icons) const;
|
||||
void do_render_overlay() const;
|
||||
|
||||
bool generate_icons_texture();
|
||||
|
||||
|
||||
@@ -590,39 +590,11 @@ void ImGuiWrapper::new_frame()
|
||||
// BBL: end copy & paste
|
||||
}
|
||||
|
||||
ImDrawData* ImGuiWrapper::end_frame()
|
||||
void ImGuiWrapper::render()
|
||||
{
|
||||
ImGui::Render();
|
||||
render_draw_data(ImGui::GetDrawData());
|
||||
m_new_frame_open = false;
|
||||
return ImGui::GetDrawData();
|
||||
}
|
||||
|
||||
void ImGuiWrapper::render(ImDrawData* draw_data)
|
||||
{
|
||||
render_draw_data(draw_data);
|
||||
}
|
||||
|
||||
ImGuiID ImGuiWrapper::draw_data_signature(const ImDrawData* draw_data)
|
||||
{
|
||||
ImGuiID hash = 0;
|
||||
if (draw_data == nullptr)
|
||||
return hash;
|
||||
|
||||
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
|
||||
const ImDrawList* list = draw_data->CmdLists[i];
|
||||
hash = ImHashData(list->VtxBuffer.Data, list->VtxBuffer.Size * sizeof(ImDrawVert), hash);
|
||||
hash = ImHashData(list->IdxBuffer.Data, list->IdxBuffer.Size * sizeof(ImDrawIdx), hash);
|
||||
// ImDrawCmd has padding, and a hovered ImageButton3() differs only in TextureId.
|
||||
for (const ImDrawCmd& cmd : list->CmdBuffer) {
|
||||
hash = ImHashData(&cmd.ClipRect, sizeof(cmd.ClipRect), hash);
|
||||
hash = ImHashData(&cmd.TextureId, sizeof(cmd.TextureId), hash);
|
||||
hash = ImHashData(&cmd.VtxOffset, sizeof(cmd.VtxOffset), hash);
|
||||
hash = ImHashData(&cmd.IdxOffset, sizeof(cmd.IdxOffset), hash);
|
||||
hash = ImHashData(&cmd.ElemCount, sizeof(cmd.ElemCount), hash);
|
||||
hash = ImHashData(&cmd.UserCallback, sizeof(cmd.UserCallback), hash);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::calc_text_size(std::string_view text,
|
||||
|
||||
@@ -98,11 +98,7 @@ public:
|
||||
const ImWchar *get_glyph_ranges() const { return m_glyph_ranges; } // language specific
|
||||
|
||||
void new_frame();
|
||||
// Ends the frame and returns its draw data without drawing it.
|
||||
ImDrawData* end_frame();
|
||||
void render(ImDrawData* draw_data);
|
||||
// Hash of every draw list's vertices, indices and commands.
|
||||
static ImGuiID draw_data_signature(const ImDrawData* draw_data);
|
||||
void render();
|
||||
|
||||
float scaled(float x) const { return x * m_font_size; }
|
||||
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
|
||||
|
||||
@@ -198,9 +198,6 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
|
||||
// Switch table page
|
||||
{ ctrl + L("Tab"), L("Switch table page")},
|
||||
// Open speed dial
|
||||
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open speed dial") },
|
||||
{ alt + "1..9,0", L("Run a Speed Dial favourite (while the Speed Dial is open)") },
|
||||
//DEL
|
||||
#ifdef __APPLE__
|
||||
{"fn+⌫", L("Delete Selected")},
|
||||
@@ -271,6 +268,8 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ "O", L("Zoom out") },
|
||||
{ "V", L("Toggle printable for object/part") },
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview") },
|
||||
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open actions speed dial") },
|
||||
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Plater"), "" }, plater_shortcuts });
|
||||
|
||||
|
||||
+93
-144
@@ -1,7 +1,6 @@
|
||||
#include "MainFrame.hpp"
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/textentry.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listbook.h>
|
||||
#include <wx/simplebook.h>
|
||||
@@ -47,9 +46,7 @@
|
||||
// BBS
|
||||
#include "PartPlate.hpp"
|
||||
#include "Preferences.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/ProgressDialog.hpp"
|
||||
#include "Widgets/StaticBox.hpp"
|
||||
#include "BindDialog.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
@@ -110,31 +107,6 @@ enum class ERescaleTarget
|
||||
SettingsDialog
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// Space opens the speed dial, but it is the activation key for buttons, checkboxes and other
|
||||
// controls. CHAR_HOOK runs before the focused child, so only take Space when the focused window has
|
||||
// no keyboard-activation meaning of its own. Canvases (GLCanvas3D) and panels are not controls and
|
||||
// fall through to "open"; the Notebook itself does too, so Space still opens the dial on any page.
|
||||
bool focus_keeps_space(wxWindow* focus)
|
||||
{
|
||||
if (!focus)
|
||||
return false;
|
||||
if (dynamic_cast<wxTextEntryBase*>(focus))
|
||||
return true; // typing a space into a text field
|
||||
if (dynamic_cast<wxWebView*>(focus))
|
||||
return true; // web content scrolls and hosts its own text fields
|
||||
if (dynamic_cast<::Button*>(focus))
|
||||
return true; // custom button: Space clicks it (it is a wxWindow, not a wxControl)
|
||||
if (dynamic_cast<StaticBox*>(focus))
|
||||
return true; // custom composites (ComboBox, SpinInput, ...) activate with Space and are wxWindow
|
||||
if (dynamic_cast<wxControl*>(focus) && !dynamic_cast<Notebook*>(focus))
|
||||
return true; // stock button/checkbox/choice/list/etc. keep Space
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef __WXGTK__
|
||||
// A thin transparent panel placed at a window edge to handle resize.
|
||||
// Works regardless of underlying content (GLCanvas3D, wxWebView, etc.)
|
||||
@@ -730,22 +702,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
}
|
||||
return;}
|
||||
#endif
|
||||
// Orca: open the speed dial from any page with a bare Space. Only when no modifier is held (so
|
||||
// editing shortcuts like Ctrl+Shift+Space in the canvas still reach it) and the focused window
|
||||
// doesn't use Space to activate itself (buttons, checkboxes, list/choice controls, text fields),
|
||||
// so a bare Space there still clicks/toggles instead of being hijacked. Gated by a preference
|
||||
// (default on) so users can hand Space back to the focused control entirely.
|
||||
if (wxGetApp().app_config->get_bool("enable_speed_dial") && !evt.CmdDown() && !evt.ShiftDown() &&
|
||||
!evt.AltDown() && evt.GetKeyCode() == WXK_SPACE) {
|
||||
if (focus_keeps_space(wxWindow::FindFocus())) {
|
||||
evt.Skip(); // let the focused control keep Space
|
||||
return;
|
||||
}
|
||||
// Defer out of the native key-event stack: open_speed_dial() may create a WebView and
|
||||
// run script, the same window work the codebase avoids doing on native callbacks.
|
||||
this->CallAfter([] { wxGetApp().open_speed_dial(); });
|
||||
return;
|
||||
}
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
|
||||
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
|
||||
m_plater->apply_background_progress();
|
||||
@@ -3388,11 +3344,6 @@ void MainFrame::init_menubar_as_editor()
|
||||
wxGetApp().open_preferences();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this, 1);
|
||||
parent_menu->AppendSeparator();
|
||||
append_menu_item(
|
||||
parent_menu, wxID_ANY, _L("Open speed dial...") + sep + "Space", "",
|
||||
[](wxCommandEvent &) { wxGetApp().open_speed_dial(); },
|
||||
"", nullptr, []() { return true; }, this);
|
||||
//parent_menu->Insert(1, preference_item);
|
||||
#endif
|
||||
// Help menu
|
||||
@@ -3417,13 +3368,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
auto top_menu = m_topbar->GetTopMenu();
|
||||
top_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
top_menu, wxID_ANY, _L("Open speed dial...") + "\t" + "Space", "",
|
||||
[](wxCommandEvent &) { wxGetApp().open_speed_dial(); },
|
||||
"", nullptr, []() { return true; }, this);
|
||||
top_menu->AppendSeparator();
|
||||
|
||||
append_menu_item(
|
||||
append_menu_item(
|
||||
top_menu, wxID_ANY, _L("Preset Bundle") + "\t", "",
|
||||
[this](wxCommandEvent &) {
|
||||
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
|
||||
@@ -3469,51 +3414,88 @@ void MainFrame::init_menubar_as_editor()
|
||||
|
||||
// Temperature
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Temperature"), _L("Temperature Calibration"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Temperature); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_temp_calib_dlg)
|
||||
m_temp_calib_dlg = new Temp_Calibration_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_temp_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Max Volumetric Speed
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Max flowrate"), _L("Max flowrate"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::MaxVolumetric); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_vol_test_dlg)
|
||||
m_vol_test_dlg = new MaxVolumetricSpeed_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_vol_test_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Pressure Advance
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Pressure advance"), _L("Pressure advance"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::PressureAdvance); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_pa_calib_dlg)
|
||||
m_pa_calib_dlg = new PA_Calibration_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_pa_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Flow rate (Wizard Dialog)
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Flow ratio"), _L("Flow Rate Calibration"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::FlowRatio); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_plater) return;
|
||||
if (!m_flow_rate_calib_dlg)
|
||||
m_flow_rate_calib_dlg = new FlowRateCalibrationDialog((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_flow_rate_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Retraction
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Retraction"), _L("Retraction"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Retraction); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_retraction_calib_dlg)
|
||||
m_retraction_calib_dlg = new Retraction_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_retraction_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Cornering
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("Cornering"), _L("Cornering calibration"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Cornering); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Cornering_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Input Shaping (with submenu)
|
||||
auto input_shaping_menu = new wxMenu();
|
||||
append_menu_item(
|
||||
input_shaping_menu, wxID_ANY, _L("Input Shaping Frequency"), _L("Input Shaping Frequency"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::InputShapingFreq); },
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Input_Shaping_Freq_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
},
|
||||
"", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
append_menu_item(
|
||||
input_shaping_menu, wxID_ANY, _L("Input Shaping Damping/zeta factor"), _L("Input Shaping Damping/zeta factor"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::InputShapingDamp); },
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Input_Shaping_Damp_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
},
|
||||
"", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
m_topbar->GetCalibMenu()->AppendSubMenu(input_shaping_menu, _L("Input Shaping"));
|
||||
|
||||
// VFA
|
||||
append_menu_item(m_topbar->GetCalibMenu(), wxID_ANY, _L("VFA"), _L("VFA"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::VFA); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_vfa_test_dlg)
|
||||
m_vfa_test_dlg = new VFA_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_vfa_test_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// help
|
||||
@@ -3524,10 +3506,6 @@ void MainFrame::init_menubar_as_editor()
|
||||
#else
|
||||
// On Mac, the Apple menu ignores non-standard custom items, so add Preset Bundle to the File menu
|
||||
fileMenu->AppendSeparator();
|
||||
append_menu_item(
|
||||
fileMenu, wxID_ANY, _L("Open speed dial...") + sep + "Space", "",
|
||||
[](wxCommandEvent&) { wxGetApp().open_speed_dial(); },
|
||||
"", nullptr, []() { return true; }, this);
|
||||
append_menu_item(
|
||||
fileMenu, wxID_ANY, _L("Preset Bundle"), "",
|
||||
[this](wxCommandEvent&) {
|
||||
@@ -3574,52 +3552,89 @@ void MainFrame::init_menubar_as_editor()
|
||||
|
||||
// Temperature
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Temperature"), _L("Temperature"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Temperature); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_temp_calib_dlg)
|
||||
m_temp_calib_dlg = new Temp_Calibration_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_temp_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Max Volumetric Speed
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Max flowrate"), _L("Max flowrate"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::MaxVolumetric); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_vol_test_dlg)
|
||||
m_vol_test_dlg = new MaxVolumetricSpeed_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_vol_test_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Pressure Advance
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Pressure advance"), _L("Pressure advance"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::PressureAdvance); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_pa_calib_dlg)
|
||||
m_pa_calib_dlg = new PA_Calibration_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_pa_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Flowrate (with submenu)
|
||||
// ORCA: Flow rate (Wizard Dialog)
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Flow ratio"), _L("Flow Rate Calibration"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::FlowRatio); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_plater) return;
|
||||
if (!m_flow_rate_calib_dlg)
|
||||
m_flow_rate_calib_dlg = new FlowRateCalibrationDialog((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_flow_rate_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Retraction
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Retraction"), _L("Retraction"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Retraction); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_retraction_calib_dlg)
|
||||
m_retraction_calib_dlg = new Retraction_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_retraction_calib_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Cornering
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Cornering"), _L("Cornering calibration"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::Cornering); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Cornering_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
|
||||
// Input Shaping (with submenu)
|
||||
auto input_shaping_menu = new wxMenu();
|
||||
append_menu_item(
|
||||
input_shaping_menu, wxID_ANY, _L("Input Shaping Frequency"), _L("Input Shaping Frequency"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::InputShapingFreq); },
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Input_Shaping_Freq_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
},
|
||||
"", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
append_menu_item(
|
||||
input_shaping_menu, wxID_ANY, _L("Input Shaping Damping/zeta factor"), _L("Input Shaping Damping/zeta factor"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::InputShapingDamp); },
|
||||
[this](wxCommandEvent&) {
|
||||
auto dlg = new Input_Shaping_Damp_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
},
|
||||
"", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
calib_menu->AppendSubMenu(input_shaping_menu, _L("Input Shaping"));
|
||||
|
||||
// VFA
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("VFA"), _L("VFA"),
|
||||
[this](wxCommandEvent&) { run_calibration(CalibKind::VFA); }, "", nullptr,
|
||||
[this](wxCommandEvent&) {
|
||||
if (!m_vfa_test_dlg)
|
||||
m_vfa_test_dlg = new VFA_Test_Dlg((wxWindow*)this, wxID_ANY, m_plater);
|
||||
m_vfa_test_dlg->ShowModal();
|
||||
}, "", nullptr,
|
||||
[this]() {return m_plater->is_view3D_shown();; }, this);
|
||||
// help
|
||||
append_menu_item(calib_menu, wxID_ANY, _L("Calibration Guide"), _L("Calibration Guide"),
|
||||
@@ -4393,72 +4408,6 @@ void MainFrame::technology_changed()
|
||||
m_menubar->SetMenuLabel(id, pt == ptSLA ? _omitL("Material Settings") : _L("Filament settings"));
|
||||
}
|
||||
|
||||
// Opens the calibration wizard for `calib_kind`. Single source of truth for the wizard lifecycle:
|
||||
// the Calibration menu handlers and the Speed Dial native commands both call it. Most wizards are
|
||||
// cached members reused across launches; cornering/input-shaping build a fresh transient dialog.
|
||||
// Call while the Prepare (3D) panel is shown.
|
||||
void MainFrame::run_calibration(CalibKind calib_kind)
|
||||
{
|
||||
switch (calib_kind) {
|
||||
case CalibKind::Temperature: {
|
||||
if (!m_temp_calib_dlg)
|
||||
m_temp_calib_dlg = new Temp_Calibration_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_temp_calib_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
case CalibKind::MaxVolumetric: {
|
||||
if (!m_vol_test_dlg)
|
||||
m_vol_test_dlg = new MaxVolumetricSpeed_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_vol_test_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
case CalibKind::PressureAdvance: {
|
||||
if (!m_pa_calib_dlg)
|
||||
m_pa_calib_dlg = new PA_Calibration_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_pa_calib_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
case CalibKind::FlowRatio: {
|
||||
if (!m_plater)
|
||||
break;
|
||||
if (!m_flow_rate_calib_dlg)
|
||||
m_flow_rate_calib_dlg = new FlowRateCalibrationDialog((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_flow_rate_calib_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
case CalibKind::Retraction: {
|
||||
if (!m_retraction_calib_dlg)
|
||||
m_retraction_calib_dlg = new Retraction_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_retraction_calib_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
case CalibKind::Cornering: {
|
||||
auto dlg = new Cornering_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
break;
|
||||
}
|
||||
case CalibKind::InputShapingFreq: {
|
||||
auto dlg = new Input_Shaping_Freq_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
break;
|
||||
}
|
||||
case CalibKind::InputShapingDamp: {
|
||||
auto dlg = new Input_Shaping_Damp_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
dlg->ShowModal();
|
||||
dlg->Destroy();
|
||||
break;
|
||||
}
|
||||
case CalibKind::VFA: {
|
||||
if (!m_vfa_test_dlg)
|
||||
m_vfa_test_dlg = new VFA_Test_Dlg((wxWindow*) this, wxID_ANY, m_plater);
|
||||
m_vfa_test_dlg->ShowModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Called after the Preferences dialog is closed and the program settings are saved.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user