12 Commits

Author SHA1 Message Date
thysson2701
d8b5f22fc9 fix(branding): KX-Logo in allen UI-Dialogen korrekt anzeigen
- Splash: SVG durch direktes PNG-Laden ersetzt (nanosvg rendert kein
  eingebettetes Raster-PNG)
- About-Dialog: Logo auf 125px skaliert statt unkontrolliert 192px
- TroubleshootDialog, UpdateDialogs, ReleaseNote, MsgDialog:
  OrcaSlicer.svg → OrcaSlicer_192px.png (bereits KX-Logo)
2026-06-30 10:05:07 +02:00
thysson2701
7ed0173b75 feat(updater): Versionsprüfung auf Gitea OrcaSlicer-KX Releases umleiten
Ersetzt die orcaslicer.com/latest URL durch die Gitea API:
  https://gitea.it-drui.de/api/v1/repos/viewit/OrcaSlicer-KX/releases/latest

Das Gitea-JSON hat dasselbe Format wie GitHub (tag_name, html_url,
prerelease) — der bestehende Parser in check_new_version_sf() funktioniert
ohne weitere Änderungen. Telemetrie-Query-Parameter und Upstream-Signaturen
werden nicht mehr angehängt.
2026-06-29 23:42:09 +02:00
thysson2701
be5cd1cba5 fix(branding): About-Dialog lädt KX-Logo als PNG statt SVG
nanosvg unterstützt keine eingebetteten Rasterbilder (data:image/png).
Direkt OrcaSlicer_192px.png laden — bereits durch das KX-Logo ersetzt.
2026-06-29 23:38:31 +02:00
thysson2701
842b871577 feat(branding): OrcaKX-Logo als App-Icon und About/Splash-Logo einsetzen
Ersetzt alle OrcaSlicer-Logos durch das Orca-KX Fork-Logo (Kobra + Orca).
Betrifft: PNG-Icons (32/64/128/154/192px), Windows .ico, macOS .icns,
About-Dialog SVGs (light + dark) und Splash-Screen SVGs.
2026-06-29 14:34:37 +02:00
thysson2701
072732a6ee docs: README auf 2.4.1-kx1 aktualisieren 2026-06-29 10:03:39 +02:00
thysson2701
bb3d1c7562 fix(ams): exclude empty placeholder slots from AMS filament mapping
Empty AMS slots (is_tray_info_ready()==false) were included in
tray_filaments with id=0. The distance-map then stored tray_id=0 for
those slots, causing the greedy mapper to assign real project filaments
to the placeholder entry instead of their actual physical slot. On a
6-slot ACS/ACE with Slot 1 empty this shifted all mapped filaments by
one position.

Fix: skip any tray that is not ready (no color/type) before calling
_parse_tray_info() so only loaded slots enter the mapping pool.

Also keep index-alignment in get_ams_cobox_infos(): empty AMS entries
(filament_id empty) now push placeholder strings instead of being
silently skipped. This prevents the Overwriting-mode tab from accessing
m_ams_combo_info.ams_filament_colors at a shifted index, which caused
an out-of-bounds read and subsequent crash when switching sync modes
while a placeholder slot was present.

Fixes KX-Bridge-Release issue #67 comment #480 (slot-index shift +
Overwriting crash).
2026-06-29 10:03:28 +02:00
thysson2701
87f6e62f26 fix(moonraker): re-resolve host on user→user preset switch for AMS sync
Two Moonraker user presets on different ports (:7125, :7126) share the
same printer agent type. switch_printer_agent() only called select_machine()
when the agent *type* changed — not when the host changed within the same
agent. A user→user preset switch therefore left the MachineObject pointing
at the first connected printer, causing AMS sync to always query the wrong
host.

Fix: when the agent type stays the same (and is not the BBL agent),
also call select_machine() so the host is re-resolved from the current
preset and set_selected_machine() reconnects to the correct Moonraker
instance.

Fixes: AMS Sync locked to first printer in multi-printer sessions (SirPeroples)
2026-06-29 10:03:25 +02:00
thysson2701
31c667443e fix(compat): add aliases for wall_filament → outer_wall_filament_id; fix verify_build.sh
Adds .aliases = { "wall_filament", "wall_filament_id", "outer_wall_filament",
"perimeter_extruder" } to outer_wall_filament_id and .aliases = { "inner_wall_filament" }
to inner_wall_filament_id in PrintConfig.cpp so that old .3mf files using these legacy
keys do not throw UnknownOptionException on load.

Also corrects verify_build.sh CHECKS to search for "tray_sub_brands" (the actual binary
string) instead of the stale "filament_sub_brands".
2026-06-29 10:03:22 +02:00
thysson2701
709caca9b8 chore: alle Upstream-Workflows deaktivieren (on: {}) 2026-06-29 10:03:18 +02:00
a0894702c6 docs: README auf 2.4.0-kx1 aktualisieren 2026-06-29 10:02:59 +02:00
thysson2701
bd652afd8c fix(preset): Legacy-Aliases durch korrekte _id-Keys in s_Preset_print_options ersetzen
wall_filament, sparse_infill_filament, solid_infill_filament waren
unregistrierte Legacy-Aliases die beim Start UnknownOptionException
in apply_only() verursachten. Durch die korrekten registrierten Keys
(outer/inner_wall_filament_id, sparse/internal_solid/top/bottom_surface
_filament_id) ersetzt. Ausserdem fehlende Upstream-2.4.0-Keys ergaenzt:
lightning_*, bridge_line_width, relative_bridge_angle,
support_parallel_printheads, parallel_printheads_*, flashforge_serial_number.
2026-06-29 10:02:56 +02:00
thysson2701
6cba576fe4 feat(v2.4): KX-Linie auf Upstream 2.4.0 konsolidieren (2.4.0-kx1)
Neue 2.4-stable-Linie auf frischem orca-upstream/release/v2.4 (2.4.0):
- KX-Moonraker-Bridge (MoonrakerPrinterAgent.cpp/.hpp) übernommen
- Preset.cpp: KX-filament_id-Patches per 3way auf neuen Upstream appliziert
  (Upstream-Fixes 10.-21.06. erhalten)
- Issue #52: Vendor-Filter bei User-Presets überspringen
- AMS-Leerslot-Fix (DevFilaSystem.cpp + AMSItem.cpp): leerer Slot zeigt
  kein altes Filament mehr
- About-Hinweis, verify_build.sh, Filament-Bridge-Doku, README
- Version 2.4.0-kx1
2026-06-29 10:02:52 +02:00
59 changed files with 1195 additions and 771 deletions

View File

@@ -1,11 +1,6 @@
name: Assign Issue name: Assign Issue
on: on: {}
schedule:
- cron: 0 0 * * *
issue_comment:
types: [created]
workflow_dispatch:
jobs: jobs:
assign: assign:

View File

@@ -1,9 +1,6 @@
name: Auto-close duplicate issues name: Auto-close duplicate issues
description: Auto-closes issues that are duplicates of existing issues description: Auto-closes issues that are duplicates of existing issues
on: on: {}
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
jobs: jobs:
auto-close-duplicates: auto-close-duplicates:

View File

@@ -1,22 +1,7 @@
name: Backfill Duplicate Comments name: Backfill Duplicate Comments
description: Triggers duplicate detection for old issues that don't have duplicate comments description: Triggers duplicate detection for old issues that don't have duplicate comments
on: on: {}
workflow_dispatch:
inputs:
days_back:
description: 'How many days back to look for old issues'
required: false
default: '90'
type: string
dry_run:
description: 'Dry run mode (true to only log what would be done)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'
jobs: jobs:
backfill-duplicate-comments: backfill-duplicate-comments:

View File

@@ -1,269 +1,209 @@
name: Build all name: Build all
on: on: {}
push:
branches: concurrency:
- main group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
- release/* cancel-in-progress: true
- belt-printer
paths:
- 'deps/**' jobs:
- 'src/**' build_linux:
- '**/CMakeLists.txt' strategy:
- 'version.inc' fail-fast: false
- 'localization/**' # Build both arches on every event (PRs included), through the same
- 'resources/**' # build_check_cache -> build_deps -> build_orca chain (the AppImage).
- ".github/workflows/build_*.yml" # aarch64 always uses the GitHub-hosted arm runner (there is no arm
- 'scripts/flatpak/**' # self-hosted server). amd64's empty arch is load-bearing: it keeps the
- 'scripts/msix/**' # historical 'linux-clang' deps cache key and the unsuffixed asset names.
- 'tests/**' matrix:
include:
pull_request: - arch: ""
branches: os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
- main - arch: "aarch64"
- release/* os: ubuntu-24.04-arm
paths: # Don't run scheduled builds on forks:
- 'deps/**' if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
- 'deps_src/**' uses: ./.github/workflows/build_check_cache.yml
- 'src/**' with:
- '**/CMakeLists.txt' os: ${{ matrix.os }}
- 'version.inc' arch: ${{ matrix.arch }}
- ".github/workflows/build_*.yml" build-deps-only: ${{ inputs.build-deps-only || false }}
- 'build_linux.sh' secrets: inherit
- 'build_release_vs.bat' build_windows:
- 'build_release_vs2022.bat' # Don't run scheduled builds on forks:
- 'build_release_macos.sh' if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
- 'scripts/flatpak/**' uses: ./.github/workflows/build_check_cache.yml
- 'scripts/msix/**' with:
- 'tests/**' os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }}
build-deps-only: ${{ inputs.build-deps-only || false }}
force-build: ${{ github.event_name == 'schedule' }}
schedule: secrets: inherit
- cron: '0 17 * * *' # run once a day at 1 AM Singapore time (UTC+8) build_macos_arch:
strategy:
workflow_dispatch: # allows for manual dispatch fail-fast: false
inputs: matrix:
build-deps-only: arch:
description: 'Only build dependencies (bypasses caching)' - arm64
type: boolean - x86_64
default: false # Don't run scheduled builds on forks:
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
concurrency: uses: ./.github/workflows/build_check_cache.yml
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} with:
cancel-in-progress: true os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
arch: ${{ matrix.arch }}
build-deps-only: ${{ inputs.build-deps-only || false }}
jobs: force-build: ${{ github.event_name == 'schedule' }}
build_linux: secrets: inherit
strategy: build_macos_universal:
fail-fast: false name: Build macOS Universal
# Build both arches on every event (PRs included), through the same needs: build_macos_arch
# build_check_cache -> build_deps -> build_orca chain (the AppImage). if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
# aarch64 always uses the GitHub-hosted arm runner (there is no arm uses: ./.github/workflows/build_orca.yml
# self-hosted server). amd64's empty arch is load-bearing: it keeps the with:
# historical 'linux-clang' deps cache key and the unsuffixed asset names. os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
matrix: arch: universal
include: macos-combine-only: true
- arch: "" secrets: inherit
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }} unit_tests:
- arch: "aarch64" name: Unit Tests
os: ubuntu-24.04-arm # Tests are built on the aarch64 leg by default (faster GitHub arm runner),
# Don't run scheduled builds on forks: # so run them there; self-hosted builds them on the amd64 server instead.
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }}
uses: ./.github/workflows/build_check_cache.yml needs: build_linux
with: if: ${{ !cancelled() && success() }}
os: ${{ matrix.os }} steps:
arch: ${{ matrix.arch }} - name: Checkout
build-deps-only: ${{ inputs.build-deps-only || false }} uses: actions/checkout@v6
secrets: inherit with:
build_windows: sparse-checkout: |
name: Build Windows ${{ matrix.arch }} .github
strategy: scripts
fail-fast: false tests
matrix: - name: Apt-Install Dependencies
include: if: ${{ !vars.SELF_HOSTED }}
- arch: x64 uses: ./.github/actions/apt-install-deps
os: windows-latest - name: Restore Test Artifact
- arch: arm64 uses: actions/download-artifact@v8
os: windows-11-arm with:
# Don't run scheduled builds on forks: name: ${{ github.sha }}-tests
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} - uses: lukka/get-cmake@latest
uses: ./.github/workflows/build_check_cache.yml with:
with: cmakeVersion: "~4.3.0" # use most recent 4.3.x version
# Self-hosted runner is x64-only; ARM64 always uses the GitHub-hosted runner. useLocalCache: true # <--= Use the local cache (default is 'false').
os: ${{ (matrix.arch == 'x64' && vars.SELF_HOSTED) && 'orca-win-server' || matrix.os }} useCloudCache: true
arch: ${{ matrix.arch }} - name: Unpackage and Run Unit Tests
build-deps-only: ${{ inputs.build-deps-only || false }} timeout-minutes: 20
force-build: ${{ github.event_name == 'schedule' }} run: |
secrets: inherit tar -xvf build_tests.tar
build_macos_arch: scripts/run_unit_tests.sh
strategy: - name: Upload Test Logs
fail-fast: false uses: actions/upload-artifact@v7
matrix: if: ${{ failure() }}
arch: with:
- arm64 name: unit-test-logs
- x86_64 path: build/tests/**/*.log
# Don't run scheduled builds on forks: - name: Publish Test Results
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} if: always()
uses: ./.github/workflows/build_check_cache.yml uses: EnricoMi/publish-unit-test-result-action@v2
with: with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} files: "ctest_results.xml"
arch: ${{ matrix.arch }} - name: Delete Test Artifact
build-deps-only: ${{ inputs.build-deps-only || false }} if: success()
force-build: ${{ github.event_name == 'schedule' }} uses: geekyeggo/delete-artifact@v6
secrets: inherit with:
build_macos_universal: name: ${{ github.sha }}-tests
name: Build macOS Universal flatpak:
needs: build_macos_arch name: "Flatpak"
if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} container:
uses: ./.github/workflows/build_orca.yml image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
with: options: --privileged
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} volumes:
arch: universal - /usr/local/lib/android:/usr/local/lib/android
macos-combine-only: true - /usr/share/dotnet:/usr/share/dotnet
secrets: inherit - /opt/ghc:/opt/ghc1
unit_tests: - /usr/local/share/boost:/usr/local/share/boost1
name: Unit Tests - /opt/hostedtoolcache:/opt/hostedtoolcache1
# Tests are built on the aarch64 leg by default (faster GitHub arm runner), strategy:
# so run them there; self-hosted builds them on the amd64 server instead. fail-fast: false
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }} matrix:
needs: build_linux variant:
if: ${{ !cancelled() && success() }} - arch: x86_64
steps: runner: ubuntu-24.04
- name: Checkout - arch: aarch64
uses: actions/checkout@v6 runner: ubuntu-24.04-arm
with: # Don't run scheduled builds on forks; skip entirely on self-hosted runners
sparse-checkout: | if: ${{ !cancelled() && !vars.SELF_HOSTED && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
.github
scripts runs-on: ${{ matrix.variant.runner }}
tests env:
- name: Apt-Install Dependencies date:
if: ${{ !vars.SELF_HOSTED }} ver:
uses: ./.github/actions/apt-install-deps ver_pure:
- name: Restore Test Artifact steps:
uses: actions/download-artifact@v8 - name: "Remove unneeded stuff to free disk space"
with: run:
name: ${{ github.sha }}-tests rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
- uses: lukka/get-cmake@latest - uses: actions/checkout@v6
with: - name: Get the version and date
cmakeVersion: "~4.3.0" # use most recent 4.3.x version run: |
useLocalCache: true # <--= Use the local cache (default is 'false'). ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
useCloudCache: true if [[ "${{ github.event_name }}" == "pull_request" ]]; then
- name: Unpackage and Run Unit Tests ver="PR-${{ github.event.number }}"
timeout-minutes: 20 git_commit_hash="${{ github.event.pull_request.head.sha }}"
run: | else
tar -xvf build_tests.tar ver=V$ver_pure
scripts/run_unit_tests.sh git_commit_hash="${{ github.sha }}"
- name: Upload Test Logs fi
uses: actions/upload-artifact@v7 echo "ver=$ver" >> $GITHUB_ENV
if: ${{ failure() }} echo "ver_pure=$ver_pure" >> $GITHUB_ENV
with: echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
name: unit-test-logs echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
path: build/tests/**/*.log shell: bash
- name: Publish Test Results # Manage flatpak-builder cache externally so PRs restore but never upload
if: always() - name: Restore flatpak-builder cache
uses: EnricoMi/publish-unit-test-result-action@v2 if: github.event_name == 'pull_request'
with: uses: actions/cache/restore@v5
files: "ctest_results.xml" with:
- name: Delete Test Artifact path: .flatpak-builder
if: success() key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.event.pull_request.base.sha }}
uses: geekyeggo/delete-artifact@v6 restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
with: - name: Save/restore flatpak-builder cache
name: ${{ github.sha }}-tests if: github.event_name != 'pull_request'
flatpak: uses: actions/cache@v5
name: "Flatpak" with:
container: path: .flatpak-builder
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50 key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.sha }}
options: --privileged restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
volumes: - name: Disable debug info for faster CI builds
- /usr/local/lib/android:/usr/local/lib/android run: |
- /usr/share/dotnet:/usr/share/dotnet sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' \
- /opt/ghc:/opt/ghc1 scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
- /usr/local/share/boost:/usr/local/share/boost1 shell: bash
- /opt/hostedtoolcache:/opt/hostedtoolcache1 - name: Inject git commit hash into Flatpak manifest
strategy: run: |
fail-fast: false sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \
matrix: scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
variant: shell: bash
- arch: x86_64 - uses: flatpak/flatpak-github-actions/flatpak-builder@master
runner: ubuntu-24.04 with:
- arch: aarch64 bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
runner: ubuntu-24.04-arm manifest-path: scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
# Don't run scheduled builds on forks; skip entirely on self-hosted runners cache: false
if: ${{ !cancelled() && !vars.SELF_HOSTED && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} arch: ${{ matrix.variant.arch }}
upload-artifact: false
runs-on: ${{ matrix.variant.runner }} - name: Upload artifacts Flatpak
env: uses: actions/upload-artifact@v7
date: with:
ver: name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
ver_pure: path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
# Belt-printer nightlies share the main nightly release but carry a `_belt` - name: Deploy Flatpak to nightly release
# suffix so they never overwrite the main assets. if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
nightly_suffix: ${{ github.ref == 'refs/heads/belt-printer' && '_belt' || '' }} uses: WebFreak001/deploy-nightly@v3.2.0
steps: with:
- name: "Remove unneeded stuff to free disk space" upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
run: release_id: 137995723
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/* asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
- uses: actions/checkout@v6 asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
- name: Get the version and date asset_content_type: application/octet-stream
run: | max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
ver="PR-${{ github.event.number }}"
git_commit_hash="${{ github.event.pull_request.head.sha }}"
else
ver=V$ver_pure
git_commit_hash="${{ github.sha }}"
fi
echo "ver=$ver" >> $GITHUB_ENV
echo "ver_pure=$ver_pure" >> $GITHUB_ENV
echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
shell: bash
# Manage flatpak-builder cache externally so PRs restore but never upload
- name: Restore flatpak-builder cache
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.event.pull_request.base.sha }}
restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
- name: Save/restore flatpak-builder cache
if: github.event_name != 'pull_request'
uses: actions/cache@v5
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.sha }}
restore-keys: flatpak-builder-${{ matrix.variant.arch }}-
- name: Disable debug info for faster CI builds
run: |
sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
shell: bash
- name: Inject git commit hash into Flatpak manifest
run: |
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \
scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
shell: bash
- uses: flatpak/flatpak-github-actions/flatpak-builder@master
with:
bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
manifest-path: scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
cache: false
arch: ${{ matrix.variant.arch }}
upload-artifact: false
- name: Upload artifacts Flatpak
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
- name: Deploy Flatpak to nightly release
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer')
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: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
asset_name: OrcaSlicer-Linux-flatpak_nightly${{ env.nightly_suffix }}_${{ matrix.variant.arch }}.flatpak
asset_content_type: application/octet-stream
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted

View File

@@ -1,67 +1,52 @@
name: Check Cache name: Check Cache
on: on: {}
workflow_call:
inputs: jobs:
os: check_cache: # determines if there is a cache and outputs variables used in caching process
required: true name: Check Cache
type: string runs-on: ${{ inputs.os }}
arch: outputs:
required: false cache-key: ${{ steps.set_outputs.outputs.cache-key }}
type: string cache-path: ${{ steps.set_outputs.outputs.cache-path }}
build-deps-only: valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
required: false steps:
type: boolean - name: Checkout
force-build: uses: actions/checkout@v6
required: false with:
type: boolean lfs: 'false'
jobs: - name: set outputs
check_cache: # determines if there is a cache and outputs variables used in caching process id: set_outputs
name: Check Cache env:
runs-on: ${{ inputs.os }} # Keep macOS/Linux cache keys architecture-specific. amd64 Linux passes
outputs: # no arch (key stays 'linux-clang', preserving the existing cache);
cache-key: ${{ steps.set_outputs.outputs.cache-key }} # aarch64 gets its own 'linux-clang-aarch64' key.
cache-path: ${{ steps.set_outputs.outputs.cache-path }} cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && 'windows' || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }} dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || '/OrcaSlicer_dep' }}
steps: output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
- name: Checkout run: |
uses: actions/checkout@v6 echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
with: echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
lfs: 'false'
- name: load cache
- name: set outputs id: cache_deps
id: set_outputs uses: actions/cache@v5
env: with:
# Keep macOS/Windows cache keys architecture-specific. amd64 Linux passes path: ${{ steps.set_outputs.outputs.cache-path }}
# no arch (key stays 'linux-clang', preserving the existing cache); key: ${{ steps.set_outputs.outputs.cache-key }}
# aarch64 gets its own 'linux-clang-aarch64' key. lookup-only: true
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && format('windows-{0}', inputs.arch) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
# ARM64 builds use the build-arm64 tree (see build_release_vs.bat); x64/other use build. build_deps: # call next step
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }} name: Build Deps
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}} needs: [check_cache]
run: | uses: ./.github/workflows/build_deps.yml
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }} with:
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }} cache-key: ${{ needs.check_cache.outputs.cache-key }}
cache-path: ${{ needs.check_cache.outputs.cache-path }}
- name: load cache valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
id: cache_deps os: ${{ inputs.os }}
uses: actions/cache@v5 arch: ${{ inputs.arch }}
with: build-deps-only: ${{ inputs.build-deps-only }}
path: ${{ steps.set_outputs.outputs.cache-path }} force-build: ${{ inputs.force-build }}
key: ${{ steps.set_outputs.outputs.cache-key }} secrets: inherit
lookup-only: true
build_deps: # call next step
name: Build Deps
needs: [check_cache]
uses: ./.github/workflows/build_deps.yml
with:
cache-key: ${{ needs.check_cache.outputs.cache-key }}
cache-path: ${{ needs.check_cache.outputs.cache-path }}
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
os: ${{ inputs.os }}
arch: ${{ inputs.arch }}
build-deps-only: ${{ inputs.build-deps-only }}
force-build: ${{ inputs.force-build }}
secrets: inherit

View File

@@ -1,27 +1,4 @@
on: on: {}
workflow_call:
inputs:
cache-key:
required: true
type: string
cache-path:
required: true
type: string
valid-cache:
required: true
type: boolean
os:
required: true
type: string
arch:
required: false
type: string
build-deps-only:
required: false
type: boolean
force-build:
required: false
type: boolean
jobs: jobs:
build_deps: build_deps:

View File

@@ -1,22 +1,4 @@
on: on: {}
workflow_call:
inputs:
cache-key:
required: false
type: string
cache-path:
required: false
type: string
os:
required: true
type: string
arch:
required: false
type: string
macos-combine-only:
required: false
type: boolean
default: false
jobs: jobs:
build_orca: build_orca:

View File

@@ -1,12 +1,6 @@
name: Check locale name: Check locale
on: on: {}
pull_request:
branches:
- main
paths:
- 'localization/**'
- ".github/workflows/check_locale.yml"
jobs: jobs:
check_translation: check_translation:

View File

@@ -1,18 +1,5 @@
name: Check profiles name: Check profiles
on: on: {}
pull_request:
branches:
- main
paths:
- 'resources/profiles/**'
- ".github/workflows/check_profiles.yml"
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
permissions: permissions:
contents: read contents: read

View File

@@ -2,11 +2,7 @@ name: Post profile check comment
# NOTE: The workflow name in the 'workflows' filter below must match the 'name' # NOTE: The workflow name in the 'workflows' filter below must match the 'name'
# field in check_profiles.yml exactly. If that name changes, update it here too. # field in check_profiles.yml exactly. If that name changes, update it here too.
on: on: {}
workflow_run:
workflows: ["Check profiles"]
types:
- completed
permissions: permissions:
pull-requests: write pull-requests: write

View File

@@ -1,14 +1,6 @@
name: Orca Issue Dedupe name: Orca Issue Dedupe
description: Automatically dedupe GitHub issues using AI description: Automatically dedupe GitHub issues using AI
on: on: {}
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue number to process for duplicate detection'
required: true
type: string
jobs: jobs:
dedupe-issues: dedupe-issues:

View File

@@ -1,9 +1,6 @@
name: Generate Doxygen Documentation name: Generate Doxygen Documentation
on: on: {}
schedule:
- cron: '0 0 * * 1' # Every Monday at midnight UTC
workflow_dispatch: # Manual trigger
concurrency: concurrency:
group: ${{ github.workflow }} group: ${{ github.workflow }}

View File

@@ -1,13 +1,6 @@
name: PR Label Bot name: PR Label Bot
on: on: {}
pull_request_target:
types:
- opened
- reopened
issue_comment:
types:
- created
permissions: permissions:
contents: read contents: read

View File

@@ -9,17 +9,7 @@ name: Publish to draft release
# `RELEASE_PUBLISHER` repo variable (set to "SoftFever"). To allow someone else, # `RELEASE_PUBLISHER` repo variable (set to "SoftFever"). To allow someone else,
# change that variable: gh variable set RELEASE_PUBLISHER --body "<login>". # change that variable: gh variable set RELEASE_PUBLISHER --body "<login>".
on: on: {}
workflow_dispatch:
inputs:
run_id:
description: 'Run ID of the "Build all" workflow to pull binaries from (the number in the Actions run URL)'
required: true
type: string
tag:
description: 'Tag of the draft release to upload to (e.g. v2.4.0)'
required: true
type: string
permissions: permissions:
contents: write # upload release assets contents: write # upload release assets

View File

@@ -1,15 +1,4 @@
on: on: {}
push:
paths:
- '**.sh'
- 'scripts/linux.d/*'
pull_request:
paths:
- '**.sh'
- 'scripts/linux.d/*'
schedule:
- cron: '55 7 * * *' # run once a day near midnight US Pacific time
workflow_dispatch: # allows for manual dispatch
name: "Shellcheck" name: "Shellcheck"
permissions: {} permissions: {}

View File

@@ -1,8 +1,5 @@
name: Update Translation Catalog name: Update Translation Catalog
on: on: {}
# schedule:
# - cron: 0 0 * * 1
workflow_dispatch:
jobs: jobs:
update_translation: update_translation:

View File

@@ -1,7 +1,6 @@
name: Publish to WinGet name: Publish to WinGet
on: on: {}
release:
types: [ released ]
jobs: jobs:
publish: publish:
runs-on: windows-latest runs-on: windows-latest

249
README.md
View File

@@ -1,237 +1,58 @@
<div align="center"> # OrcaSlicer-KX
<picture> **OrcaSlicer mit KX-Bridge-Patches für den Anycubic Kobra X**
<img alt="OrcaSlicer logo" src="resources/images/OrcaSlicer.png" width="15%" height="15%">
</picture>
<a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> Fertige Binaries von [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer) mit integrierten KX-spezifischen Erweiterungen — empfohlen für den Einsatz mit [KX-Bridge](https://gitea.it-drui.de/viewit/KX-Bridge-Release).
[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml) [![Download](https://img.shields.io/badge/Download-Releases-2EA043?style=for-the-badge&logo=gitea&logoColor=white)](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/releases)
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints. ---
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
<h3>
# Official links and community ## Download
#### Official Website: Aktuelle Version: **2.4.1-kx1** (Basis: OrcaSlicer 2.4.1 stable)
<a href="https://www.orcaslicer.com/" style="font-size:2em;">OrcaSlicer.com</a> | Plattform | Datei | Hinweis |
|-----------|-------|---------|
| Linux x86_64 | `OrcaSlicer-KX-Linux-x86_64.AppImage` | `chmod +x` setzen, dann starten |
| Windows x86_64 | `OrcaSlicer-KX-Windows-x86_64.zip` | Entpacken, `orca-slicer.exe` starten |
#### Github Repository: macOS wird nicht bereitgestellt — bitte aus dem Upstream selbst bauen.
<a href="https://github.com/OrcaSlicer/OrcaSlicer"><img src="https://img.shields.io/badge/OrcaSlicer-181717?style=flat&logo=github&logoColor=white" width="200" alt="GitHub Logo"/> </a> ---
#### Follow us: ## Enthaltene Patches
<a href="https://twitter.com/real_OrcaSlicer"><img src="https://img.shields.io/badge/real__OrcaSlicer-000000?style=flat&logo=x&logoColor=white" width="200" alt="X Logo"/> </a> - **Moonraker-Bridge:** mehrstufiges Filament-Matching (Name, Sub-Brand, Vendor-Fallback) mit Vendor-Filter-Skip
<a href="https://www.youtube.com/@OfficialOrcaSlicer"><img src="https://img.shields.io/badge/OfficialOrcaSlicer-FF0000?style=flat&logo=youtube&logoColor=white" width="200" alt="YouTube Logo"/> </a> - **AMS-Leerslot-Fix:** leere AMS-Slots werden korrekt grau dargestellt (kein Absturz / falsches Filament)
- **Filament-ID:** eindeutige `filament_id` für abgeleitete User-Presets (korrekte Bridge-Synchronisation)
- **Kobra X Profile:** Drucker- und G-Code-Profile für den Anycubic Kobra X
#### Join our Discord community: ---
<a href="https://discord.gg/P4VE9UY9gJ"><img src="https://img.shields.io/badge/-Discord-5865F2?style=flat&logo=discord&logoColor=fff" width="200" alt="discord logo"/> </a> ## Filament-Presets & KX-Bridge
<table border="2" style="border-color: #ffa500; background-color:rgb(232, 220, 180); color: #856404;"> 📄 [Anleitung: eigene Filament-Presets erstellen, prüfen und importieren](https://gitea.it-drui.de/viewit/KX-Bridge-Release/src/branch/master/docs/filament-preset-bridge-guide.md)
<tr>
<td>
<strong>⚠️ CAUTION:</strong><br>
Several clickbait and malicious websites, such as <b>orca-slicer[.]com</b> and <b>orcaslicer[.]net</b>, are pretending to be the official OrcaSlicer site. These sites may redirect you to dangerous downloads or contain misleading information.<br>
<b>Our only official website is <a href="https://www.orcaslicer.com/">www.orcaslicer.com</a>.</b><br><br>
If you come across any of these in search results, please <b>report them</b> as unsafe or phishing to help keep the community secure with:<br>
- <a href="https://safebrowsing.google.com/safebrowsing/report_phish/">Google Safe Browsing</a><br>
- <a href="https://www.microsoft.com/en-us/wdsi/support/report-unsafe-site">Microsoft Security Intelligence</a><br>
- <a href="https://ipthreat.net/tools/reportphishing">IPThreat</a>
</td>
</tr>
</table>
</div> ---
# Main features ## Branch-Struktur
- **[Advanced Calibration Tools](https://www.orcaslicer.com/wiki/calibration_guide)** | Branch | Version | Rolle |
Comprehensive suite: temperature towers, flow rate, retraction & more for optimal performance. |--------|---------|-------|
- **[Precise Wall](https://www.orcaslicer.com/wiki/quality_settings_precision#precise-wall) and [Seam Control](https://www.orcaslicer.com/wiki/quality_settings_seam)** | `stable` / `kx-v2.4` | 2.4.1-kx1 | **aktuell stabil** |
Adjust outer wall spacing and apply scarf seams to enhance print accuracy. | `kx-v2.3` | 2.3.2-kx4 | eingefroren (Altstand) |
- **[Sandwich Mode](https://www.orcaslicer.com/wiki/quality_settings_wall_and_surfaces#innerouterinner) and [Polyholes](https://www.orcaslicer.com/wiki/quality_settings_precision#polyholes) Support**
Use varied infill [patterns](https://www.orcaslicer.com/wiki/strength_settings_patterns) and accurate hole shapes for improved clarity.
- **[Overhang](https://www.orcaslicer.com/wiki/quality_settings_overhangs) and [Support Optimization](https://www.orcaslicer.com/wiki#support-settings)**
Modify geometry for printable overhangs with precise support placement.
- **[Granular Controls and Customization](https://www.orcaslicer.com/wiki#process-settings)**
Fine-tune print speed, layer height, pressure, and temperature with precision.
- **Network Printer Support**
Seamless integration with Klipper, PrusaLink, and OctoPrint for remote control.
- **[Mouse Ear Brims](https://www.orcaslicer.com/wiki/others_settings_brim) & [Adaptive Bed Mesh](https://www.orcaslicer.com/wiki/printer_basic_information_adaptive_bed_mesh)**
Automatic brims and adaptive mesh calibration ensure consistent adhesion.
- **User-Friendly Interface**
Intuitive drag-and-drop design with pre-made profiles for popular printers.
- **[Open-Source](https://github.com/OrcaSlicer/OrcaSlicer) & [Community Driven](https://discord.gg/P4VE9UY9gJ)**
Regular updates fueled by continuous community contributions.
- **Wide Printer Compatibility**
Supports a broad range of printers: Bambu Lab, Prusa, Creality, Voron, and more.
- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
# Wiki ---
The [wiki](https://www.orcaslicer.com/wiki) aims to provide a detailed explanation of the slicer settings, including how to maximize their use and how to calibrate and set up your printer. ## Warum ein eigenes Repo?
- **[Access the wiki here](https://www.orcaslicer.com/wiki)** KX-Bridge selbst liegt unter [viewit/KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release).
- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/how_to_wiki)** OrcaSlicer steht unter AGPL-3.0 — ein separates Repo hält Lizenzen, Quellherkunft und Update-Zyklen sauber getrennt.
# Download ---
## Stable Release ## Lizenz & Quelle
📥 **[Download the Latest Stable Release](https://github.com/OrcaSlicer/OrcaSlicer/releases/latest)** Diese Builds sind Ableitungen von [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer), lizenziert unter **GNU AGPL-3.0**.
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users. Der KX-Patch-Quellcode liegt auf Branch [`kx-v2.4`](https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/kx-v2.4) dieses Repos.
## Nightly Builds
🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds)**
Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated.
# How to install
## Windows
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
- *For convenience there is also a portable build available.*
<details>
<summary>Troubleshooting</summary>
- *If you have troubles to run the build, you might need to install following runtimes:*
- [MicrosoftEdgeWebView2RuntimeInstallerX64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe)
- [Details of this runtime](https://aka.ms/webview2)
- [Alternative Download Link Hosted by Microsoft](https://go.microsoft.com/fwlink/p/?LinkId=2124703)
- [vcredist2019_x64](https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v1.0.10-sf2/vcredist2019_x64.exe)
- [Alternative Download Link Hosted by Microsoft](https://aka.ms/vs/17/release/vc_redist.x64.exe)
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
</details>
Windows Package Manager
```shell
winget install --id=SoftFever.OrcaSlicer -e
```
## Mac
1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU.
2. Drag OrcaSlicer.app to Application folder.
3. *If you want to run a build from a PR, you also need to follow the instructions below:*
<details>
<summary>Quarantine</summary>
- Option 1 (You only need to do this once. After that the app can be opened normally.):
- Step 1: Hold _cmd_ and right click the app, from the context menu choose **Open**.
- Step 2: A warning window will pop up, click _Open_
- Option 2:
Execute this command in terminal:
```shell
xattr -dr com.apple.quarantine /Applications/OrcaSlicer.app
```
- Option 3:
- Step 1: open the app, a warning window will pop up
![mac_cant_open](./SoftFever_doc/mac_cant_open.png)
- Step 2: in `System Settings` -> `Privacy & Security`, click `Open Anyway`:
![mac_security_setting](./SoftFever_doc/mac_security_setting.png)
</details>
## Linux
### Flathub (Recommended)
OrcaSlicer is available through FlatHub:
<a href='https://flathub.org/apps/com.orcaslicer.OrcaSlicer'><img width='240' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
Install from the command line:
```shell
flatpak install flathub com.orcaslicer.OrcaSlicer
flatpak run com.orcaslicer.OrcaSlicer
```
It can also be installed through graphical software managers (KDE Discover, GNOME Software, etc.) when Flathub is enabled. Search for **OrcaSlicer** in your software center.
### AppImage
AppImages are published for both **x86_64** and **aarch64** (ARM64). Pick the file matching your CPU — the ARM64 build has `aarch64` in its name (e.g. `OrcaSlicer_Linux_AppImage_Ubuntu2404_aarch64_*.AppImage`).
1. Download App image from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
2. Double click the downloaded file to run it.
3. If you run into trouble executing it, try this command in the terminal:
`chmod +x /path_to_appimage/OrcaSlicer_Linux.AppImage`
# How to Compile
All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://www.orcaslicer.com/wiki/how_to_build) page.
Please refer to the wiki to ensure you're following the latest and most accurate steps for your platform.
# Klipper Note
If you're running Klipper, it's recommended to add the following configuration to your `printer.cfg` file.
```gcode
# Enable object exclusion
[exclude_object]
# Enable arcs support
[gcode_arcs]
resolution: 0.1
```
# Supports
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
Thank you! :)
## Sponsors
<table>
<tr>
<td>
<a href="https://qidi3d.com/" style="display:inline-block; border-radius:8px; background:#fff;">
<img src="SoftFever_doc\sponsor_logos\QIDI.png" alt="QIDI" width="100" height="100">
</a>
</td>
<td>
<a href="https://bigtree-tech.com/" style="display:inline-block; border-radius:8px; background:#222;">
<img src="SoftFever_doc\sponsor_logos\BigTreeTech.png" alt="BIGTREE TECH" width="100" height="100">
</a>
</td>
</tr>
</table>
## Backers:
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
## Support me
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://paypal.me/softfever3d"><img src="https://img.shields.io/badge/PayPal-003087?style=flat&logo=paypal&logoColor=fff" height="50"></a>
## Some Background
Open-source slicing has always been built on a tradition of collaboration and attribution. [Slic3r](https://github.com/Slic3r/Slic3r), created by Alessandro Ranellucci and the RepRap community, laid the foundation. [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research built on Slic3r and acknowledged that heritage. [Bambu Studio](https://github.com/bambulab/BambuStudio) in turn forked from PrusaSlicer, and [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill extended PrusaSlicer with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.
OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and ideas inspired by CuraSlicer and SuperSlicer. But it has since grown far beyond its origins. Through relentless innovation — introducing advanced calibration tools, precise wall and seam control, tree supports, adaptive slicing, and hundreds of other features — OrcaSlicer has become the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.
The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license.
- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3.
- The **Bambu networking plugin** is based on non-free libraries from BambuLab. It is optional to the OrcaSlicer and provides extended functionalities for Bambulab printer users.

View File

@@ -0,0 +1,356 @@
# Eigene Filament-Presets anlegen, prüfen und mit KX-Bridge verknüpfen
> **Gilt für:** OrcaSlicer-KX v2.4.0-alpha-kx2 oder neuer
---
## Was ist die `filament_id` und warum ist sie wichtig?
Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wird von der KX-Bridge genutzt, um beim AMS-Sync das richtige Preset zuzuordnen.
- System-Presets (z.B. "Polymaker PolyTerra PLA") haben eine feste ID wie `GFL99` oder `OGFL04`.
- **Eigene (User-)Presets** bekommen in OrcaSlicer-KX automatisch eine eindeutige ID, die mit `P` beginnt (z.B. `P3a7f2c1`).
Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert.
> **Achtung — abgeleitete Presets (Issue #52):** Wenn du dein Preset von einem **Hersteller-Preset** ableitest (z.B. "Anycubic PLA Matte"), übernimmt es zunächst die feste Hersteller-ID (z.B. `GFA001`). Beim Sync wird dann fälschlicherweise das Hersteller-Preset statt deines eigenen ausgewählt.
> Ab OrcaSlicer-KX **v2.4.0-alpha-kx3** wird beim Speichern automatisch eine eigene `P...`-ID vergeben — auch für abgeleitete Presets. Hast du das Preset mit einer älteren Version angelegt, **öffne es einmal und speichere es erneut** (Save), damit die `P`-ID generiert wird.
---
## 1. Eigenes Filament-Preset anlegen
1. OrcaSlicer-KX starten
2. Rechts oben im **Filament-Dropdown** ein passendes Basis-Preset wählen (z.B. "Generic PLA" oder ein Hersteller-Preset)
3. Einstellungen nach Wunsch anpassen (Temperaturen, Kühlung, etc.)
4. Auf das **Speichern-Symbol** (Diskette) klicken → **"Save as new preset"**
5. Namen eingeben — z.B. `SUNLU PLA+ 2.0`
> Der Name muss später exakt so in der Bridge eingetragen werden.
6. Drucker auswählen: **Anycubic Kobra X 0.4 nozzle** — wichtig für die Kompatibilität!
7. **Speichern** klicken
8. OrcaSlicer **einmal neu starten** — erst dann wird die `filament_id` dauerhaft gespeichert.
---
## 2. Eindeutige ID prüfen
Nach dem Neustart prüfen, ob die ID korrekt gesetzt wurde:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Die Datei öffnen und nach `filament_id` suchen:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Korrekt: ID beginnt mit `P` gefolgt von 7 Hex-Zeichen
❌ Fehlt oder leer: OrcaSlicer-KX zu alt — Update auf v2.4.0-alpha-kx2 oder neuer
---
## 3. Preset auf einen anderen PC übertragen (Import)
### Exportieren (Quell-PC)
Die Preset-Datei einfach kopieren:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importieren (Ziel-PC)
**Methode A — Datei direkt kopieren:**
1. Die `.json`-Datei in das gleiche Verzeichnis auf dem Ziel-PC kopieren
2. OrcaSlicer neu starten → Preset erscheint im Dropdown
**Methode B — OrcaSlicer Import-Funktion:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Die `.json`-Datei auswählen
3. OrcaSlicer neu starten
> **Wichtig:** Die `filament_id` in der Datei bleibt erhalten — das Preset wird auf dem Ziel-PC genauso erkannt wie auf dem Quell-PC.
---
## 4. Preset in KX-Bridge verknüpfen
1. KX-Bridge UI öffnen
2. **Filament-Verwaltung** → AMS-Slot auswählen
3. Im Feld **Filament-Name** exakt den OrcaSlicer-Preset-Namen eintragen:
```
SUNLU PLA+ 2.0
```
4. Speichern
Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer findet das Preset anhand von Name und `filament_id` → zeigt es korrekt an.
---
## Wichtige Hinweise
| Was | Warum |
|-----|-------|
| Name in OrcaSlicer und Bridge müssen **exakt** übereinstimmen | Groß-/Kleinschreibung und Sonderzeichen werden verglichen |
| Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen |
| Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben |
| **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets |
| Bei von Hersteller-Presets abgeleiteten Presets: **v2.4.0-alpha-kx3** oder neuer | Erst ab dieser Version wird die geerbte Hersteller-ID beim Speichern durch eine eigene `P`-ID ersetzt (Issue #52) |
---
---
# How to Create, Verify and Import Custom Filament Presets for KX-Bridge
> **Requires:** OrcaSlicer-KX v2.4.0-alpha-kx2 or newer
---
## What is the `filament_id` and why does it matter?
Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge uses this ID to match the correct preset during AMS sync.
- System presets (e.g. "Polymaker PolyTerra PLA") have a fixed ID like `GFL99` or `OGFL04`.
- **Custom (user) presets** automatically receive a unique ID starting with `P` (e.g. `P3a7f2c1`) in OrcaSlicer-KX.
Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists.
> **Caution — derived presets (Issue #52):** If you derive your preset from a **vendor preset** (e.g. "Anycubic PLA Matte"), it initially inherits the fixed vendor ID (e.g. `GFA001`). During sync the vendor preset is then incorrectly selected instead of your own.
> As of OrcaSlicer-KX **v2.4.0-alpha-kx3**, a unique `P...` ID is generated automatically on save — including for derived presets. If you created the preset with an older version, **open it once and save it again** so the `P` ID gets generated.
---
## 1. Create a Custom Filament Preset
1. Launch OrcaSlicer-KX
2. Select a suitable base preset from the **filament dropdown** (e.g. "Generic PLA" or a vendor preset)
3. Adjust settings as needed (temperatures, cooling, etc.)
4. Click the **save icon** (floppy disk) → **"Save as new preset"**
5. Enter a name — e.g. `SUNLU PLA+ 2.0`
> This name must be entered in the bridge exactly as typed here.
6. Select printer: **Anycubic Kobra X 0.4 nozzle** — required for compatibility!
7. Click **Save**
8. **Restart OrcaSlicer once** — the `filament_id` is only written permanently after a restart.
---
## 2. Verify the Unique ID
After restarting, check that the ID was set correctly:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Open the file and look for `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correct: ID starts with `P` followed by 7 hex characters
❌ Missing or empty: Your OrcaSlicer-KX version is too old — update to v2.4.0-alpha-kx2 or newer
---
## 3. Transfer a Preset to Another PC (Import)
### Export (source PC)
Simply copy the preset file:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Import (target PC)
**Method A — Copy file directly:**
1. Copy the `.json` file to the same directory on the target PC
2. Restart OrcaSlicer → preset appears in the dropdown
**Method B — OrcaSlicer import function:**
1. In OrcaSlicer: **File → Import → Import Configs...**
2. Select the `.json` file
3. Restart OrcaSlicer
> **Note:** The `filament_id` inside the file is preserved — the preset will be recognized on the target PC exactly as on the source PC.
---
## 4. Link the Preset in KX-Bridge
1. Open the KX-Bridge UI
2. Go to **Filament Management** → select the AMS slot
3. In the **Filament Name** field, enter the OrcaSlicer preset name exactly:
```
SUNLU PLA+ 2.0
```
4. Save
The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer matches by name and `filament_id` → displays the preset correctly.
---
## Quick Reference
| What | Why |
|------|-----|
| Name in OrcaSlicer and Bridge must match **exactly** | Case and special characters are compared |
| Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving |
| **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart |
| Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets |
| For presets derived from vendor presets: **v2.4.0-alpha-kx3** or newer | Only from this version is the inherited vendor ID replaced with a unique `P` ID on save (Issue #52) |
---
---
# Cómo crear, verificar e importar perfiles de filamento personalizados para KX-Bridge
> **Requiere:** OrcaSlicer-KX v2.4.0-alpha-kx2 o superior
---
## ¿Qué es el `filament_id` y por qué es importante?
Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge usa este ID para asignar el perfil correcto durante la sincronización AMS.
- Los perfiles del sistema (p. ej. "Polymaker PolyTerra PLA") tienen un ID fijo como `GFL99` o `OGFL04`.
- Los **perfiles personalizados (usuario)** reciben automáticamente un ID único que empieza por `P` (p. ej. `P3a7f2c1`) en OrcaSlicer-KX.
Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista.
> **Atención — perfiles derivados (Issue #52):** Si derivas tu perfil de un **perfil de fabricante** (p. ej. "Anycubic PLA Matte"), inicialmente hereda el ID fijo del fabricante (p. ej. `GFA001`). Durante la sincronización se selecciona entonces por error el perfil del fabricante en lugar del tuyo.
> A partir de OrcaSlicer-KX **v2.4.0-alpha-kx3**, se genera automáticamente un ID `P...` único al guardar — también para perfiles derivados. Si creaste el perfil con una versión anterior, **ábrelo una vez y vuelve a guardarlo** (Save) para que se genere el ID `P`.
---
## 1. Crear un perfil de filamento personalizado
1. Iniciar OrcaSlicer-KX
2. Seleccionar un perfil base adecuado en el **menú desplegable de filamento** (p. ej. "Generic PLA" o un perfil de fabricante)
3. Ajustar la configuración según sea necesario (temperaturas, refrigeración, etc.)
4. Hacer clic en el **icono de guardar** (disquete) → **"Save as new preset"**
5. Introducir un nombre — p. ej. `SUNLU PLA+ 2.0`
> Este nombre debe introducirse en la bridge exactamente igual.
6. Seleccionar impresora: **Anycubic Kobra X 0.4 nozzle** — ¡necesario para la compatibilidad!
7. Hacer clic en **Guardar**
8. **Reiniciar OrcaSlicer una vez** — el `filament_id` solo se escribe de forma permanente tras un reinicio.
---
## 2. Verificar el ID único
Tras reiniciar, comprobar que el ID se ha establecido correctamente:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
Abrir el archivo y buscar `filament_id`:
```json
{
"filament_id": "P3a7f2c1",
...
}
```
✅ Correcto: el ID empieza por `P` seguido de 7 caracteres hexadecimales
❌ Falta o está vacío: la versión de OrcaSlicer-KX es demasiado antigua — actualizar a v2.4.0-alpha-kx2 o superior
---
## 3. Transferir un perfil a otro PC (importar)
### Exportar (PC de origen)
Simplemente copiar el archivo del perfil:
**Windows:**
```
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
```
**Linux:**
```
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
```
### Importar (PC de destino)
**Método A — Copiar el archivo directamente:**
1. Copiar el archivo `.json` al mismo directorio en el PC de destino
2. Reiniciar OrcaSlicer → el perfil aparece en el menú desplegable
**Método B — Función de importación de OrcaSlicer:**
1. En OrcaSlicer: **File → Import → Import Configs...**
2. Seleccionar el archivo `.json`
3. Reiniciar OrcaSlicer
> **Nota:** El `filament_id` dentro del archivo se conserva — el perfil se reconocerá en el PC de destino exactamente igual que en el de origen.
---
## 4. Vincular el perfil en KX-Bridge
1. Abrir la interfaz de KX-Bridge
2. Ir a **Gestión de filamentos** → seleccionar la ranura AMS
3. En el campo **Nombre de filamento**, introducir el nombre exacto del perfil de OrcaSlicer:
```
SUNLU PLA+ 2.0
```
4. Guardar
La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización → OrcaSlicer busca por nombre y `filament_id` → muestra el perfil correctamente.
---
## Referencia rápida
| Qué | Por qué |
|-----|---------|
| El nombre en OrcaSlicer y en Bridge debe coincidir **exactamente** | Se comparan mayúsculas, minúsculas y caracteres especiales |
| El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar |
| **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio |
| Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario |
| Para perfiles derivados de perfiles de fabricante: **v2.4.0-alpha-kx3** o superior | Solo a partir de esta versión se reemplaza el ID heredado del fabricante por un ID `P` único al guardar (Issue #52) |

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 246 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 246 KiB

View File

@@ -40,7 +40,7 @@ using namespace nlohmann;
namespace Slic3r { namespace Slic3r {
static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest"; static const std::string VERSION_CHECK_URL = "https://gitea.it-drui.de/api/v1/repos/viewit/OrcaSlicer-KX/releases/latest";
static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile"; static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile";
static const std::string MODELS_STR = "models"; static const std::string MODELS_STR = "models";

View File

@@ -43,6 +43,8 @@
#include <boost/uuid/uuid_generators.hpp> #include <boost/uuid/uuid_generators.hpp>
#include <boost/locale.hpp> #include <boost/locale.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/uuid/detail/md5.hpp>
#include <boost/algorithm/hex.hpp>
#include "libslic3r.h" #include "libslic3r.h"
#include "Utils.hpp" #include "Utils.hpp"
@@ -671,6 +673,31 @@ void Preset::save(DynamicPrintConfig* parent_config)
//BBS: add project embedded preset logic //BBS: add project embedded preset logic
if (this->is_project_embedded) if (this->is_project_embedded)
return; return;
// Generate a unique filament_id for user filament presets that don't have one yet.
// Inherited presets (e.g. "My PLA" inheriting "Generic PLA @System") previously had
// no filament_id which caused AMS sync to fall back to the parent's Generic ID.
// Also generate a new ID if filament_id is inherited from the parent (== base_id).
// This happens when a user preset is saved for the first time without having its own ID.
// A user preset needs its own filament_id if:
// - it has no filament_id at all, OR
// - its filament_id does not start with "P" (user preset IDs start with "P",
// system IDs start with GFL/OGFL/GFA/etc.)
bool needs_unique_filament_id = this->is_user() && !this->name.empty() &&
this->type == Preset::TYPE_FILAMENT &&
(this->filament_id.empty() || this->filament_id.front() != 'P');
if (needs_unique_filament_id) {
boost::uuids::detail::md5 hash;
boost::uuids::detail::md5::digest_type digest;
hash.process_bytes(this->name.data(), this->name.size());
hash.get_digest(digest);
const auto char_digest = reinterpret_cast<const char *>(&digest);
std::string result;
boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result));
this->filament_id = "P" + result.substr(0, 7);
BOOST_LOG_TRIVIAL(info) << "Preset::save: generated filament_id='" << this->filament_id << "' for user preset '" << this->name << "'";
}
//BBS: change to json format //BBS: change to json format
//this->config.save(this->file); //this->config.save(this->file);
std::string from_str; std::string from_str;
@@ -726,6 +753,8 @@ void Preset::save(DynamicPrintConfig* parent_config)
opt_dst->set(opt_src); opt_dst->set(opt_src);
} }
} }
if (!filament_id.empty())
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string()); temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
} else if (!filament_id.empty() && inherits().empty()) { } else if (!filament_id.empty() && inherits().empty()) {
DynamicPrintConfig temp_config = config; DynamicPrintConfig temp_config = config;
@@ -1679,7 +1708,10 @@ void PresetCollection::load_presets(
const Preset& default_preset = this->default_preset_for(config); const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) { if (inherit_preset) {
preset.config = inherit_preset->config; preset.config = inherit_preset->config;
preset.filament_id = inherit_preset->filament_id; // Only inherit filament_id from parent if this preset has no own ID in JSON.
// User presets with a P-prefix ID (generated by Preset::save) must keep their own ID.
if (preset.filament_id.empty())
preset.filament_id = inherit_preset->filament_id;
extend_default_config_length(config, false, {}); extend_default_config_length(config, false, {});
preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); preset.config.update_diff_values_to_child_config(config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
} }
@@ -2880,8 +2912,20 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
if (m_type == Preset::TYPE_PRINT) if (m_type == Preset::TYPE_PRINT)
preset.config.option<ConfigOptionString>("print_settings_id", true)->value = new_name; preset.config.option<ConfigOptionString>("print_settings_id", true)->value = new_name;
else if (m_type == Preset::TYPE_FILAMENT) else if (m_type == Preset::TYPE_FILAMENT) {
preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = new_name; preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = new_name;
// Generate a unique filament_id for user presets that don't have one yet (PR #13315).
if (preset.filament_id.empty() || preset.filament_id.front() != 'P') {
boost::uuids::detail::md5 hash;
boost::uuids::detail::md5::digest_type digest;
hash.process_bytes(new_name.data(), new_name.size());
hash.get_digest(digest);
const auto char_digest = reinterpret_cast<const char *>(&digest);
std::string result;
boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result));
preset.filament_id = "P" + result.substr(0, 7);
}
}
else if (m_type == Preset::TYPE_PRINTER) else if (m_type == Preset::TYPE_PRINTER)
preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = new_name; preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = new_name;
final_inherits = preset.inherits(); final_inherits = preset.inherits();

View File

@@ -3106,6 +3106,12 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed"); auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values; auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
if (filament_id.empty()) { if (filament_id.empty()) {
// keep index-alignment with filament_ams_list so that callers using the
// returned vectors by position (Overwriting mode) don't see a shifted index
combox_info.ams_filament_presets.push_back("");
combox_info.ams_filament_colors.push_back("");
combox_info.ams_multi_color_filment.push_back({});
combox_info.ams_names.push_back("");
continue; continue;
} }
if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) { if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) {
@@ -3216,9 +3222,28 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
} }
bool has_type = false; bool has_type = false;
auto filament_type = ams.opt_string("filament_type", 0u); auto filament_type = ams.opt_string("filament_type", 0u);
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) { auto sub_brands = ams.opt_string("filament_sub_brands", 0u);
has_type |= f.config.opt_string("filament_type", 0u) == filament_type; // If sub_brands (filament brand name) is known, prefer a preset whose name starts with it
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; }); // to resolve ID collisions where two different vendors share the same filament_id.
auto iter = filaments.end();
if (!sub_brands.empty() && !filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type, &sub_brands](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id
&& boost::algorithm::istarts_with(f.name, sub_brands); });
}
if (iter == filaments.end()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
}
// Also search user presets (not base presets) with matching filament_id.
// User presets have P-prefix IDs and are not returned by get_preset_base(f) == &f.
if (iter == filaments.end() && !filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && !f.is_system && f.filament_id == filament_id; });
}
if (iter == filaments.end()) { if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
if (!filament_type.empty()) { if (!filament_type.empty()) {

View File

@@ -5015,6 +5015,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Filament to print outer walls.\n\"Default\" uses the active object/part filament."); def->tooltip = L("Filament to print outer walls.\n\"Default\" uses the active object/part filament.");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->mode = comAdvanced;
def->aliases = { "wall_filament", "wall_filament_id", "outer_wall_filament", "perimeter_extruder" };
def->set_default_value(new ConfigOptionInt(0)); def->set_default_value(new ConfigOptionInt(0));
def = this->add("inner_wall_filament_id", coInt); def = this->add("inner_wall_filament_id", coInt);
@@ -5024,6 +5025,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Filament to print inner walls.\n\"Default\" uses the active object/part filament."); def->tooltip = L("Filament to print inner walls.\n\"Default\" uses the active object/part filament.");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->mode = comAdvanced;
def->aliases = { "inner_wall_filament" };
def->set_default_value(new ConfigOptionInt(0)); def->set_default_value(new ConfigOptionInt(0));
def = this->add("inner_wall_line_width", coFloatOrPercent); def = this->add("inner_wall_line_width", coFloatOrPercent);

View File

@@ -229,9 +229,16 @@ AboutDialog::AboutDialog()
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1"; bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";
// logo // logo — load KX fork logo as PNG scaled to fill the 125px header panel
m_logo_bitmap = ScalableBitmap(this, is_dark ? "OrcaSlicer_about_dark" : "OrcaSlicer_about", 125); {
m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp(), wxDefaultPosition,wxDefaultSize, 0); wxImage img;
img.LoadFile(Slic3r::var("OrcaSlicer_192px.png"), wxBITMAP_TYPE_PNG);
if (img.IsOk())
img = img.Scale(FromDIP(125), FromDIP(125), wxIMAGE_QUALITY_HIGH);
m_logo_bitmap = ScalableBitmap(this, Slic3r::var("OrcaSlicer_192px.png"), wxBITMAP_TYPE_PNG);
m_logo = new wxStaticBitmap(this, wxID_ANY, img.IsOk() ? wxBitmap(img) : m_logo_bitmap.bmp(),
wxDefaultPosition, wxSize(FromDIP(125), FromDIP(125)), 0);
}
m_logo->SetSizer(vesizer); m_logo->SetSizer(vesizer);
panel_versizer->Add(m_logo, 1, wxALL | wxEXPAND, 0); panel_versizer->Add(m_logo, 1, wxALL | wxEXPAND, 0);
@@ -269,6 +276,7 @@ AboutDialog::AboutDialog()
text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.")); text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before."));
text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features.")); text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features."));
text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.")); text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry."));
text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). GNU AGPL-3.0: upstream https://github.com/SoftFever/OrcaSlicer + KX patches https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/release/v2.4"));
text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33)); text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33));
bool is_zh = wxGetApp().app_config->get("language") == "zh_CN"; bool is_zh = wxGetApp().app_config->get("language") == "zh_CN";

View File

@@ -612,9 +612,10 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{ {
curr_tray->remain = -1; curr_tray->remain = -1;
} }
if (tray_it->contains("tray_slot_placeholder")) { // Reset the placeholder flag every update so a slot that
curr_tray->is_slot_placeholder = true; // was empty before but is now loaded does not stay marked
} // as a placeholder (and vice versa).
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
int ams_id_int = 0; int ams_id_int = 0;
int tray_id_int = 0; int tray_id_int = 0;
try try
@@ -638,6 +639,21 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
catch (...) catch (...)
{ {
} }
// An empty slot (placeholder or exist-bit not set) must not
// keep the previously loaded filament's name/type/color from
// an earlier update. The Moonraker/AMS-Lite bridge reports
// empty slots with a placeholder + cleared fields, but the
// tray object is merged (not rebuilt) between updates, so the
// stale values would otherwise survive and the slot would
// render as loaded. Reset them explicitly.
if (curr_tray->is_slot_placeholder || !curr_tray->is_exists)
{
curr_tray->setting_id = "";
curr_tray->m_fila_type = "";
curr_tray->color = "";
curr_tray->sub_brands = "";
curr_tray->cols.clear();
}
if (tray_it->contains("setting_id")) if (tray_it->contains("setting_id"))
{ {
curr_tray->filament_setting_id = (*tray_it)["setting_id"].get<std::string>(); curr_tray->filament_setting_id = (*tray_it)["setting_id"].get<std::string>();

View File

@@ -141,12 +141,15 @@ namespace Slic3r
if (tray_index == exclude_id[i]) if (tray_index == exclude_id[i])
continue; continue;
} }
// push // skip empty / placeholder slots: is_tray_info_ready() returns false when
// color/type are cleared. Including them would add an entry with id=0
// to tray_filaments, causing the distance-map to store tray_id=0 for
// that slot and then map real project filaments to the empty placeholder.
if (!tray->second->is_tray_info_ready())
continue;
FilamentInfo info; FilamentInfo info;
if (tray->second->is_tray_info_ready()) _parse_tray_info(ams_id, tray_id, ams_type, *(tray->second), info);
{
_parse_tray_info(ams_id, tray_id, ams_type, *(tray->second), info);
}
//first: left,nozzle=1,map=1 second: right,nozzle=0,map=2 //first: left,nozzle=1,map=1 second: right,nozzle=0,map=2
bool right_ams_valid = ams->second->GetExtruderId() == 0 && map_opt[MappingOption::USE_RIGHT_AMS]; bool right_ams_valid = ams->second->GetExtruderId() == 0 && map_opt[MappingOption::USE_RIGHT_AMS];

View File

@@ -306,8 +306,12 @@ public:
m_fg_color = StateColor::darkModeColorFor(wxColour("#6B6A6A")); m_fg_color = StateColor::darkModeColorFor(wxColour("#6B6A6A"));
bool dark_mode = m_fg_color != wxColour("#6B6A6A"); bool dark_mode = m_fg_color != wxColour("#6B6A6A");
wxSize sz = m_window->GetClientSize(); wxSize sz = m_window->GetClientSize();
BitmapCache bmp_cache; // KX: load PNG directly — nanosvg cannot render embedded raster images in SVGs
m_logo_bmp = *bmp_cache.load_svg(dark_mode ? "splash_logo_dark" : "splash_logo", sz.GetWidth(), sz.GetHeight()); wxImage img;
img.LoadFile(Slic3r::var("OrcaSlicer_192px.png"), wxBITMAP_TYPE_PNG);
if (img.IsOk())
img = img.Scale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH);
m_logo_bmp = img.IsOk() ? wxBitmap(img) : wxBitmap();
m_window->Bind(wxEVT_PAINT, &SplashScreen::OnPaint, this); m_window->Bind(wxEVT_PAINT, &SplashScreen::OnPaint, this);
m_window->Refresh(); m_window->Refresh();
@@ -3564,6 +3568,11 @@ void GUI_App::switch_printer_agent()
// Auto-switch MachineObject // Auto-switch MachineObject
select_machine(effective_agent_id); select_machine(effective_agent_id);
} else if (effective_agent_id != BBL_PRINTER_AGENT_ID) {
// Same agent type but host may have changed (e.g. user→user preset switch between
// two Moonraker instances on different ports). select_machine() re-resolves the host
// from the current preset and reconnects if the MachineObject differs.
select_machine(effective_agent_id);
} }
} }
@@ -5569,20 +5578,14 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
detect_updater_os_info() detect_updater_os_info()
}; };
const std::string query_string = build_updater_query(query); // KX fork: do not append upstream telemetry query params to Gitea API URL
if (!query_string.empty()) { const std::string query_string;
const bool has_query = version_check_url.find('?') != std::string::npos;
if (!has_query)
version_check_url.push_back('?');
else if (!version_check_url.empty() && version_check_url.back() != '&' && version_check_url.back() != '?')
version_check_url.push_back('&');
version_check_url += query_string;
}
auto http = Http::get(version_check_url); auto http = Http::get(version_check_url);
maybe_attach_updater_signature(http, query_string, version_check_url); // KX fork: skip upstream signature headers — we query Gitea, not orcaslicer.com
// maybe_attach_updater_signature(http, query_string, version_check_url);
http.header("accept", "application/vnd.github.v3+json") http.header("accept", "application/json")
.timeout_connect(5) .timeout_connect(5)
.timeout_max(10) .timeout_max(10)
.on_error([&](std::string body, std::string error, unsigned http_status) { .on_error([&](std::string body, std::string error, unsigned http_status) {

View File

@@ -215,7 +215,7 @@ void MsgDialog::apply_style(long style)
logo->SetBitmap( create_scaled_bitmap(style & wxAPPLY ? "completed" : logo->SetBitmap( create_scaled_bitmap(style & wxAPPLY ? "completed" :
style & wxICON_WARNING ? "exclamation" : // ORCA "exclamation" used for dialogs "obj_warning" used for 16x16 areas style & wxICON_WARNING ? "exclamation" : // ORCA "exclamation" used for dialogs "obj_warning" used for 16x16 areas
style & wxICON_INFORMATION ? "info" : style & wxICON_INFORMATION ? "info" :
style & wxICON_QUESTION ? "question" : "OrcaSlicer", this, 64, style & wxICON_ERROR)); style & wxICON_QUESTION ? "question" : "OrcaSlicer_192px", this, 64, style & wxICON_ERROR));
} }
void MsgDialog::finalize() void MsgDialog::finalize()

View File

@@ -3360,6 +3360,9 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)}); tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)});
tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists}); tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists});
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder}); tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder});
// KX: pass the human-readable sub-brand/filament name through to the
// preset matcher (consumed in PresetBundle, see filament_sub_brands).
tray_config.set_key_value("filament_sub_brands", new ConfigOptionStrings{tray.sub_brands});
std::optional<FilamentBaseInfo> info; std::optional<FilamentBaseInfo> info;
if (wxGetApp().preset_bundle) { if (wxGetApp().preset_bundle) {
info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id); info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id);

View File

@@ -267,6 +267,13 @@ int PresetComboBox::update_ams_color()
auto color_pack = static_cast<ConfigOptionStrings *>(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament) auto color_pack = static_cast<ConfigOptionStrings *>(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament)
auto color_type = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour_type")->clone()); // color type auto color_type = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour_type")->clone()); // color type
// KX: guard against out-of-range filament indices (e.g. AMS slots beyond the
// current project's filament count) and default an empty color type.
if (m_filament_idx >= color_head->values.size()) color_head->values.resize(m_filament_idx + 1);
if (m_filament_idx >= color_pack->values.size()) color_pack->values.resize(m_filament_idx + 1);
if (m_filament_idx >= color_type->values.size()) color_type->values.resize(m_filament_idx + 1);
if (ctype.empty()) ctype = "1";
color_head->values[m_filament_idx] = color; color_head->values[m_filament_idx] = color;
color_type->values[m_filament_idx] = ctype; color_type->values[m_filament_idx] = ctype;
std::string color_str = ""; // Translate multi color info to config storage format std::string color_str = ""; // Translate multi color info to config storage format

View File

@@ -60,7 +60,7 @@ ReleaseNoteDialog::ReleaseNoteDialog(Plater *plater /*= nullptr*/)
m_sizer_body->Add(0, 0, 0, wxLEFT, FromDIP(38)); m_sizer_body->Add(0, 0, 0, wxLEFT, FromDIP(38));
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 70); auto sm = create_scaled_bitmap("OrcaSlicer_192px", nullptr, 70);
auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70))); auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70)));
m_sizer_body->Add(brand, 0, wxALL, 0); m_sizer_body->Add(brand, 0, wxALL, 0);
@@ -127,7 +127,7 @@ UpdatePluginDialog::UpdatePluginDialog(wxWindow* parent /*= nullptr*/)
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 55); auto sm = create_scaled_bitmap("OrcaSlicer_192px", nullptr, 55);
auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(55), FromDIP(55))); auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(55), FromDIP(55)));
wxBoxSizer* m_sizer_right = new wxBoxSizer(wxVERTICAL); wxBoxSizer* m_sizer_right = new wxBoxSizer(wxVERTICAL);
@@ -247,7 +247,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
wxBoxSizer *m_sizer_top = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *m_sizer_top = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *m_sizer_desc = new wxBoxSizer(wxVERTICAL); wxBoxSizer *m_sizer_desc = new wxBoxSizer(wxVERTICAL);
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 64); auto sm = create_scaled_bitmap("OrcaSlicer_192px", nullptr, 64);
m_brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, FromDIP(wxSize(64, 64))); m_brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, FromDIP(wxSize(64, 64)));
m_text_up_info = new Label(this, Label::Head_14, wxEmptyString, LB_AUTO_WRAP); m_text_up_info = new Label(this, Label::Head_14, wxEmptyString, LB_AUTO_WRAP);

View File

@@ -127,8 +127,14 @@ TroubleshootDialog::TroubleshootDialog()
// LEFT SIZER ////////////////////// // LEFT SIZER //////////////////////
// HEADER // HEADER
m_logo = ScalableBitmap(this, is_dark ? "OrcaSlicer_horizontal_dark" : "OrcaSlicer_horizontal_light", 64); // KX: load PNG directly — horizontal SVGs contain nanosvg-incompatible embedded raster
m_header_logo = new wxStaticBitmap(this, wxID_ANY, m_logo.bmp()); {
wxImage img;
img.LoadFile(Slic3r::var("OrcaSlicer_64.png"), wxBITMAP_TYPE_PNG);
if (img.IsOk()) img = img.Scale(FromDIP(64), FromDIP(64), wxIMAGE_QUALITY_HIGH);
m_logo = ScalableBitmap(this, "OrcaSlicer_64.png", 64);
m_header_logo = new wxStaticBitmap(this, wxID_ANY, img.IsOk() ? wxBitmap(img) : m_logo.bmp());
}
auto logo_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(2))); auto logo_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(2)));
logo_line->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#009687"))); logo_line->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#009687")));
auto version = new Label(this, wxString(SoftFever_VERSION), wxALIGN_CENTRE_HORIZONTAL); auto version = new Label(this, wxString(SoftFever_VERSION), wxALIGN_CENTRE_HORIZONTAL);

View File

@@ -111,7 +111,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
m_sizer_body->Add(0, 0, 0, wxLEFT, FromDIP(38)); m_sizer_body->Add(0, 0, 0, wxLEFT, FromDIP(38));
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 70); auto sm = create_scaled_bitmap("OrcaSlicer_192px", nullptr, 70);
auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70))); auto brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70)));
m_sizer_body->Add(brand, 0, wxALL, 0); m_sizer_body->Add(brand, 0, wxALL, 0);

View File

@@ -69,7 +69,12 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag,
auto it = ams->GetTrays().find(std::to_string(i)); auto it = ams->GetTrays().find(std::to_string(i));
Caninfo info; Caninfo info;
// tray is exists // tray is exists
if (it != ams->GetTrays().end() && it->second->is_exists) { // A placeholder slot is explicitly empty (reported by the Moonraker/AMS-Lite
// bridge): treat it as empty regardless of any stale is_exists/filament data
// left over from a previous update, so it renders grey at its position
// instead of showing the previously loaded filament.
if (it != ams->GetTrays().end() && it->second->is_exists
&& !it->second->is_slot_placeholder) {
if (it->second->is_tray_info_ready()) { if (it->second->is_tray_info_ready()) {
info.can_id = it->second->id; info.can_id = it->second->id;
info.ctype = it->second->ctype; info.ctype = it->second->ctype;

View File

@@ -4,6 +4,7 @@
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h" #include "slic3r/GUI/DeviceCore/DevManager.h"
#include "../GUI/DeviceCore/DevStorage.h" #include "../GUI/DeviceCore/DevStorage.h"
#include "../GUI/DeviceCore/DevFirmware.h" #include "../GUI/DeviceCore/DevFirmware.h"
@@ -88,6 +89,166 @@ std::string map_moonraker_state(std::string state)
return "IDLE"; return "IDLE";
} }
std::string normalize_filament_name_for_match(const std::string& input)
{
std::string normalized = input;
boost::trim(normalized);
// Ignore profile suffixes like " @0.4 nozzle" for name matching.
if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) {
normalized = normalized.substr(0, suffix_pos);
}
// Remove non-name symbols (e.g. trademark signs) while preserving separators
// commonly used in filament names.
std::string cleaned;
cleaned.reserve(normalized.size());
for (unsigned char c : normalized) {
if (std::isalnum(c) || c == '-' || c == '+' || c == '/' || std::isspace(c)) {
cleaned.push_back(static_cast<char>(std::toupper(c)));
} else {
cleaned.push_back(' ');
}
}
// Collapse repeated whitespace.
std::string collapsed;
collapsed.reserve(cleaned.size());
bool prev_space = true;
for (unsigned char c : cleaned) {
if (std::isspace(c)) {
if (!prev_space) {
collapsed.push_back(' ');
}
prev_space = true;
} else {
collapsed.push_back(static_cast<char>(c));
prev_space = false;
}
}
boost::trim(collapsed);
return collapsed;
}
bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate)
{
if (wanted == candidate) {
return true;
}
// Allow lane names with trailing color descriptors, e.g.:
// "ELEGOO RAPID PETG GREY" -> "ELEGOO RAPID PETG".
if (!candidate.empty() && boost::starts_with(wanted, candidate + " ")) {
return true;
}
return false;
}
std::vector<std::string> vendor_match_candidates(std::string vendor)
{
std::vector<std::string> candidates;
boost::trim(vendor);
if (vendor.empty()) {
return candidates;
}
candidates.push_back(vendor);
// Also try first token (e.g. "Bambu Lab" -> "Bambu") without hardcoded aliases.
const auto first_space = vendor.find_first_of(" \t");
if (first_space != std::string::npos) {
std::string first = vendor.substr(0, first_space);
boost::trim(first);
if (!first.empty() && !boost::iequals(first, vendor)) {
candidates.push_back(first);
}
}
return candidates;
}
std::string filament_id_by_name(const Slic3r::PresetCollection& filaments,
const std::string& filament_name,
const std::vector<std::string>& vendor_filters = {})
{
if (filament_name.empty()) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name";
return "";
}
const std::string wanted = normalize_filament_name_for_match(filament_name);
std::vector<std::string> normalized_vendor_filters;
normalized_vendor_filters.reserve(vendor_filters.size());
for (const auto& vendor_filter : vendor_filters) {
const std::string normalized_vendor = normalize_filament_name_for_match(vendor_filter);
if (!normalized_vendor.empty()) {
normalized_vendor_filters.push_back(normalized_vendor);
}
}
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name
<< "' normalized='" << wanted << "' vendor_filters=" << normalized_vendor_filters.size();
// Two-pass search: Pass 1 = compatible presets only (ideal), Pass 2 = all visible presets
// (fallback for vendors like eSUN/Eryone that have no printer-specific Kobra X profile).
for (int pass = 1; pass <= 3; ++pass) {
for (size_t i = 0; i < filaments.size(); ++i) {
const auto& preset = filaments.preset(i);
// Pass 1: compatible + visible only
// Pass 2: visible but not necessarily compatible (vendors without printer-specific profile)
// Pass 3: any preset including invisible (vendors not installed as printer)
if (pass <= 2 && !preset.is_visible) {
continue;
}
// User presets (created via "Save As") may have no filament_id yet.
// We still match them by name and use their name as the identifier.
const std::string preset_id = preset.filament_id.empty() ? preset.name : preset.filament_id;
if (pass == 1 && !preset.is_compatible) {
continue; // Pass 1: only compatible presets
}
// Skip the vendor filter for user presets: a preset derived from a
// vendor-specific parent (e.g. "Anycubic PLA Matte @...") resolves
// filament_vendor from the PARENT ("Anycubic"), not from the user
// preset itself ("Tinmorry"), so the filter would wrongly drop it
// and the AMS sync falls back to the vendor preset (Issue #52). The
// strict name match below is discriminating enough for user presets.
if (!preset.is_user() && !normalized_vendor_filters.empty()) {
const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u));
bool vendor_match = false;
for (const auto& vendor_filter : normalized_vendor_filters) {
if (preset_vendor == vendor_filter) {
vendor_match = true;
break;
}
}
if (!vendor_match) {
if (pass == 1) {
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name
<< "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'";
}
continue;
}
}
const std::string candidate = normalize_filament_name_for_match(preset.name);
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare (pass=" << pass << ") preset='" << preset.name
<< "' normalized='" << candidate << "' preset_id='" << preset_id << "'";
if (filament_name_match_relaxed(wanted, candidate)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched (pass=" << pass << ") requested='" << filament_name
<< "' normalized='" << wanted << "' to preset='" << preset.name
<< "' preset_id='" << preset_id << "'";
return preset_id;
}
}
if (pass == 1) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 1 (compatible) found no match for '"
<< filament_name << "', trying pass 2 (all visible)";
} else if (pass == 2) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher pass 2 (all visible) found no match for '"
<< filament_name << "', trying pass 3 (all presets incl. invisible)";
}
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name
<< "' normalized='" << wanted << "'";
return "";
}
} // namespace } // namespace
namespace Slic3r { namespace Slic3r {
@@ -448,7 +609,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
return BAMBU_NETWORK_SUCCESS; return BAMBU_NETWORK_SUCCESS;
} }
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays) void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index)
{ {
// Look up MachineObject via DeviceManager // Look up MachineObject via DeviceManager
@@ -499,6 +660,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
tray_json["tray_info_idx"] = tray->tray_info_idx; tray_json["tray_info_idx"] = tray->tray_info_idx;
tray_json["tray_type"] = tray->tray_type; tray_json["tray_type"] = tray->tray_type;
tray_json["tray_sub_brands"] = tray->tray_sub_brands.empty() ? tray->tray_type : tray->tray_sub_brands;
tray_json["tray_color"] = normalize_color_value(tray->tray_color); tray_json["tray_color"] = normalize_color_value(tray->tray_color);
// Add temperature data if provided // Add temperature data if provided
@@ -530,6 +692,11 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
ams_json["ams"] = ams_array; ams_json["ams"] = ams_array;
ams_json["ams_exist_bits"] = ams_exist_ss.str(); ams_json["ams_exist_bits"] = ams_exist_ss.str();
ams_json["tray_exist_bits"] = tray_exist_ss.str(); ams_json["tray_exist_bits"] = tray_exist_ss.str();
if (active_lane_index >= 0) {
const std::string active_lane = std::to_string(active_lane_index);
ams_json["tray_now"] = active_lane;
ams_json["tray_tar"] = active_lane;
}
// Wrap in the expected structure for ParseV1_0 // Wrap in the expected structure for ParseV1_0
nlohmann::json print_json = nlohmann::json::object(); nlohmann::json print_json = nlohmann::json::object();
@@ -537,6 +704,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
// Call the parser to populate DevFilaSystem // Call the parser to populate DevFilaSystem
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false); DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false);
ExtderSystemParser::ParseV1_0(print_json, obj->GetExtderSystem());
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays"; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
// Set printer_type so update_sync_status() can match it against the preset's printer type. // Set printer_type so update_sync_status() can match it against the preset's printer type.
@@ -570,6 +738,7 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
{ {
std::vector<AmsTrayData> trays; std::vector<AmsTrayData> trays;
int max_lane_index = 0; int max_lane_index = 0;
int active_lane_index = -1;
// Try Moonraker filament data (more generic, supports any filament changer // Try Moonraker filament data (more generic, supports any filament changer
// software that reports lane data to Moonraker like AFC and recent Happy // software that reports lane data to Moonraker like AFC and recent Happy
@@ -578,16 +747,16 @@ bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with " BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with "
<< (max_lane_index + 1) << " lanes"; << (max_lane_index + 1) << " lanes";
int ams_count = (max_lane_index + 4) / 4; int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays); build_ams_payload(ams_count, max_lane_index, trays, active_lane_index);
return true; return true;
} }
// Attempt Happy Hare first (more widely adopted, supports more filament changers) // Attempt Happy Hare first (more widely adopted, supports more filament changers)
if (fetch_hh_filament_info(trays, max_lane_index)) { if (fetch_hh_filament_info(trays, max_lane_index, active_lane_index)) {
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with " BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
<< (max_lane_index + 1) << " gates"; << (max_lane_index + 1) << " gates";
int ams_count = (max_lane_index + 4) / 4; int ams_count = (max_lane_index + 4) / 4;
build_ams_payload(ams_count, max_lane_index, trays); build_ams_payload(ams_count, max_lane_index, trays, active_lane_index);
return true; return true;
} }
@@ -801,13 +970,51 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
tray.slot_index = lane_index; tray.slot_index = lane_index;
tray.tray_color = safe_json_string(lane_obj, "color"); tray.tray_color = safe_json_string(lane_obj, "color");
tray.tray_type = safe_json_string(lane_obj, "material"); tray.tray_type = safe_json_string(lane_obj, "material");
const std::string lane_name = safe_json_string(lane_obj, "name");
const std::string lane_vendor = safe_json_string(lane_obj, "vendor_name");
tray.tray_sub_brands = lane_name;
tray.bed_temp = safe_json_int(lane_obj, "bed_temp"); tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp"); tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp");
tray.has_filament = !tray.tray_type.empty(); tray.has_filament = !tray.tray_type.empty();
auto* bundle = GUI::wxGetApp().preset_bundle; auto* bundle = GUI::wxGetApp().preset_bundle;
tray.tray_info_idx = bundle if (bundle) {
? bundle->filaments.filament_id_by_type(tray.tray_type) const auto vendor_candidates = vendor_match_candidates(lane_vendor);
: map_filament_type_to_generic_id(tray.tray_type); auto match_with_vendor_prefix = [&](const std::string& suffix) -> std::string {
if (suffix.empty()) {
return "";
}
for (const auto& vendor_candidate : vendor_candidates) {
const std::string requested = vendor_candidate + " " + suffix;
std::string match_id = filament_id_by_name(bundle->filaments, requested, vendor_candidates);
if (!match_id.empty()) {
return match_id;
}
}
return "";
};
// Prefer the most specific lane identity first, then broader vendor/material mapping.
tray.tray_info_idx = match_with_vendor_prefix(lane_name);
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = match_with_vendor_prefix(tray.tray_type);
}
if (tray.tray_info_idx.empty() && !lane_name.empty()) {
tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name, vendor_candidates);
}
if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' vendor='" << lane_vendor << "' vendor_candidates=" << vendor_candidates.size()
<< "' name='" << lane_name
<< "' mapped_by='preset_bundle' tray_info_idx='" << tray.tray_info_idx << "'";
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
<< "' index=" << lane_index << " material='" << tray.tray_type
<< "' mapped_by='generic_fallback' tray_info_idx='" << tray.tray_info_idx << "'";
}
max_lane_index = std::max(max_lane_index, lane_index); max_lane_index = std::max(max_lane_index, lane_index);
trays.push_back(tray); trays.push_back(tray);
@@ -822,7 +1029,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
} }
// Fetch filament info from Happy Hare MMU // Fetch filament info from Happy Hare MMU
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index) bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index)
{ {
// Query Happy Hare MMU status // Query Happy Hare MMU status
std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu"); std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
@@ -894,8 +1101,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Get arrays // Get arrays
const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array(); const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array();
const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array(); const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array();
const auto& gate_filament_name = mmu.contains("gate_filament_name") ? mmu["gate_filament_name"] : nlohmann::json::array();
const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array(); const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array();
const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array(); const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array();
const int active_gate = safe_json_int(mmu, "gate");
active_lane_index = active_gate;
std::string active_filament_name;
if (mmu.contains("active_filament") && mmu["active_filament"].is_object()) {
active_filament_name = safe_json_string(mmu["active_filament"], "filament_name");
if (active_filament_name.empty()) {
active_filament_name = safe_json_string(mmu["active_filament"], "material");
}
}
if (!gate_status.is_array() || !gate_material.is_array() || if (!gate_status.is_array() || !gate_material.is_array() ||
!gate_color.is_array() || !gate_temperature.is_array()) { !gate_color.is_array() || !gate_temperature.is_array()) {
@@ -916,8 +1133,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
// Extract gate data // Extract gate data
std::string material = safe_array_string(gate_material, gate_idx); std::string material = safe_array_string(gate_material, gate_idx);
std::string filament_name = safe_array_string(gate_filament_name, gate_idx);
std::string color = safe_array_string(gate_color, gate_idx); std::string color = safe_array_string(gate_color, gate_idx);
int nozzle_temp = safe_array_int(gate_temperature, gate_idx); int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
// For the active gate, prefer active_filament from MMU state.
if (gate_idx == active_gate && !active_filament_name.empty()) {
filament_name = active_filament_name;
}
// Skip if no material type (empty gate) // Skip if no material type (empty gate)
if (material.empty()) { if (material.empty()) {
@@ -927,15 +1149,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
AmsTrayData tray; AmsTrayData tray;
tray.slot_index = gate_idx; tray.slot_index = gate_idx;
tray.tray_type = material; tray.tray_type = material;
tray.tray_sub_brands = filament_name;
tray.tray_color = color; tray.tray_color = color;
tray.nozzle_temp = nozzle_temp; tray.nozzle_temp = nozzle_temp;
tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
tray.has_filament = true; tray.has_filament = true;
auto* bundle = GUI::wxGetApp().preset_bundle; auto* bundle = GUI::wxGetApp().preset_bundle;
tray.tray_info_idx = bundle if (bundle) {
? bundle->filaments.filament_id_by_type(tray.tray_type) tray.tray_info_idx = filament_id_by_name(bundle->filaments, filament_name);
: map_filament_type_to_generic_id(tray.tray_type); if (tray.tray_info_idx.empty()) {
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
}
} else {
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
}
max_lane_index = std::max(max_lane_index, gate_idx); max_lane_index = std::max(max_lane_index, gate_idx);
trays.push_back(tray); trays.push_back(tray);

View File

@@ -92,14 +92,16 @@ protected:
int slot_index = 0; // 0-based slot index int slot_index = 0; // 0-based slot index
bool has_filament = false; bool has_filament = false;
std::string tray_type; // Material type (e.g., "PLA", "ASA") std::string tray_type; // Material type (e.g., "PLA", "ASA")
std::string tray_sub_brands; // Human-readable filament name
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA) std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
std::string tray_info_idx; // Setting ID (optional) std::string tray_info_idx; // Setting ID (optional)
std::string filament_vendor; // Vendor hint from bridge (optional, KX-Bridge sendet das)
int bed_temp = 0; // Optional int bed_temp = 0; // Optional
int nozzle_temp = 0; // Optional int nozzle_temp = 0; // Optional
}; };
// Build ams JSON and call parser // Build ams JSON and call parser
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays); void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index = -1);
// Methods that derived classes may need to override or access // Methods that derived classes may need to override or access
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
@@ -161,7 +163,7 @@ private:
uint64_t generation); uint64_t generation);
// System-specific filament fetch methods // System-specific filament fetch methods
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index); bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index);
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index); bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
// JSON helper methods // JSON helper methods

View File

@@ -387,25 +387,8 @@ void PresetUpdater::priv::sync_version() const
{ {
if (! enabled_version_check) { return; } if (! enabled_version_check) { return; }
#if 0 // Version check is handled by GUI_App::check_new_version_sf() which queries the
Http::get(version_check_url) // Gitea releases API directly. Nothing to do here.
.size_limit(SLIC3R_VERSION_BODY_MAX)
.on_progress([this](Http::Progress, bool &cancel) {
cancel = this->cancel;
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
version_check_url,
http_status,
error);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
boost::trim(body);
parse_version_string(body);
})
.perform_sync();
#endif
} }
// Parses version string obtained in sync_version() and sends events to UI thread. // Parses version string obtained in sync_version() and sends events to UI thread.

117
tools/verify_build.sh Executable file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Prüft ob ein OrcaSlicer-KX Build-Artefakt die erwarteten Strings enthält.
# Verwendung:
# ./tools/verify_build.sh linux <path/to/OrcaSlicer.AppImage>
# ./tools/verify_build.sh windows <path/to/OrcaSlicer-package.zip>
#
# Gibt Exit-Code 0 bei Erfolg, 1 bei Fehler zurück.
set -euo pipefail
PLATFORM="${1:-}"
ARTIFACT="${2:-}"
if [[ -z "$PLATFORM" || -z "$ARTIFACT" ]]; then
echo "Verwendung: $0 <linux|windows> <artefakt-pfad>"
exit 1
fi
if [[ ! -f "$ARTIFACT" ]]; then
echo "FEHLER: Artefakt nicht gefunden: $ARTIFACT"
exit 1
fi
# Versionstring aus version.inc lesen
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
VERSION=$(grep 'set(SoftFever_VERSION' "$REPO_ROOT/version.inc" | head -1 | grep -oP 'SoftFever_VERSION\s+"\K[^"]+')
if [[ -z "$VERSION" ]]; then
echo "FEHLER: Konnte VERSION nicht aus version.inc lesen"
exit 1
fi
echo "=== OrcaSlicer-KX Build-Verifikation ==="
echo "Plattform : $PLATFORM"
echo "Artefakt : $ARTIFACT"
echo "Erwartet : $VERSION"
echo "========================================"
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
CHECKS=(
"$VERSION"
"KX-Bridge"
"tray_sub_brands"
)
BINARY=""
if [[ "$PLATFORM" == "linux" ]]; then
# Bevorzugt die unkomprimierte Binary aus dem build/-Baum prüfen (kein FUSE,
# keine AppImage-Extraktion nötig). Fällt nur zurück auf --appimage-extract,
# wenn keine lose Binary gefunden wird.
# Echte ELF-Binary suchen, nicht die 4K-Shell-Wrapper im build/-Root.
BUILD_ROOT="$(dirname "$ARTIFACT")"
BINARY=""
while IFS= read -r cand; do
[[ -z "$cand" ]] && continue
if file -b "$cand" 2>/dev/null | grep -q "ELF"; then
BINARY="$cand"
break
fi
done < <(find "$BUILD_ROOT" -name "orca-slicer" -type f -not -path "*/squashfs-root/*" 2>/dev/null)
if [[ -z "$BINARY" ]]; then
echo "Keine lose Binary gefunden — extrahiere AppImage..."
chmod +x "$ARTIFACT"
cd "$TMPDIR"
if ! "$ARTIFACT" --appimage-extract bin/orca-slicer >/dev/null 2>&1; then
"$ARTIFACT" --appimage-extract >/dev/null 2>&1 || true
fi
BINARY=$(find "$TMPDIR/squashfs-root" -name "orca-slicer" -type f 2>/dev/null | head -1)
else
echo "Prüfe lose Binary aus build/: $BINARY"
fi
if [[ -z "$BINARY" || ! -f "$BINARY" ]]; then
echo "FEHLER: orca-slicer Binary weder im build/-Baum noch im AppImage gefunden"
exit 1
fi
elif [[ "$PLATFORM" == "windows" ]]; then
echo "Extrahiere Windows ZIP..."
unzip -q "$ARTIFACT" "OrcaSlicer.dll" -d "$TMPDIR" 2>/dev/null || true
BINARY="$TMPDIR/OrcaSlicer.dll"
if [[ ! -f "$BINARY" ]]; then
echo "FEHLER: OrcaSlicer.dll nicht im ZIP gefunden"
exit 1
fi
else
echo "FEHLER: Unbekannte Plattform '$PLATFORM' (erwartet: linux oder windows)"
exit 1
fi
echo "Binary : $BINARY ($(du -sh "$BINARY" | cut -f1))"
echo ""
FAILED=0
for needle in "${CHECKS[@]}"; do
if grep -qaF "$needle" "$BINARY" 2>/dev/null; then
echo " [OK] $needle"
else
echo " [FAIL] $needle — NICHT GEFUNDEN"
FAILED=1
fi
done
echo ""
if [[ $FAILED -eq 0 ]]; then
echo "=== VERIFIKATION ERFOLGREICH ==="
exit 0
else
echo "=== VERIFIKATION FEHLGESCHLAGEN — Upload abgebrochen ==="
exit 1
fi

View File

@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
if(NOT DEFINED BBL_INTERNAL_TESTING) if(NOT DEFINED BBL_INTERNAL_TESTING)
set(BBL_INTERNAL_TESTING "0") set(BBL_INTERNAL_TESTING "0")
endif() endif()
set(SoftFever_VERSION "2.4.1") set(SoftFever_VERSION "2.4.1-kx1")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
SoftFever_VERSION_MATCH ${SoftFever_VERSION}) SoftFever_VERSION_MATCH ${SoftFever_VERSION})
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1}) set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})