Compare commits
291 Commits
v2.4.1-kx1
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f96072d55 | ||
|
|
5c5a22c44f | ||
|
|
15b094540f | ||
|
|
2d7a3165d4 | ||
|
|
a827f9358b | ||
|
|
7595d76158 | ||
|
|
bef6de4863 | ||
|
|
08bee0f34c | ||
|
|
0794e23b43 | ||
|
|
5659a83c44 | ||
|
|
bcc3f1eb44 | ||
|
|
19998ae487 | ||
|
|
d8190d474e | ||
|
|
e75ae87295 | ||
|
|
acc3db79f9 | ||
|
|
f64cb21f73 | ||
|
|
ad4ac71be4 | ||
|
|
05cb4ecf48 | ||
|
|
f5b71de932 | ||
| 8d48947b8e | |||
|
|
514cd26bca | ||
|
|
c02df26328 | ||
|
|
8500fcdcca | ||
|
|
c95887a93f | ||
|
|
d3405cf461 | ||
|
|
a524ec7dde | ||
|
|
55d0108cd3 | ||
|
|
d91972e576 | ||
|
|
ccf28f394b | ||
|
|
6888f9d806 | ||
|
|
07a87b4129 | ||
|
|
23555f8df2 | ||
|
|
116658de29 | ||
|
|
a8f2fea5e2 | ||
|
|
7c80ea1fc1 | ||
|
|
b5d46a3e7c | ||
|
|
fa0a08d8f3 | ||
|
|
a38d9ccb21 | ||
|
|
92ea1ce57e | ||
|
|
2c029402d7 | ||
|
|
f8dc94ab7f | ||
|
|
76e16d8db4 | ||
|
|
feeea3e651 | ||
|
|
128068cb65 | ||
|
|
2f13fd82b1 | ||
|
|
dafc223d1a | ||
|
|
1ff08f8614 | ||
|
|
b371c68fbf | ||
|
|
7dc088a8b4 | ||
|
|
6a50902150 | ||
|
|
a2e188294e | ||
|
|
6aa2686bfe | ||
|
|
e8444445e8 | ||
|
|
4606acea73 | ||
|
|
3fd02ef899 | ||
|
|
83d3ec97f5 | ||
|
|
27d5ab6acb | ||
|
|
036ffba307 | ||
|
|
b4a532622f | ||
|
|
83c287d6f4 | ||
|
|
b3764c8454 | ||
|
|
8cc4040f5d | ||
|
|
4084c0a8d8 | ||
|
|
0493de8084 | ||
|
|
b1e3f265b0 | ||
|
|
3a7f52398c | ||
|
|
3249a2ccee | ||
|
|
5ecc99c321 | ||
|
|
b8cc3c077d | ||
|
|
055c3cde9a | ||
|
|
f72d1fc6ac | ||
|
|
cac5f7b562 | ||
|
|
e9a4aae68a | ||
|
|
a58b964e52 | ||
|
|
f474af2348 | ||
|
|
c6802c73dc | ||
|
|
b5685b34f8 | ||
|
|
29525e652f | ||
|
|
94accf7a94 | ||
|
|
5540971e7f | ||
|
|
f6c56bf2bb | ||
|
|
1a6bb9d652 | ||
|
|
25aeaec0e5 | ||
|
|
2738e5d474 | ||
|
|
e9fe58b348 | ||
|
|
63bc97e9ee | ||
|
|
42d79dffbd | ||
|
|
2bc65951b3 | ||
|
|
64ad2eb2e8 | ||
|
|
3503f0613f | ||
|
|
9aed9a4e6a | ||
|
|
ffd4732dd6 | ||
|
|
d6323f3924 | ||
|
|
07d1a85464 | ||
|
|
5accfa52ea | ||
|
|
138406a483 | ||
|
|
8cdb5b0272 | ||
|
|
6401c87c3a | ||
|
|
00555687a7 | ||
|
|
e1884ea0b4 | ||
|
|
df8f231168 | ||
|
|
28e51216f9 | ||
|
|
9e010689aa | ||
|
|
e55c775c18 | ||
|
|
6cd8fec603 | ||
|
|
c831e78177 | ||
|
|
8b7da48ae2 | ||
|
|
5ca2f24732 | ||
|
|
afbcab395f | ||
|
|
29abec47b1 | ||
|
|
fdf97b19dd | ||
|
|
dcf79e332c | ||
|
|
646f558560 | ||
|
|
1aae13641a | ||
|
|
012062a189 | ||
|
|
f41a18bcb9 | ||
|
|
11cacfabab | ||
|
|
1fcd74ac77 | ||
|
|
68460338d4 | ||
|
|
1f3fe6d6a1 | ||
|
|
7b209131d4 | ||
|
|
2d762be590 | ||
|
|
a75b8b69e3 | ||
|
|
2ea7239849 | ||
|
|
ef4ba55320 | ||
|
|
01c915be65 | ||
|
|
c519c107e7 | ||
|
|
15498a6942 | ||
|
|
10c5d14be4 | ||
|
|
f6f5123501 | ||
|
|
1ef400bbf1 | ||
|
|
36658ee005 | ||
|
|
cb298296eb | ||
|
|
5d02c827e3 | ||
|
|
b9c73d7a41 | ||
|
|
bf81236bc5 | ||
|
|
21c64595a6 | ||
|
|
3cfd8c656b | ||
|
|
8e9a0cbfdc | ||
|
|
bd28850f70 | ||
|
|
9e27af3032 | ||
|
|
24ab0b81a2 | ||
|
|
a32bcfda35 | ||
|
|
d9e74f22f6 | ||
|
|
359693fa89 | ||
|
|
8668114350 | ||
|
|
054ae4f903 | ||
|
|
06f3511b2d | ||
|
|
679ed9b8d1 | ||
|
|
63f5828425 | ||
|
|
c6035a7c91 | ||
|
|
14e61db633 | ||
|
|
31969e8ae0 | ||
|
|
06e7a6d611 | ||
|
|
64d24c755f | ||
|
|
4e06e9d30b | ||
|
|
2500828c58 | ||
|
|
b938ed363d | ||
|
|
3f9638ce86 | ||
|
|
60fed30069 | ||
|
|
0bdefc5d8b | ||
|
|
26f84bdc1b | ||
|
|
b86100aa14 | ||
|
|
6646aba3cf | ||
|
|
b6e70a886d | ||
|
|
29be1d396e | ||
|
|
0adef6ec19 | ||
|
|
11a6b694d8 | ||
|
|
ade01c68d3 | ||
|
|
49424433df | ||
|
|
87e3897a7f | ||
|
|
e77452e2e3 | ||
|
|
532de0f04a | ||
|
|
a3e979ab8a | ||
|
|
bf7e4a1f6d | ||
|
|
6af1dbf3c0 | ||
|
|
bf7341fcc3 | ||
|
|
5841df8c19 | ||
|
|
1844a83e8e | ||
|
|
fa68f7653e | ||
|
|
591e7b940f | ||
|
|
d37697d492 | ||
|
|
1dd0532026 | ||
|
|
6a3e29b21c | ||
|
|
396cca82bb | ||
|
|
c26d06125d | ||
|
|
372496d10f | ||
|
|
b60e841cc7 | ||
|
|
47f8d15e0b | ||
|
|
a1b37455f3 | ||
|
|
a01ec22be4 | ||
|
|
a0b522628a | ||
|
|
53164d063b | ||
|
|
281f81d1c7 | ||
|
|
277e0f83c2 | ||
|
|
1692778cda | ||
|
|
0bad0d4fd4 | ||
|
|
e8c305e4cd | ||
|
|
8a550bb513 | ||
|
|
5775c81167 | ||
|
|
9da925fa52 | ||
|
|
cf129f9ded | ||
|
|
aea4282633 | ||
|
|
7066faa9d7 | ||
|
|
f473f3e7f1 | ||
|
|
ec0d6406e9 | ||
|
|
b3622f5f9e | ||
|
|
10c338fe38 | ||
|
|
a7ba31adea | ||
|
|
0e724d1801 | ||
|
|
be63704600 | ||
|
|
d7c66ac575 | ||
|
|
fdbbf5f135 | ||
|
|
8c7a3b55bb | ||
|
|
643a39976d | ||
|
|
122196730f | ||
|
|
9468e8b335 | ||
|
|
1de6aec92d | ||
|
|
2dc5c697bc | ||
|
|
f27801b4ef | ||
|
|
4b33dae806 | ||
|
|
aecead1f8c | ||
|
|
7667cd8c96 | ||
|
|
5823c83691 | ||
|
|
d7603ed561 | ||
|
|
6952711491 | ||
|
|
0aed75461f | ||
|
|
346d40ba24 | ||
|
|
57cd99b950 | ||
|
|
6ab2e96c0a | ||
|
|
fd67e4a421 | ||
|
|
9dfda06442 | ||
|
|
1b5a300cb3 | ||
|
|
e93ce996d3 | ||
|
|
380cc025cd | ||
|
|
3cc3cba664 | ||
|
|
d532c447c2 | ||
|
|
b42f3666a4 | ||
|
|
ed260a8055 | ||
|
|
8f898cc6ea | ||
|
|
de81550309 | ||
|
|
d115824d6a | ||
|
|
aee6db8e27 | ||
|
|
0664e47a70 | ||
|
|
30dee1911c | ||
|
|
4d03044159 | ||
|
|
684747a8b9 | ||
|
|
dd2c0f9b29 | ||
|
|
2343e8ff26 | ||
|
|
76d5e99483 | ||
|
|
5f840fdba4 | ||
|
|
6960168c07 | ||
|
|
7580532c6a | ||
|
|
f8a90ec89c | ||
|
|
303ec41cb2 | ||
|
|
4ac7f4380e | ||
|
|
7f4d3097a5 | ||
|
|
42af381c2a | ||
|
|
86094ffd9b | ||
|
|
0c39ef1c43 | ||
|
|
e2372ac5d5 | ||
|
|
33a391185c | ||
|
|
0927e9fda1 | ||
|
|
c0801f5773 | ||
|
|
ef40a49431 | ||
|
|
6e0a3486a2 | ||
|
|
770358653a | ||
|
|
c1d5c06744 | ||
|
|
988718a091 | ||
|
|
81b0e61845 | ||
|
|
61a1c232c5 | ||
|
|
d18043291f | ||
|
|
711ac44cb1 | ||
|
|
1134d45530 | ||
|
|
5d93d333dd | ||
|
|
9923664212 | ||
|
|
7899f15b58 | ||
|
|
e70831f5b5 | ||
|
|
c7390ef970 | ||
|
|
a9dc1858b1 | ||
|
|
6df4c7f16a | ||
|
|
f8338a498a | ||
|
|
548aa1ac98 | ||
|
|
f41f2136c2 | ||
|
|
7cb92e2502 | ||
|
|
7530e344d7 | ||
|
|
f719d895f5 | ||
|
|
195b5868ac | ||
|
|
0fbc85c0a0 | ||
|
|
6222bd0f23 | ||
|
|
33325df15d |
9
.devcontainer/devcontainer-lock.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/desktop-lite:1": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "ghcr.io/devcontainers/features/desktop-lite@sha256:d8649bc8ca8e50a52fbee29d281739ea05dc1022905b185434d6bbe9afc8f221",
|
||||
"integrity": "sha256:d8649bc8ca8e50a52fbee29d281739ea05dc1022905b185434d6bbe9afc8f221"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
.github/workflows/assign.yml
vendored
@@ -1,11 +1,6 @@
|
||||
name: Assign Issue
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
assign:
|
||||
|
||||
5
.github/workflows/auto-close-duplicates.yml
vendored
@@ -1,9 +1,6 @@
|
||||
name: Auto-close duplicate issues
|
||||
description: Auto-closes issues that are duplicates of existing issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 9 * * *"
|
||||
workflow_dispatch:
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
auto-close-duplicates:
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
name: Backfill Duplicate Comments
|
||||
description: Triggers duplicate detection for old issues that don't have duplicate comments
|
||||
|
||||
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'
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
backfill-duplicate-comments:
|
||||
|
||||
478
.github/workflows/build_all.yml
vendored
@@ -1,269 +1,209 @@
|
||||
name: Build all
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
- belt-printer
|
||||
paths:
|
||||
- 'deps/**'
|
||||
- 'src/**'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'version.inc'
|
||||
- 'localization/**'
|
||||
- 'resources/**'
|
||||
- ".github/workflows/build_*.yml"
|
||||
- 'scripts/flatpak/**'
|
||||
- 'scripts/msix/**'
|
||||
- 'tests/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
- 'deps/**'
|
||||
- 'deps_src/**'
|
||||
- 'src/**'
|
||||
- '**/CMakeLists.txt'
|
||||
- 'version.inc'
|
||||
- ".github/workflows/build_*.yml"
|
||||
- 'build_linux.sh'
|
||||
- 'build_release_vs.bat'
|
||||
- 'build_release_vs2022.bat'
|
||||
- 'build_release_macos.sh'
|
||||
- 'scripts/flatpak/**'
|
||||
- 'scripts/msix/**'
|
||||
- 'tests/**'
|
||||
|
||||
|
||||
schedule:
|
||||
- cron: '0 17 * * *' # run once a day at 1 AM Singapore time (UTC+8)
|
||||
|
||||
workflow_dispatch: # allows for manual dispatch
|
||||
inputs:
|
||||
build-deps-only:
|
||||
description: 'Only build dependencies (bypasses caching)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
build_linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Build both arches on every event (PRs included), through the same
|
||||
# build_check_cache -> build_deps -> build_orca chain (the AppImage).
|
||||
# aarch64 always uses the GitHub-hosted arm runner (there is no arm
|
||||
# self-hosted server). amd64's empty arch is load-bearing: it keeps the
|
||||
# historical 'linux-clang' deps cache key and the unsuffixed asset names.
|
||||
matrix:
|
||||
include:
|
||||
- arch: ""
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
|
||||
- arch: "aarch64"
|
||||
os: ubuntu-24.04-arm
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
arch: ${{ matrix.arch }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
secrets: inherit
|
||||
build_windows:
|
||||
name: Build Windows ${{ matrix.arch }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
os: windows-latest
|
||||
- arch: arm64
|
||||
os: windows-11-arm
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
# Self-hosted runner is x64-only; ARM64 always uses the GitHub-hosted runner.
|
||||
os: ${{ (matrix.arch == 'x64' && vars.SELF_HOSTED) && 'orca-win-server' || matrix.os }}
|
||||
arch: ${{ matrix.arch }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
build_macos_arch:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- arm64
|
||||
- x86_64
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
|
||||
arch: ${{ matrix.arch }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
build_macos_universal:
|
||||
name: Build macOS Universal
|
||||
needs: build_macos_arch
|
||||
if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_orca.yml
|
||||
with:
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
|
||||
arch: universal
|
||||
macos-combine-only: true
|
||||
secrets: inherit
|
||||
unit_tests:
|
||||
name: Unit Tests
|
||||
# Tests are built on the aarch64 leg by default (faster GitHub arm runner),
|
||||
# so run them there; self-hosted builds them on the amd64 server instead.
|
||||
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }}
|
||||
needs: build_linux
|
||||
if: ${{ !cancelled() && success() }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
scripts
|
||||
tests
|
||||
- name: Apt-Install Dependencies
|
||||
if: ${{ !vars.SELF_HOSTED }}
|
||||
uses: ./.github/actions/apt-install-deps
|
||||
- name: Restore Test Artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ github.sha }}-tests
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
|
||||
useLocalCache: true # <--= Use the local cache (default is 'false').
|
||||
useCloudCache: true
|
||||
- name: Unpackage and Run Unit Tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
tar -xvf build_tests.tar
|
||||
scripts/run_unit_tests.sh
|
||||
- name: Upload Test Logs
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: unit-test-logs
|
||||
path: build/tests/**/*.log
|
||||
- name: Publish Test Results
|
||||
if: always()
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
with:
|
||||
files: "ctest_results.xml"
|
||||
- name: Delete Test Artifact
|
||||
if: success()
|
||||
uses: geekyeggo/delete-artifact@v6
|
||||
with:
|
||||
name: ${{ github.sha }}-tests
|
||||
flatpak:
|
||||
name: "Flatpak"
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
|
||||
options: --privileged
|
||||
volumes:
|
||||
- /usr/local/lib/android:/usr/local/lib/android
|
||||
- /usr/share/dotnet:/usr/share/dotnet
|
||||
- /opt/ghc:/opt/ghc1
|
||||
- /usr/local/share/boost:/usr/local/share/boost1
|
||||
- /opt/hostedtoolcache:/opt/hostedtoolcache1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant:
|
||||
- arch: x86_64
|
||||
runner: ubuntu-24.04
|
||||
- arch: aarch64
|
||||
runner: ubuntu-24.04-arm
|
||||
# Don't run scheduled builds on forks; skip entirely on self-hosted runners
|
||||
if: ${{ !cancelled() && !vars.SELF_HOSTED && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
env:
|
||||
date:
|
||||
ver:
|
||||
ver_pure:
|
||||
# Belt-printer nightlies share the main nightly release but carry a `_belt`
|
||||
# suffix so they never overwrite the main assets.
|
||||
nightly_suffix: ${{ github.ref == 'refs/heads/belt-printer' && '_belt' || '' }}
|
||||
steps:
|
||||
- name: "Remove unneeded stuff to free disk space"
|
||||
run:
|
||||
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get the version and date
|
||||
run: |
|
||||
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
|
||||
name: Build all
|
||||
|
||||
on: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
build_linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Build both arches on every event (PRs included), through the same
|
||||
# build_check_cache -> build_deps -> build_orca chain (the AppImage).
|
||||
# aarch64 always uses the GitHub-hosted arm runner (there is no arm
|
||||
# self-hosted server). amd64's empty arch is load-bearing: it keeps the
|
||||
# historical 'linux-clang' deps cache key and the unsuffixed asset names.
|
||||
matrix:
|
||||
include:
|
||||
- arch: ""
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
|
||||
- arch: "aarch64"
|
||||
os: ubuntu-24.04-arm
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
arch: ${{ matrix.arch }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
secrets: inherit
|
||||
build_windows:
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
build_macos_arch:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- arm64
|
||||
- x86_64
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_check_cache.yml
|
||||
with:
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
|
||||
arch: ${{ matrix.arch }}
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
build_macos_universal:
|
||||
name: Build macOS Universal
|
||||
needs: build_macos_arch
|
||||
if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_orca.yml
|
||||
with:
|
||||
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
|
||||
arch: universal
|
||||
macos-combine-only: true
|
||||
secrets: inherit
|
||||
unit_tests:
|
||||
name: Unit Tests
|
||||
# Tests are built on the aarch64 leg by default (faster GitHub arm runner),
|
||||
# so run them there; self-hosted builds them on the amd64 server instead.
|
||||
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }}
|
||||
needs: build_linux
|
||||
if: ${{ !cancelled() && success() }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
scripts
|
||||
tests
|
||||
- name: Apt-Install Dependencies
|
||||
if: ${{ !vars.SELF_HOSTED }}
|
||||
uses: ./.github/actions/apt-install-deps
|
||||
- name: Restore Test Artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ github.sha }}-tests
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
|
||||
useLocalCache: true # <--= Use the local cache (default is 'false').
|
||||
useCloudCache: true
|
||||
- name: Unpackage and Run Unit Tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
tar -xvf build_tests.tar
|
||||
scripts/run_unit_tests.sh
|
||||
- name: Upload Test Logs
|
||||
uses: actions/upload-artifact@v7
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: unit-test-logs
|
||||
path: build/tests/**/*.log
|
||||
- name: Publish Test Results
|
||||
if: always()
|
||||
uses: EnricoMi/publish-unit-test-result-action@v2
|
||||
with:
|
||||
files: "ctest_results.xml"
|
||||
- name: Delete Test Artifact
|
||||
if: success()
|
||||
uses: geekyeggo/delete-artifact@v6
|
||||
with:
|
||||
name: ${{ github.sha }}-tests
|
||||
flatpak:
|
||||
name: "Flatpak"
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
|
||||
options: --privileged
|
||||
volumes:
|
||||
- /usr/local/lib/android:/usr/local/lib/android
|
||||
- /usr/share/dotnet:/usr/share/dotnet
|
||||
- /opt/ghc:/opt/ghc1
|
||||
- /usr/local/share/boost:/usr/local/share/boost1
|
||||
- /opt/hostedtoolcache:/opt/hostedtoolcache1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant:
|
||||
- arch: x86_64
|
||||
runner: ubuntu-24.04
|
||||
- arch: aarch64
|
||||
runner: ubuntu-24.04-arm
|
||||
# Don't run scheduled builds on forks; skip entirely on self-hosted runners
|
||||
if: ${{ !cancelled() && !vars.SELF_HOSTED && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
env:
|
||||
date:
|
||||
ver:
|
||||
ver_pure:
|
||||
steps:
|
||||
- name: "Remove unneeded stuff to free disk space"
|
||||
run:
|
||||
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get the version and date
|
||||
run: |
|
||||
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'
|
||||
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_${{ 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
|
||||
|
||||
119
.github/workflows/build_check_cache.yml
vendored
@@ -1,67 +1,52 @@
|
||||
name: Check Cache
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
required: true
|
||||
type: string
|
||||
arch:
|
||||
required: false
|
||||
type: string
|
||||
build-deps-only:
|
||||
required: false
|
||||
type: boolean
|
||||
force-build:
|
||||
required: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
check_cache: # determines if there is a cache and outputs variables used in caching process
|
||||
name: Check Cache
|
||||
runs-on: ${{ inputs.os }}
|
||||
outputs:
|
||||
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: 'false'
|
||||
|
||||
- name: set outputs
|
||||
id: set_outputs
|
||||
env:
|
||||
# Keep macOS/Windows cache keys architecture-specific. amd64 Linux passes
|
||||
# no arch (key stays 'linux-clang', preserving the existing cache);
|
||||
# aarch64 gets its own 'linux-clang-aarch64' key.
|
||||
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.
|
||||
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
|
||||
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
||||
run: |
|
||||
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
|
||||
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
|
||||
|
||||
- name: load cache
|
||||
id: cache_deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
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
|
||||
name: Check Cache
|
||||
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
check_cache: # determines if there is a cache and outputs variables used in caching process
|
||||
name: Check Cache
|
||||
runs-on: ${{ inputs.os }}
|
||||
outputs:
|
||||
cache-key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
cache-path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: 'false'
|
||||
|
||||
- name: set outputs
|
||||
id: set_outputs
|
||||
env:
|
||||
# Keep macOS/Linux cache keys architecture-specific. amd64 Linux passes
|
||||
# no arch (key stays 'linux-clang', preserving the existing cache);
|
||||
# aarch64 gets its own 'linux-clang-aarch64' key.
|
||||
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && 'windows' || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
|
||||
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || '/OrcaSlicer_dep' }}
|
||||
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
||||
run: |
|
||||
echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }}
|
||||
echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }}
|
||||
|
||||
- name: load cache
|
||||
id: cache_deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.set_outputs.outputs.cache-path }}
|
||||
key: ${{ steps.set_outputs.outputs.cache-key }}
|
||||
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
|
||||
|
||||
25
.github/workflows/build_deps.yml
vendored
@@ -1,27 +1,4 @@
|
||||
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
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
build_deps:
|
||||
|
||||
20
.github/workflows/build_orca.yml
vendored
@@ -1,22 +1,4 @@
|
||||
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
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
build_orca:
|
||||
|
||||
8
.github/workflows/check_locale.yml
vendored
@@ -1,12 +1,6 @@
|
||||
name: Check locale
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'localization/**'
|
||||
- ".github/workflows/check_locale.yml"
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
check_translation:
|
||||
|
||||
117
.github/workflows/check_profiles.yml
vendored
@@ -1,18 +1,5 @@
|
||||
name: Check profiles
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'resources/profiles/**'
|
||||
- ".github/workflows/check_profiles.yml"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
on: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -41,7 +28,7 @@ jobs:
|
||||
curl -L -o OrcaSlicer_profile_validator https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/OrcaSlicer_profile_validator_Linux_Ubuntu2404_nightly
|
||||
chmod +x ./OrcaSlicer_profile_validator
|
||||
|
||||
# validate profiles
|
||||
# Validate all system profiles.
|
||||
- name: validate system profiles
|
||||
id: validate_system
|
||||
continue-on-error: true
|
||||
@@ -63,11 +50,101 @@ jobs:
|
||||
continue-on-error: true
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set +e
|
||||
curl -LJO https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds/orca_custom_preset_tests.zip
|
||||
unzip -q ./orca_custom_preset_tests.zip -d ${{ github.workspace }}/resources/profiles
|
||||
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_custom.log
|
||||
exit ${PIPESTATUS[0]}
|
||||
fixtures_dir="${{ runner.temp }}/profile-fixtures"
|
||||
output_dir="${{ runner.temp }}/custom-preset-validation"
|
||||
combined_log="${{ runner.temp }}/validate_custom.log"
|
||||
summary="${output_dir}/summary.md"
|
||||
release_url="https://github.com/OrcaSlicer/OrcaSlicer-profile-validator/releases/download/fixture-archive"
|
||||
|
||||
rm -rf "${fixtures_dir}" "${output_dir}"
|
||||
mkdir -p "${fixtures_dir}" "${output_dir}"
|
||||
|
||||
curl -fsSL -o "${fixtures_dir}/manifest.json" "${release_url}/manifest.json"
|
||||
|
||||
MANIFEST_PATH="${fixtures_dir}/manifest.json" python3 <<'PY' > "${fixtures_dir}/fixtures.tsv"
|
||||
import json
|
||||
import os
|
||||
|
||||
with open(os.environ["MANIFEST_PATH"], encoding="utf-8") as fh:
|
||||
manifest = json.load(fh)
|
||||
|
||||
if isinstance(manifest, dict):
|
||||
entries = manifest.get("fixtures", [])
|
||||
else:
|
||||
entries = manifest
|
||||
|
||||
for entry in entries:
|
||||
version = entry.get("version", "")
|
||||
asset = entry.get("asset", "")
|
||||
sha256 = entry.get("asset_sha256", "")
|
||||
if not version or not asset:
|
||||
continue
|
||||
print(f"{version}\t{asset}\t{sha256}")
|
||||
PY
|
||||
|
||||
if [ ! -s "${fixtures_dir}/fixtures.tsv" ]; then
|
||||
echo "No custom preset fixtures found in ${release_url}/manifest.json" | tee "${combined_log}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "## Custom Preset Fixture Validation"
|
||||
echo ""
|
||||
echo "| Version | Status | Log |"
|
||||
echo "| --- | --- | --- |"
|
||||
} > "${summary}"
|
||||
|
||||
status=0
|
||||
failed_logs=()
|
||||
|
||||
while IFS=$'\t' read -r version asset expected_sha256; do
|
||||
fixture_zip="${fixtures_dir}/${asset}"
|
||||
asset_url_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${asset}")"
|
||||
profile_tree="${output_dir}/profiles-${version}"
|
||||
log_path="${output_dir}/${version}.log"
|
||||
|
||||
curl -fsSL -o "${fixture_zip}" "${release_url}/${asset_url_name}"
|
||||
|
||||
if [ -n "${expected_sha256}" ] && [ "${expected_sha256}" != "<sha256>" ]; then
|
||||
echo "${expected_sha256} ${fixture_zip}" | sha256sum -c -
|
||||
fi
|
||||
|
||||
rm -rf "${profile_tree}"
|
||||
mkdir -p "${profile_tree}"
|
||||
cp -a "${{ github.workspace }}/resources/profiles/." "${profile_tree}/"
|
||||
rm -rf "${profile_tree}/user"
|
||||
unzip -q "${fixture_zip}" -d "${profile_tree}"
|
||||
|
||||
set +e
|
||||
./OrcaSlicer_profile_validator -p "${profile_tree}" -l 2 > "${log_path}" 2>&1
|
||||
result=$?
|
||||
set -e
|
||||
|
||||
if [ "${result}" -eq 0 ]; then
|
||||
echo "| ${version} | PASS | ${version}.log |" >> "${summary}"
|
||||
else
|
||||
echo "| ${version} | FAIL | ${version}.log |" >> "${summary}"
|
||||
failed_logs+=("${log_path}")
|
||||
status=1
|
||||
fi
|
||||
done < "${fixtures_dir}/fixtures.tsv"
|
||||
|
||||
{
|
||||
cat "${summary}"
|
||||
if [ "${#failed_logs[@]}" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "## Failed Fixture Logs"
|
||||
for log_path in "${failed_logs[@]}"; do
|
||||
echo ""
|
||||
echo "### $(basename "${log_path}" .log)"
|
||||
echo '```'
|
||||
head -c 12000 "${log_path}" || echo "No output captured"
|
||||
echo '```'
|
||||
done
|
||||
fi
|
||||
} | tee "${combined_log}"
|
||||
|
||||
exit "${status}"
|
||||
|
||||
- name: Prepare PR number for comment workflow
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
|
||||
6
.github/workflows/check_profiles_comment.yml
vendored
@@ -2,11 +2,7 @@ name: Post profile check comment
|
||||
|
||||
# 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.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Check profiles"]
|
||||
types:
|
||||
- completed
|
||||
on: {}
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
10
.github/workflows/dedupe-issues.yml
vendored
@@ -1,14 +1,6 @@
|
||||
name: Orca Issue Dedupe
|
||||
description: Automatically dedupe GitHub issues using AI
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to process for duplicate detection'
|
||||
required: true
|
||||
type: string
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
dedupe-issues:
|
||||
|
||||
5
.github/workflows/doxygen-docs.yml
vendored
@@ -1,9 +1,6 @@
|
||||
name: Generate Doxygen Documentation
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Every Monday at midnight UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
on: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
|
||||
9
.github/workflows/pr-label-bot.yml
vendored
@@ -1,13 +1,6 @@
|
||||
name: PR Label Bot
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
on: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
12
.github/workflows/publish_release.yml
vendored
@@ -9,17 +9,7 @@ name: Publish to draft release
|
||||
# `RELEASE_PUBLISHER` repo variable (set to "SoftFever"). To allow someone else,
|
||||
# change that variable: gh variable set RELEASE_PUBLISHER --body "<login>".
|
||||
|
||||
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
|
||||
on: {}
|
||||
|
||||
permissions:
|
||||
contents: write # upload release assets
|
||||
|
||||
13
.github/workflows/shellcheck.yml
vendored
@@ -1,15 +1,4 @@
|
||||
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
|
||||
on: {}
|
||||
|
||||
name: "Shellcheck"
|
||||
permissions: {}
|
||||
|
||||
5
.github/workflows/update-translation.yml
vendored
@@ -1,8 +1,5 @@
|
||||
name: Update Translation Catalog
|
||||
on:
|
||||
# schedule:
|
||||
# - cron: 0 0 * * 1
|
||||
workflow_dispatch:
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
update_translation:
|
||||
|
||||
5
.github/workflows/winget_updater.yml
vendored
@@ -1,7 +1,6 @@
|
||||
name: Publish to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [ released ]
|
||||
on: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
|
||||
2
.gitignore
vendored
@@ -49,3 +49,5 @@ internal_docs/
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.3mf
|
||||
*.gcode
|
||||
|
||||
@@ -85,6 +85,7 @@ else ()
|
||||
endif ()
|
||||
|
||||
find_package(Git)
|
||||
set(GIT_COMMIT_HASH "0000000")
|
||||
if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "")
|
||||
message(STATUS "Specified git commit hash: $ENV{git_commit_hash}")
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -99,7 +100,6 @@ if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "")
|
||||
# No .git directory (e.g., Flatpak sandbox) — truncate directly
|
||||
string(SUBSTRING "$ENV{git_commit_hash}" 0 7 GIT_COMMIT_HASH)
|
||||
endif()
|
||||
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
# Check current Git commit hash
|
||||
execute_process(
|
||||
@@ -108,7 +108,6 @@ elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{SLIC3R_STATIC})
|
||||
@@ -434,8 +433,8 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
|
||||
|
||||
if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15)
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-Wno-error=enum-constexpr-conversion HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
|
||||
if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
|
||||
check_cxx_compiler_flag("-Werror=unknown-warning-option -Wno-error=enum-constexpr-conversion" HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV_STRICT)
|
||||
if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV_STRICT)
|
||||
add_compile_options(-Wno-error=enum-constexpr-conversion)
|
||||
endif()
|
||||
endif()
|
||||
@@ -510,10 +509,10 @@ include_directories(SYSTEM ${LIBDIR_BIN}/dev-utils/platform)
|
||||
include_directories(SYSTEM ${LIBDIR}/libigl)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS -DNOMINMAX)
|
||||
if(MSVC)
|
||||
# BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking.
|
||||
add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8 )
|
||||
add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8 -DBOOST_REGEX_NO_WIN32_LOCALE)
|
||||
# Force the source code encoding to UTF-8. See OrcaSlicer GH pull request #5583
|
||||
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
@@ -578,7 +577,7 @@ endif()
|
||||
# boost::process was introduced first in version 1.64.0,
|
||||
# boost::beast::detail::base64 was introduced first in version 1.66.0
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
cmake_policy(SET CMP0167 OLD)
|
||||
endif()
|
||||
set(Boost_NO_SYSTEM_PATHS TRUE)
|
||||
find_package(Boost 1.83.0 REQUIRED COMPONENTS system filesystem thread log log_setup locale regex chrono atomic date_time iostreams program_options nowide)
|
||||
@@ -956,7 +955,7 @@ set (CPACK_PACKAGE_INSTALL_REGISTRY_KEY "OrcaSlicer")
|
||||
set (CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
|
||||
set (CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
# set (CPACK_NSIS_MODIFY_PATH "ON")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer-ImageMap")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
|
||||
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
|
||||
|
||||
|
||||
250
README.md
@@ -1,237 +1,59 @@
|
||||
<div align="center">
|
||||
# OrcaSlicer-KX
|
||||
|
||||
<picture>
|
||||
<img alt="OrcaSlicer logo" src="resources/images/OrcaSlicer.png" width="15%" height="15%">
|
||||
</picture>
|
||||
**OrcaSlicer mit KX-Bridge-Patches für den Anycubic Kobra X**
|
||||
|
||||
<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).
|
||||
|
||||
[](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
|
||||
[](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.2-kx1** (Basis: OrcaSlicer 2.4.2 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>
|
||||
<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>
|
||||
- **Moonraker-Bridge:** mehrstufiges Filament-Matching (Name, Sub-Brand, Vendor-Fallback) mit Vendor-Filter-Skip
|
||||
- **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
|
||||
- **Kobra X Bett-Temp-Fix:** `bed_temperature_formula` auf `by_first_filament` gesetzt — G9111-Makro heizt jetzt auf die korrekte Temperatur des gedruckten Filaments statt auf den Höchstwert aller Projekt-Filamente
|
||||
|
||||
#### 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;">
|
||||
<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>
|
||||
📄 [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)
|
||||
|
||||
</div>
|
||||
---
|
||||
|
||||
# Main features
|
||||
## Branch-Struktur
|
||||
|
||||
- **[Advanced Calibration Tools](https://www.orcaslicer.com/wiki/calibration_guide)**
|
||||
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)**
|
||||
Adjust outer wall spacing and apply scarf seams to enhance print accuracy.
|
||||
- **[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/).
|
||||
| Branch | Version | Rolle |
|
||||
|--------|---------|-------|
|
||||
| `stable` / `kx-v2.4` | 2.4.1-kx1 | **aktuell stabil** |
|
||||
| `kx-v2.3` | 2.3.2-kx4 | eingefroren (Altstand) |
|
||||
|
||||
# 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)**
|
||||
- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/how_to_wiki)**
|
||||
KX-Bridge selbst liegt unter [viewit/KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release).
|
||||
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)**
|
||||
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users.
|
||||
|
||||
## 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
|
||||

|
||||
- Step 2: in `System Settings` -> `Privacy & Security`, click `Open Anyway`:
|
||||

|
||||
</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.
|
||||
Diese Builds sind Ableitungen von [OrcaSlicer](https://github.com/SoftFever/OrcaSlicer), lizenziert unter **GNU AGPL-3.0**.
|
||||
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.
|
||||
|
||||
@@ -237,6 +237,7 @@ function build_slicer() {
|
||||
resources_path=$(readlink ./OrcaSlicer.app/Contents/Resources)
|
||||
rm ./OrcaSlicer.app/Contents/Resources
|
||||
cp -R "$resources_path" ./OrcaSlicer.app/Contents/Resources
|
||||
plutil -replace CFBundleIconFile -string "images/OrcaSlicer.icns" ./OrcaSlicer.app/Contents/Info.plist
|
||||
# delete .DS_Store file
|
||||
find ./OrcaSlicer.app/ -name '.DS_Store' -delete
|
||||
|
||||
|
||||
34
cmake/GenerateSoftFeverVersion.cmake
Normal file
@@ -0,0 +1,34 @@
|
||||
if(NOT DEFINED INPUT)
|
||||
message(FATAL_ERROR "INPUT is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED OUTPUT)
|
||||
message(FATAL_ERROR "OUTPUT is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED GIT_COMMIT_HASH)
|
||||
set(GIT_COMMIT_HASH "0000000")
|
||||
endif()
|
||||
|
||||
file(STRINGS "${INPUT}" SOFTFEVER_VERSION LIMIT_COUNT 1)
|
||||
string(STRIP "${SOFTFEVER_VERSION}" SOFTFEVER_VERSION)
|
||||
|
||||
set(CONTENT [=[
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const char* softfever_version()
|
||||
{
|
||||
return "@SOFTFEVER_VERSION@";
|
||||
}
|
||||
|
||||
const char* git_commit_hash()
|
||||
{
|
||||
return "@GIT_COMMIT_HASH@";
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
]=])
|
||||
string(CONFIGURE "${CONTENT}" CONTENT @ONLY)
|
||||
file(WRITE "${OUTPUT}" "${CONTENT}")
|
||||
@@ -30,6 +30,10 @@ add_subdirectory(md4c)
|
||||
add_subdirectory(mdns)
|
||||
add_subdirectory(miniz)
|
||||
add_subdirectory(minilzo)
|
||||
add_subdirectory(pigment-painter)
|
||||
add_subdirectory(prusa-fdm-mixer)
|
||||
add_subdirectory(colorsolver)
|
||||
add_subdirectory(qhull)
|
||||
add_subdirectory(qoi)
|
||||
add_subdirectory(semver) # Semver static library
|
||||
add_subdirectory(tinygltf)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX)
|
||||
project(Clipper2 VERSION 2.0.1 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@@ -19,6 +19,7 @@ set(CLIPPER2_INC
|
||||
Clipper2Lib/include/clipper2/clipper.minkowski.h
|
||||
Clipper2Lib/include/clipper2/clipper.offset.h
|
||||
Clipper2Lib/include/clipper2/clipper.rectclip.h
|
||||
Clipper2Lib/include/clipper2/clipper.triangulation.h
|
||||
Clipper2Lib/include/clipper2/clipper2_z.hpp
|
||||
)
|
||||
|
||||
@@ -26,6 +27,7 @@ set(CLIPPER2_SRC
|
||||
Clipper2Lib/src/clipper.engine.cpp
|
||||
Clipper2Lib/src/clipper.offset.cpp
|
||||
Clipper2Lib/src/clipper.rectclip.cpp
|
||||
Clipper2Lib/src/clipper.triangulation.cpp
|
||||
Clipper2Lib/src/clipper2_z.cpp
|
||||
)
|
||||
|
||||
@@ -51,4 +53,3 @@ set_target_properties(Clipper2 PROPERTIES FOLDER Libraries
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
PUBLIC_HEADER "${CLIPPER2_INC}"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 12 May 2024 *
|
||||
* Date : 12 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Core Clipper Library structures and functions *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -141,7 +141,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
}
|
||||
|
||||
explicit Point() : x(0), y(0), z(0) {};
|
||||
explicit Point() : x(0), y(0), z(0) {}
|
||||
|
||||
template <typename T2>
|
||||
Point(const T2 x_, const T2 y_, const z_type z_ = 0)
|
||||
@@ -193,7 +193,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
}
|
||||
|
||||
explicit Point() : x(0), y(0) {};
|
||||
explicit Point() : x(0), y(0) {}
|
||||
|
||||
template <typename T2>
|
||||
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
|
||||
@@ -251,6 +251,20 @@ namespace Clipper2Lib {
|
||||
template <typename T>
|
||||
using Paths = std::vector<Path<T>>;
|
||||
|
||||
template <typename T, typename T2=T>
|
||||
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
|
||||
{
|
||||
poly.emplace_back(p);
|
||||
return poly;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
|
||||
{
|
||||
polys.emplace_back(p);
|
||||
return polys;
|
||||
}
|
||||
|
||||
using Path64 = Path<int64_t>;
|
||||
using PathD = Path<double>;
|
||||
using Paths64 = std::vector< Path64>;
|
||||
@@ -356,13 +370,13 @@ namespace Clipper2Lib {
|
||||
bottom *= scale;
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return bottom <= top || right <= left; };
|
||||
bool IsEmpty() const { return bottom <= top || right <= left; }
|
||||
|
||||
bool Intersects(const Rect<T>& rec) const
|
||||
{
|
||||
return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) &&
|
||||
((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom));
|
||||
};
|
||||
}
|
||||
|
||||
bool operator==(const Rect<T>& other) const {
|
||||
return left == other.left && right == other.right &&
|
||||
@@ -688,29 +702,28 @@ namespace Clipper2Lib {
|
||||
return (x > 0) - (x < 0);
|
||||
}
|
||||
|
||||
struct MultiplyUInt64Result
|
||||
struct UInt128Struct
|
||||
{
|
||||
const uint64_t result = 0;
|
||||
const uint64_t carry = 0;
|
||||
const uint64_t lo = 0;
|
||||
const uint64_t hi = 0;
|
||||
|
||||
bool operator==(const MultiplyUInt64Result& other) const
|
||||
bool operator==(const UInt128Struct& other) const
|
||||
{
|
||||
return result == other.result && carry == other.carry;
|
||||
};
|
||||
return lo == other.lo && hi == other.hi;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835
|
||||
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
|
||||
{
|
||||
// note to self - lamba expressions follow
|
||||
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
|
||||
const auto hi = [](uint64_t x) { return x >> 32; };
|
||||
|
||||
const uint64_t x1 = lo(a) * lo(b);
|
||||
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
|
||||
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
|
||||
const uint64_t result = lo(x3) << 32 | lo(x1);
|
||||
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3);
|
||||
|
||||
return { result, carry };
|
||||
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
|
||||
}
|
||||
|
||||
// returns true if (and only if) a * b == c * d
|
||||
@@ -727,14 +740,50 @@ namespace Clipper2Lib {
|
||||
const auto abs_c = static_cast<uint64_t>(std::abs(c));
|
||||
const auto abs_d = static_cast<uint64_t>(std::abs(d));
|
||||
|
||||
const auto abs_ab = Multiply(abs_a, abs_b);
|
||||
const auto abs_cd = Multiply(abs_c, abs_d);
|
||||
const auto ab = MultiplyUInt64(abs_a, abs_b);
|
||||
const auto cd = MultiplyUInt64(abs_c, abs_d);
|
||||
|
||||
// nb: it's important to differentiate 0 values here from other values
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
return abs_ab == abs_cd && sign_ab == sign_cd;
|
||||
return ab == cd && sign_ab == sign_cd;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3)
|
||||
{
|
||||
const auto a = pt2.x - pt1.x;
|
||||
const auto b = pt3.y - pt2.y;
|
||||
const auto c = pt2.y - pt1.y;
|
||||
const auto d = pt3.x - pt2.x;
|
||||
|
||||
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
|
||||
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
|
||||
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
|
||||
if (ab > cd) return 1;
|
||||
else if (ab < cd) return -1;
|
||||
else return 0;
|
||||
#else
|
||||
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
|
||||
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
|
||||
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
if (sign_ab == sign_cd)
|
||||
{
|
||||
int result;
|
||||
if (ab.hi == cd.hi)
|
||||
{
|
||||
if (ab.lo == cd.lo) return 0;
|
||||
result = (ab.lo > cd.lo) ? 1 : -1;
|
||||
}
|
||||
else result = (ab.hi > cd.hi) ? 1 : -1;
|
||||
return (sign_ab > 0) ? result : -result;
|
||||
}
|
||||
return (sign_ab > sign_cd) ? 1 : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -838,14 +887,18 @@ namespace Clipper2Lib {
|
||||
return Area<T>(poly) >= 0;
|
||||
}
|
||||
|
||||
#if CLIPPER2_HI_PRECISION
|
||||
// GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
|
||||
// be constrained to seg1. However, it's possible that 'ip' won't be inside
|
||||
// seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
|
||||
|
||||
#if defined(CLIPPER2_HI_PRECISION) && CLIPPER2_HI_PRECISION
|
||||
// caution: this will compromise performance
|
||||
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
|
||||
// See also CPP/BenchMark/GetIntersectPtBenchmark.cpp
|
||||
#define CC_MIN(x,y) ((x)>(y)?(y):(x))
|
||||
#define CC_MAX(x,y) ((x)<(y)?(y):(x))
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
|
||||
@@ -891,11 +944,14 @@ namespace Clipper2Lib {
|
||||
ip.x = originx + static_cast<T>(hitx);
|
||||
ip.y = originy + static_cast<T>(hity);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
@@ -913,7 +969,10 @@ namespace Clipper2Lib {
|
||||
{
|
||||
ip.x = static_cast<T>(ln1a.x + t * dx1);
|
||||
ip.y = static_cast<T>(ln1a.y + t * dy1);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -949,21 +1008,44 @@ namespace Clipper2Lib {
|
||||
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
|
||||
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
|
||||
{
|
||||
double dy1 = static_cast<double>(seg1b.y - seg1a.y);
|
||||
double dx1 = static_cast<double>(seg1b.x - seg1a.x);
|
||||
double dy2 = static_cast<double>(seg2b.y - seg2a.y);
|
||||
double dx2 = static_cast<double>(seg2b.x - seg2a.x);
|
||||
double cp = dy1 * dx2 - dy2 * dx1;
|
||||
if (cp == 0) return false; // ie parallel segments
|
||||
|
||||
if (inclusive)
|
||||
{
|
||||
double res1 = CrossProduct(seg1a, seg2a, seg2b);
|
||||
double res2 = CrossProduct(seg1b, seg2a, seg2b);
|
||||
if (res1 * res2 > 0) return false;
|
||||
double res3 = CrossProduct(seg2a, seg1a, seg1b);
|
||||
double res4 = CrossProduct(seg2b, seg1a, seg1b);
|
||||
if (res3 * res4 > 0) return false;
|
||||
return (res1 || res2 || res3 || res4); // ensures not collinear
|
||||
//result **includes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return true;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t > cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t < cp) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return true;
|
||||
if (t > 0) return (cp > 0 && t <= cp);
|
||||
else return (cp < 0 && t >= cp); // true when t less neg. than cp
|
||||
}
|
||||
else {
|
||||
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
|
||||
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
|
||||
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
|
||||
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
|
||||
else
|
||||
{
|
||||
//result **excludes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return false;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t >= cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t <= cp ) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return false;
|
||||
if (t > 0) return (cp > 0 && t < cp);
|
||||
else return (cp < 0 && t > cp); // true when t less neg. than cp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,7 +1133,7 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
@@ -1065,7 +1147,7 @@ namespace Clipper2Lib {
|
||||
if (curr == cend) curr = cbegin;
|
||||
if (curr == cbegin) prev = cend - 1;
|
||||
else prev = curr - 1;
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Clipper2Lib {
|
||||
if (splits) delete splits;
|
||||
// nb: don't delete the split pointers
|
||||
// as these are owned by ClipperBase's outrec_list_
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
@@ -160,7 +160,7 @@ namespace Clipper2Lib {
|
||||
struct HorzJoin {
|
||||
OutPt* op1 = nullptr;
|
||||
OutPt* op2 = nullptr;
|
||||
HorzJoin() {};
|
||||
HorzJoin() {}
|
||||
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
|
||||
};
|
||||
|
||||
@@ -280,11 +280,11 @@ namespace Clipper2Lib {
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ClipperBase();
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
|
||||
bool PreserveCollinear() const { return preserve_collinear_;};
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; };
|
||||
bool ReverseSolution() const { return reverse_solution_; };
|
||||
int ErrorCode() const { return error_code_; }
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; }
|
||||
bool PreserveCollinear() const { return preserve_collinear_;}
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; }
|
||||
bool ReverseSolution() const { return reverse_solution_; }
|
||||
void Clear();
|
||||
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
|
||||
#ifdef USINGZ
|
||||
@@ -304,7 +304,7 @@ namespace Clipper2Lib {
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() {};
|
||||
virtual ~PolyPath() {}
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
PolyPath(const PolyPath&) = delete;
|
||||
PolyPath& operator=(const PolyPath&) = delete;
|
||||
@@ -352,7 +352,7 @@ namespace Clipper2Lib {
|
||||
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
|
||||
|
||||
~PolyPath64() {
|
||||
~PolyPath64() override {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Clipper2Lib {
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const Path64& Polygon() const { return polygon_; };
|
||||
const Path64& Polygon() const { return polygon_; }
|
||||
|
||||
double Area() const
|
||||
{
|
||||
@@ -418,7 +418,7 @@ namespace Clipper2Lib {
|
||||
polygon_ = path;
|
||||
}
|
||||
|
||||
~PolyPathD() {
|
||||
~PolyPathD() override {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ namespace Clipper2Lib {
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const PathD& Polygon() const { return polygon_; };
|
||||
const PathD& Polygon() const { return polygon_; }
|
||||
|
||||
double Area() const
|
||||
{
|
||||
@@ -548,7 +548,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; }
|
||||
|
||||
void ZCB(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)
|
||||
@@ -564,7 +564,7 @@ namespace Clipper2Lib {
|
||||
PointD e2t = PointD(e2top) * invScale_;
|
||||
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
|
||||
pt.z = tmp.z; // only update 'z'
|
||||
};
|
||||
}
|
||||
|
||||
void CheckCallback()
|
||||
{
|
||||
|
||||
@@ -116,6 +116,7 @@ the four vertices that define the two segments that are intersecting.
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -816,6 +817,24 @@ EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath6
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 Triangulate64(const CPaths64 paths, bool use_delaunay)
|
||||
{
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsFromPathsT(sol);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD TriangulateD(const CPathsD paths, int decimal_precison, bool use_delaunay)
|
||||
{
|
||||
if (decimal_precison < -8 || decimal_precison > 8) return nullptr;
|
||||
const double scale = std::pow(10, decimal_precison);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsDFromPaths64(sol, 1 / scale);
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
|
||||
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 27 April 2024 *
|
||||
* Date : 5 March 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : This module provides a simple interface to the Clipper Library *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -13,8 +13,9 @@
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -137,7 +138,7 @@
|
||||
JoinType jt, EndType et, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0)
|
||||
{
|
||||
if (!delta) return paths;
|
||||
if (delta==0.0) return paths;
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
clip_offset.AddPaths(paths, jt, et);
|
||||
Paths64 solution;
|
||||
@@ -151,10 +152,10 @@
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (!delta) return paths;
|
||||
if (delta==0.0) return paths;
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance * scale);
|
||||
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
|
||||
if (error_code) return PathsD();
|
||||
Paths64 solution;
|
||||
@@ -355,6 +356,29 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
} // end details namespace
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
|
||||
@@ -615,29 +639,6 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> SimplifyPath(const Path<T> &path,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
@@ -669,13 +670,13 @@
|
||||
start = curr;
|
||||
do
|
||||
{
|
||||
curr = GetNext(curr, high, flags);
|
||||
curr = details::GetNext(curr, high, flags);
|
||||
} while (curr != start && distSqr[curr] > epsSqr);
|
||||
if (curr == start) break;
|
||||
}
|
||||
|
||||
prior = GetPrior(curr, high, flags);
|
||||
next = GetNext(curr, high, flags);
|
||||
prior = details::GetPrior(curr, high, flags);
|
||||
next = details::GetNext(curr, high, flags);
|
||||
if (next == prior) break;
|
||||
|
||||
// flag for removal the smaller of adjacent 'distances'
|
||||
@@ -684,14 +685,14 @@
|
||||
prior2 = prior;
|
||||
prior = curr;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
}
|
||||
else
|
||||
prior2 = GetPrior(prior, high, flags);
|
||||
prior2 = details::GetPrior(prior, high, flags);
|
||||
|
||||
flags[curr] = true;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
|
||||
if (isClosedPath || ((curr != high) && (curr != 0)))
|
||||
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
|
||||
@@ -716,6 +717,35 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
|
||||
{
|
||||
// precondition: paths must not intersect, except for
|
||||
// transient (and presumed 'micro') path intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
for (const Point<T>& pt : path1)
|
||||
{
|
||||
switch (PointInPolygon(pt, path2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pip != PointInPolygonResult::IsInside) return false;
|
||||
// result is likely true but check midpoint
|
||||
Point<T> mp1 = GetBounds(path1).MidPoint();
|
||||
return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void RDP(const Path<T> path, std::size_t begin,
|
||||
std::size_t end, double epsSqrd, std::vector<bool>& flags)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Clipper2Lib {
|
||||
quad.emplace_back(tmp[i][h]);
|
||||
quad.emplace_back(tmp[i][j]);
|
||||
quad.emplace_back(tmp[g][j]);
|
||||
};
|
||||
}
|
||||
if (!IsPositive(quad))
|
||||
std::reverse(quad.begin(), quad.end());
|
||||
result.emplace_back(std::move(quad));
|
||||
|
||||
@@ -39,7 +39,7 @@ private:
|
||||
class Group {
|
||||
public:
|
||||
Paths64 paths_in;
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
bool is_reversed = false;
|
||||
JoinType join_type;
|
||||
EndType end_type;
|
||||
@@ -92,14 +92,14 @@ public:
|
||||
bool reverse_solution = false) :
|
||||
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
|
||||
preserve_collinear_(preserve_collinear),
|
||||
reverse_solution_(reverse_solution) { };
|
||||
reverse_solution_(reverse_solution) { }
|
||||
|
||||
~ClipperOffset() { Clear(); };
|
||||
~ClipperOffset() { Clear(); }
|
||||
|
||||
int ErrorCode() const { return error_code_; };
|
||||
int ErrorCode() const { return error_code_; }
|
||||
void AddPath(const Path64& path, JoinType jt_, EndType et_);
|
||||
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
|
||||
void Clear() { groups_.clear(); norms.clear(); };
|
||||
void Clear() { groups_.clear(); norms.clear(); }
|
||||
|
||||
void Execute(double delta, Paths64& sols_64);
|
||||
void Execute(double delta, PolyTree64& polytree);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Clipper2Lib {
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
public:
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {}
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 6 December 2025 *
|
||||
* Release : BETA RELEASE *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Delaunay Triangulation *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_TRIANGULATION_H
|
||||
#define CLIPPER_TRIANGULATION_H
|
||||
|
||||
#include <stack>
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
enum class TriangulateResult { success, fail, no_polygons, paths_intersect };
|
||||
|
||||
// Triangulate - this function will not accept intesecting paths
|
||||
TriangulateResult Triangulate(const Paths64& pp, Paths64& solution, bool useDelaunay = true);
|
||||
TriangulateResult Triangulate(const PathsD& pp, int decPlaces, PathsD& solution, bool useDelaunay = true);
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
#endif // CLIPPER_TRIANGULATION_H
|
||||
@@ -1,6 +1,6 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
constexpr auto CLIPPER2_VERSION = "2.0.1";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Date : 21 February 2026 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2026 *
|
||||
* Purpose : This is the main polygon clipping module *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -482,8 +482,7 @@ namespace Clipper2Lib {
|
||||
inline void SetOwner(OutRec* outrec, OutRec* new_owner)
|
||||
{
|
||||
//precondition1: new_owner is never null
|
||||
while (new_owner->owner && !new_owner->owner->pts)
|
||||
new_owner->owner = new_owner->owner->owner;
|
||||
new_owner->owner = GetRealOutRec(new_owner->owner);
|
||||
OutRec* tmp = new_owner;
|
||||
while (tmp && tmp != outrec) tmp = tmp->owner;
|
||||
if (tmp) new_owner->owner = outrec->owner;
|
||||
@@ -536,9 +535,9 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
is_above = !is_above;
|
||||
op2 = op2->next;
|
||||
@@ -546,9 +545,9 @@ namespace Clipper2Lib {
|
||||
|
||||
if (is_above != starting_above)
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
if (val == 0) return PointInPolygonResult::IsOutside;
|
||||
@@ -578,30 +577,31 @@ namespace Clipper2Lib {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool Path1InsidePath2(OutPt* op1, OutPt* op2)
|
||||
inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)
|
||||
{
|
||||
// we need to make some accommodation for rounding errors
|
||||
// so we won't jump if the first vertex is found outside
|
||||
PointInPolygonResult result;
|
||||
int outside_cnt = 0;
|
||||
// this function accommodates rounding errors that
|
||||
// can cause path micro intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
OutPt* op = op1;
|
||||
do
|
||||
{
|
||||
result = PointInOpPolygon(op->pt, op2);
|
||||
if (result == PointInPolygonResult::IsOutside) ++outside_cnt;
|
||||
else if (result == PointInPolygonResult::IsInside) --outside_cnt;
|
||||
do {
|
||||
switch (PointInOpPolygon(op->pt, op2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
op = op->next;
|
||||
} while (op != op1 && std::abs(outside_cnt) < 2);
|
||||
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0);
|
||||
// since path1's location is still equivocal, check its midpoint
|
||||
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
|
||||
Path64 path2 = GetCleanPath(op2);
|
||||
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
|
||||
} while (op != op1);
|
||||
// result unclear, so try again using cleaned paths
|
||||
return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void AddLocMin(LocalMinimaList& list,
|
||||
Vertex& vert, PathType polytype, bool is_open)
|
||||
{
|
||||
@@ -621,7 +621,7 @@ namespace Clipper2Lib {
|
||||
{return a + path.size(); });
|
||||
if (total_vertex_count == 0) return;
|
||||
|
||||
Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;
|
||||
Vertex* allVertices = new Vertex[total_vertex_count], * v = allVertices;
|
||||
for (const Path64& path : paths)
|
||||
{
|
||||
//for each path create a circular double linked list of vertices
|
||||
@@ -713,7 +713,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
} // end processing current path
|
||||
|
||||
vertexLists.emplace_back(vertices);
|
||||
vertexLists.emplace_back(allVertices);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1126,21 +1126,19 @@ namespace Clipper2Lib {
|
||||
return newcomer.curr_x > resident.curr_x;
|
||||
|
||||
//get the turning direction a1.top, a2.bot, a2.top
|
||||
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top);
|
||||
if (d != 0) return d < 0;
|
||||
int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
|
||||
if (i != 0) return i < 0;
|
||||
|
||||
//edges must be collinear to get here
|
||||
//for starting open paths, place them according to
|
||||
//the direction they're about to turn
|
||||
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
resident.top, NextVertex(resident)->pt) <= 0;
|
||||
return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
|
||||
}
|
||||
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
newcomer.top, NextVertex(newcomer)->pt) >= 0;
|
||||
return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
|
||||
}
|
||||
|
||||
int64_t y = newcomer.bot.y;
|
||||
@@ -1155,7 +1153,7 @@ namespace Clipper2Lib {
|
||||
resident.bot, resident.top)) return true;
|
||||
else
|
||||
//compare turning direction of the alternate bound
|
||||
return (CrossProduct(PrevPrevVertex(resident)->pt,
|
||||
return (CrossProductSign(PrevPrevVertex(resident)->pt,
|
||||
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
|
||||
}
|
||||
|
||||
@@ -1565,7 +1563,7 @@ namespace Clipper2Lib {
|
||||
FixSelfIntersects(outrec);
|
||||
}
|
||||
|
||||
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
|
||||
void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
|
||||
{
|
||||
// splitOp.prev -> splitOp &&
|
||||
// splitOp.next -> splitOp.next.next are intersecting
|
||||
@@ -1574,7 +1572,7 @@ namespace Clipper2Lib {
|
||||
outrec->pts = prevOp;
|
||||
|
||||
Point64 ip;
|
||||
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
|
||||
GetLineIntersectPt(prevOp->pt, splitOp->pt,
|
||||
splitOp->next->pt, nextNextOp->pt, ip);
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -1630,7 +1628,7 @@ namespace Clipper2Lib {
|
||||
|
||||
if (using_polytree_)
|
||||
{
|
||||
if (Path1InsidePath2(prevOp, newOp))
|
||||
if (Path2ContainsPath1(prevOp, newOp))
|
||||
{
|
||||
newOr->splits = new OutRecList();
|
||||
newOr->splits->emplace_back(outrec);
|
||||
@@ -1652,10 +1650,10 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::FixSelfIntersects(OutRec* outrec)
|
||||
{
|
||||
OutPt* op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
return; // because triangles can't self-intersect
|
||||
for (; ; )
|
||||
{
|
||||
// triangles can't self-intersect
|
||||
if (op2->prev == op2->next->next) break;
|
||||
if (SegmentsIntersect(op2->prev->pt,
|
||||
op2->pt, op2->next->pt, op2->next->next->pt))
|
||||
{
|
||||
@@ -1664,6 +1662,8 @@ namespace Clipper2Lib {
|
||||
DoSplitOp(outrec, op2);
|
||||
if (!outrec->pts) break;
|
||||
op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
break; // again, because triangles can't self-intersect
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -2112,10 +2112,9 @@ namespace Clipper2Lib {
|
||||
e->prev_in_sel = e->prev_in_ael;
|
||||
e->next_in_sel = e->next_in_ael;
|
||||
e->jump = e->next_in_sel;
|
||||
if (e->join_with == JoinWith::Left)
|
||||
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
|
||||
else
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
// it is safe to ignore 'joined' edges here because
|
||||
// if necessary they will be split in IntersectEdges()
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
e = e->next_in_ael;
|
||||
}
|
||||
}
|
||||
@@ -2262,15 +2261,14 @@ namespace Clipper2Lib {
|
||||
|
||||
void MoveSplits(OutRec* fromOr, OutRec* toOr)
|
||||
{
|
||||
if (!fromOr->splits) return;
|
||||
if (!toOr->splits) toOr->splits = new OutRecList();
|
||||
OutRecList::iterator orIter = fromOr->splits->begin();
|
||||
for (; orIter != fromOr->splits->end(); ++orIter)
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
if (toOr != *orIter) // #987
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
fromOr->splits->clear();
|
||||
}
|
||||
|
||||
|
||||
void ClipperBase::ProcessHorzJoins()
|
||||
{
|
||||
for (const HorzJoin& j : horz_join_list_)
|
||||
@@ -2300,7 +2298,7 @@ namespace Clipper2Lib {
|
||||
|
||||
if (using_polytree_) //#498, #520, #584, D#576, #618
|
||||
{
|
||||
if (Path1InsidePath2(or1->pts, or2->pts))
|
||||
if (Path2ContainsPath1(or1->pts, or2->pts))
|
||||
{
|
||||
//swap or1's & or2's pts
|
||||
OutPt* tmp = or1->pts;
|
||||
@@ -2311,7 +2309,7 @@ namespace Clipper2Lib {
|
||||
//or2 is now inside or1
|
||||
or2->owner = or1;
|
||||
}
|
||||
else if (Path1InsidePath2(or2->pts, or1->pts))
|
||||
else if (Path2ContainsPath1(or2->pts, or1->pts))
|
||||
{
|
||||
or2->owner = or1;
|
||||
}
|
||||
@@ -2324,13 +2322,14 @@ namespace Clipper2Lib {
|
||||
else
|
||||
or2->owner = or1;
|
||||
}
|
||||
else
|
||||
else // joining, not splitting
|
||||
{
|
||||
or2->pts = nullptr;
|
||||
if (using_polytree_)
|
||||
{
|
||||
SetOwner(or2, or1);
|
||||
MoveSplits(or2, or1); //#618
|
||||
if (or2->splits)
|
||||
MoveSplits(or2, or1); //#618
|
||||
}
|
||||
else
|
||||
or2->owner = or1;
|
||||
@@ -2350,7 +2349,7 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
|
||||
{
|
||||
Point64 ip;
|
||||
if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
if (!GetLineIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
ip = Point64(e1.curr_x, top_y); //parallel edges
|
||||
|
||||
//rounding errors can occasionally place the calculated intersection
|
||||
@@ -2934,22 +2933,28 @@ namespace Clipper2Lib {
|
||||
|
||||
bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
|
||||
{
|
||||
for (auto split : *splits)
|
||||
// nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
|
||||
for (size_t idx = 0; idx < splits->size(); ++idx)
|
||||
{
|
||||
OutRec* split = (*splits)[idx];
|
||||
if (!split->pts && split->splits &&
|
||||
CheckSplitOwner(outrec, split->splits)) return true; //#942
|
||||
split = GetRealOutRec(split);
|
||||
if(!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
if (!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
split->recursive_split = outrec; // prevent infinite loops
|
||||
|
||||
if (split->splits && CheckSplitOwner(outrec, split->splits))
|
||||
return true;
|
||||
else if (CheckBounds(split) &&
|
||||
IsValidOwner(outrec, split) &&
|
||||
split->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, split->pts))
|
||||
{
|
||||
outrec->owner = split; //found in split
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
|
||||
!Path2ContainsPath1(outrec->pts, split->pts)) continue;
|
||||
|
||||
if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
|
||||
split->owner = outrec->owner;
|
||||
|
||||
outrec->owner = split;
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2960,13 +2965,12 @@ namespace Clipper2Lib {
|
||||
// post-condition: if a valid path, outrec will have a polypath
|
||||
|
||||
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
|
||||
|
||||
while (outrec->owner)
|
||||
{
|
||||
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
|
||||
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
|
||||
outrec->owner->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break;
|
||||
Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;
|
||||
outrec->owner = outrec->owner->owner;
|
||||
}
|
||||
|
||||
@@ -3029,6 +3033,7 @@ namespace Clipper2Lib {
|
||||
{
|
||||
OutRec* outrec = outrec_list_[i];
|
||||
if (!outrec || !outrec->pts) continue;
|
||||
|
||||
if (outrec->is_open)
|
||||
{
|
||||
Path64 path;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 22 January 2025 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
@@ -37,22 +37,28 @@ const double arc_const = 0.002; // <-- 1/500
|
||||
// Miscellaneous methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
||||
void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
|
||||
{
|
||||
std::optional<size_t> result;
|
||||
idx.reset();
|
||||
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
double a = MAX_DBL;
|
||||
for (const Point64& pt : paths[i])
|
||||
{
|
||||
if ((pt.y < botPt.y) ||
|
||||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
||||
result = i;
|
||||
if (a == MAX_DBL)
|
||||
{
|
||||
a = Area(paths[i]);
|
||||
if (a == 0) break; // invalid closed path, so break from inner loop
|
||||
is_neg_area = a < 0;
|
||||
}
|
||||
idx = i;
|
||||
botPt.x = pt.x;
|
||||
botPt.y = pt.y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y)
|
||||
@@ -145,15 +151,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
|
||||
|
||||
if (end_type == EndType::Polygon)
|
||||
{
|
||||
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
||||
bool is_neg_area;
|
||||
GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area);
|
||||
// the lowermost path must be an outer path, so if its orientation is negative,
|
||||
// then flag the whole group is 'reversed' (will negate delta etc.)
|
||||
// as this is much more efficient than reversing every path.
|
||||
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
||||
is_reversed = lowest_path_idx.has_value() && is_neg_area;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_path_idx = std::nullopt;
|
||||
lowest_path_idx.reset();
|
||||
is_reversed = false;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +243,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
path_out.emplace_back(pt);
|
||||
@@ -245,7 +252,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
path_out.emplace_back(pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
@@ -601,10 +608,10 @@ void ClipperOffset::ExecuteInternal(double delta)
|
||||
|
||||
if (!solution->size()) return;
|
||||
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear(false);
|
||||
c.PreserveCollinear(preserve_collinear_);
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||
#ifdef USINGZ
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 5 July 2024 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : FAST rectangular clipping *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -77,8 +77,8 @@ namespace Clipper2Lib {
|
||||
bool GetSegmentIntersection(const Point64& p1,
|
||||
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
|
||||
{
|
||||
double res1 = CrossProduct(p1, p3, p4);
|
||||
double res2 = CrossProduct(p2, p3, p4);
|
||||
int res1 = CrossProductSign(p1, p3, p4);
|
||||
int res2 = CrossProductSign(p2, p3, p4);
|
||||
if (res1 == 0)
|
||||
{
|
||||
ip = p1;
|
||||
@@ -97,8 +97,8 @@ namespace Clipper2Lib {
|
||||
}
|
||||
if ((res1 > 0) == (res2 > 0)) return false;
|
||||
|
||||
double res3 = CrossProduct(p3, p1, p2);
|
||||
double res4 = CrossProduct(p4, p1, p2);
|
||||
int res3 = CrossProductSign(p3, p1, p2);
|
||||
int res4 = CrossProductSign(p4, p1, p2);
|
||||
if (res3 == 0)
|
||||
{
|
||||
ip = p3;
|
||||
@@ -116,7 +116,7 @@ namespace Clipper2Lib {
|
||||
if ((res3 > 0) == (res4 > 0)) return false;
|
||||
|
||||
// segments must intersect to get here
|
||||
return GetSegmentIntersectPt(p1, p2, p3, p4, ip);
|
||||
return GetLineIntersectPt(p1, p2, p3, p4, ip);
|
||||
}
|
||||
|
||||
inline bool GetIntersection(const Path64& rectPath,
|
||||
@@ -227,7 +227,7 @@ namespace Clipper2Lib {
|
||||
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
|
||||
{
|
||||
if (AreOpposites(prev, curr))
|
||||
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
|
||||
return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
|
||||
else
|
||||
return HeadingClockwise(prev, curr);
|
||||
}
|
||||
|
||||
1224
deps_src/clipper2/Clipper2Lib/src/clipper.triangulation.cpp
Normal file
@@ -6,3 +6,4 @@
|
||||
#include "clipper.engine.cpp"
|
||||
#include "clipper.offset.cpp"
|
||||
#include "clipper.rectclip.cpp"
|
||||
#include "clipper.triangulation.cpp"
|
||||
|
||||
11
deps_src/colorsolver/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(colorsolver)
|
||||
|
||||
add_library(colorsolver STATIC
|
||||
ColorSolver.cpp
|
||||
ColorSolver.hpp
|
||||
)
|
||||
|
||||
target_include_directories(colorsolver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(colorsolver PUBLIC cxx_std_17)
|
||||
target_link_libraries(colorsolver PUBLIC pigment_painter prusa_fdm_mixer)
|
||||
2024
deps_src/colorsolver/ColorSolver.cpp
Normal file
243
deps_src/colorsolver/ColorSolver.hpp
Normal file
@@ -0,0 +1,243 @@
|
||||
// ColorSolver
|
||||
// Copyright (C) 2026 sentientstardust
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef slic3r_ColorSolver_hpp_
|
||||
#define slic3r_ColorSolver_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ColorSolverMixModel : int
|
||||
{
|
||||
PigmentPainter = 0,
|
||||
PrusaFdmMixer = 1
|
||||
};
|
||||
|
||||
enum class ColorSolverLookupMode : int
|
||||
{
|
||||
ClosestMix = 0,
|
||||
BlendClosestTwo = 1
|
||||
};
|
||||
|
||||
enum class ColorSolverMode : int
|
||||
{
|
||||
RGB = 0,
|
||||
Oklab = 1,
|
||||
OklabSoftCap4Dark4 = 2
|
||||
};
|
||||
|
||||
enum class ColorSolverStackComponentRole : uint8_t
|
||||
{
|
||||
Generic = 0,
|
||||
Cyan = 1,
|
||||
Magenta = 2,
|
||||
Yellow = 3,
|
||||
Black = 4,
|
||||
White = 5
|
||||
};
|
||||
|
||||
enum class ColorSolverCalibratedStackModelKind : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
CurrentLinearAffine = 1,
|
||||
AlphaEffective = 2,
|
||||
DepthKernelLinear = 3,
|
||||
NearestMeasuredSample = 4
|
||||
};
|
||||
|
||||
struct ColorSolverCalibratedStackModel {
|
||||
ColorSolverCalibratedStackModelKind kind { ColorSolverCalibratedStackModelKind::None };
|
||||
size_t component_count { 0 };
|
||||
int measured_stack_depth { 0 };
|
||||
std::vector<float> measured_layer_heights_mm;
|
||||
std::vector<float> alphas;
|
||||
std::vector<float> taus;
|
||||
std::vector<float> coefficients_linear_rgb;
|
||||
std::vector<float> measured_sample_rgbs;
|
||||
std::vector<uint16_t> measured_sample_stacks;
|
||||
mutable std::string cached_key;
|
||||
|
||||
bool valid() const;
|
||||
std::string cache_key() const;
|
||||
};
|
||||
|
||||
struct ColorSolverCandidateSet {
|
||||
struct KdNode {
|
||||
uint32_t candidate_idx { 0 };
|
||||
int left { -1 };
|
||||
int right { -1 };
|
||||
uint8_t axis { 0 };
|
||||
};
|
||||
|
||||
size_t component_count { 0 };
|
||||
std::vector<float> rgbs;
|
||||
std::vector<float> perceptual_coords;
|
||||
std::vector<float> weights;
|
||||
std::vector<KdNode> kd_nodes;
|
||||
std::vector<KdNode> perceptual_kd_nodes;
|
||||
int kd_root { -1 };
|
||||
int perceptual_kd_root { -1 };
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return component_count == 0 || rgbs.empty() || rgbs.size() % 3 != 0 ||
|
||||
weights.size() != (rgbs.size() / 3) * component_count;
|
||||
}
|
||||
};
|
||||
|
||||
using ColorSolverCandidateCache = std::map<std::string, ColorSolverCandidateSet>;
|
||||
|
||||
struct ColorSolverOrderedStackCandidateSet {
|
||||
using KdNode = ColorSolverCandidateSet::KdNode;
|
||||
|
||||
size_t component_count { 0 };
|
||||
int stack_depth { 0 };
|
||||
int simulated_stack_depth { 0 };
|
||||
std::vector<float> rgbs;
|
||||
std::vector<float> perceptual_coords;
|
||||
std::vector<uint16_t> stacks;
|
||||
std::vector<KdNode> kd_nodes;
|
||||
std::vector<KdNode> perceptual_kd_nodes;
|
||||
int kd_root { -1 };
|
||||
int perceptual_kd_root { -1 };
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return component_count == 0 || stack_depth <= 0 || rgbs.empty() || rgbs.size() % 3 != 0 ||
|
||||
stacks.size() != (rgbs.size() / 3) * size_t(stack_depth);
|
||||
}
|
||||
};
|
||||
|
||||
struct ColorSolverOrderedStackResult {
|
||||
std::vector<uint16_t> surface_to_deep;
|
||||
std::array<float, 3> rgb { { 0.f, 0.f, 0.f } };
|
||||
bool has_rgb { false };
|
||||
};
|
||||
|
||||
using ColorSolverOrderedStackCandidateCache = std::map<std::string, ColorSolverOrderedStackCandidateSet>;
|
||||
|
||||
ColorSolverMixModel color_solver_mix_model_from_index(int model);
|
||||
ColorSolverLookupMode color_solver_lookup_mode_from_index(int mode);
|
||||
ColorSolverMode color_solver_mode_from_index(int mode);
|
||||
|
||||
int color_solver_total_units_for_component_count(size_t component_count);
|
||||
size_t color_solver_candidate_count(size_t component_count, int total_units);
|
||||
|
||||
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<int> &weights,
|
||||
ColorSolverMixModel mix_model);
|
||||
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &weights,
|
||||
ColorSolverMixModel mix_model);
|
||||
std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<uint16_t> &surface_to_deep,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb);
|
||||
std::array<float, 3> color_solver_srgb_from_oklab(const std::array<float, 3> &oklab);
|
||||
|
||||
std::string color_solver_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
ColorSolverCandidateSet build_color_solver_candidates(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
void build_color_solver_candidate_kd_trees(ColorSolverCandidateSet &candidates);
|
||||
const ColorSolverCandidateSet &color_solver_candidates(ColorSolverCandidateCache &cache,
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
std::vector<float> solve_color_solver_weights_for_target(const ColorSolverCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverLookupMode lookup_mode,
|
||||
ColorSolverMode solver_mode);
|
||||
std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
|
||||
ColorSolverOrderedStackCandidateCache &cache,
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
ColorSolverOrderedStackResult solve_color_solver_ordered_stack_result_for_target(
|
||||
const ColorSolverOrderedStackCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverMode solver_mode);
|
||||
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(
|
||||
const ColorSolverOrderedStackCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverMode solver_mode);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
14
deps_src/pigment-painter/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(pigment_painter)
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
|
||||
add_library(pigment_painter STATIC
|
||||
lut_wide.png.c
|
||||
pigment_painter_mixer.cpp
|
||||
pigment_painter_mixer.hpp
|
||||
)
|
||||
|
||||
target_include_directories(pigment_painter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(pigment_painter PUBLIC cxx_std_17)
|
||||
target_link_libraries(pigment_painter PRIVATE PNG::PNG)
|
||||
674
deps_src/pigment-painter/COPYING
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
480199
deps_src/pigment-painter/lut_wide.png.c
Normal file
334
deps_src/pigment-painter/pigment_painter_mixer.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "pigment_painter_mixer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
extern "C" const char lut_wide_png_data[10295238];
|
||||
|
||||
namespace pigment_painter {
|
||||
namespace {
|
||||
|
||||
constexpr int k_lut_dimen = 256;
|
||||
constexpr size_t k_lut_png_size = sizeof(lut_wide_png_data);
|
||||
|
||||
struct PngMemoryReader
|
||||
{
|
||||
const std::uint8_t *data = nullptr;
|
||||
size_t size = 0;
|
||||
size_t offset = 0;
|
||||
};
|
||||
|
||||
struct LutImage
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int row_size = 0;
|
||||
std::vector<std::uint8_t> rgba;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
float clamp01(float value)
|
||||
{
|
||||
return std::max(0.f, std::min(1.f, value));
|
||||
}
|
||||
|
||||
float srgb_to_linear(float value)
|
||||
{
|
||||
const float x = clamp01(value);
|
||||
return x <= 0.04045f ? x / 12.92f : std::pow((x + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
float linear_to_srgb(float value)
|
||||
{
|
||||
const float x = clamp01(value);
|
||||
return x <= 0.0031308f ? x * 12.92f : 1.055f * std::pow(x, 1.f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
void png_memory_read(png_structp png_ptr, png_bytep out, png_size_t bytes)
|
||||
{
|
||||
PngMemoryReader *reader = static_cast<PngMemoryReader *>(png_get_io_ptr(png_ptr));
|
||||
if (reader == nullptr || reader->offset + bytes > reader->size)
|
||||
png_error(png_ptr, "pigment painter lut read failed");
|
||||
|
||||
std::memcpy(out, reader->data + reader->offset, bytes);
|
||||
reader->offset += bytes;
|
||||
}
|
||||
|
||||
bool decode_lut_png(LutImage &image)
|
||||
{
|
||||
PngMemoryReader reader {
|
||||
reinterpret_cast<const std::uint8_t *>(lut_wide_png_data),
|
||||
k_lut_png_size,
|
||||
0
|
||||
};
|
||||
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (png == nullptr)
|
||||
return false;
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (info == nullptr) {
|
||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png))) {
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
png_set_read_fn(png, &reader, png_memory_read);
|
||||
png_read_info(png, info);
|
||||
|
||||
png_uint_32 width = png_get_image_width(png, info);
|
||||
png_uint_32 height = png_get_image_height(png, info);
|
||||
int bit_depth = png_get_bit_depth(png, info);
|
||||
int color_type = png_get_color_type(png, info);
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16(png);
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png);
|
||||
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png);
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_filler(png, 0xff, PNG_FILLER_AFTER);
|
||||
|
||||
png_read_update_info(png, info);
|
||||
|
||||
if (png_get_channels(png, info) != 4 || width % k_lut_dimen != 0 || height % k_lut_dimen != 0) {
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
image.width = int(width);
|
||||
image.height = int(height);
|
||||
image.row_size = image.width / k_lut_dimen;
|
||||
image.rgba.resize(size_t(width) * size_t(height) * 4);
|
||||
|
||||
std::vector<png_bytep> rows(height);
|
||||
for (png_uint_32 row = 0; row < height; ++row)
|
||||
rows[row] = image.rgba.data() + size_t(row) * size_t(width) * 4;
|
||||
|
||||
png_read_image(png, rows.data());
|
||||
png_read_end(png, nullptr);
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
|
||||
image.loaded = image.width == k_lut_dimen * image.row_size &&
|
||||
image.height >= k_lut_dimen * ((k_lut_dimen * 2 + image.row_size - 1) / image.row_size);
|
||||
return image.loaded;
|
||||
}
|
||||
|
||||
const LutImage *lut_image()
|
||||
{
|
||||
static LutImage image;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() { decode_lut_png(image); });
|
||||
return image.loaded ? &image : nullptr;
|
||||
}
|
||||
|
||||
std::array<float, 3> sample_lut_texel(const LutImage &image, int x, int y, int z)
|
||||
{
|
||||
const int col = z % image.row_size;
|
||||
const int row = z / image.row_size;
|
||||
const int ix = x + col * k_lut_dimen;
|
||||
const int iy = y + row * k_lut_dimen;
|
||||
const size_t idx = (size_t(iy) * size_t(image.width) + size_t(ix)) * 4;
|
||||
|
||||
return {
|
||||
float(image.rgba[idx]) / 255.f,
|
||||
float(image.rgba[idx + 2]) / 255.f,
|
||||
float(image.rgba[idx + 1]) / 255.f
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 3> lerp_color(const std::array<float, 3> &a, const std::array<float, 3> &b, float t)
|
||||
{
|
||||
return {
|
||||
a[0] + (b[0] - a[0]) * t,
|
||||
a[1] + (b[1] - a[1]) * t,
|
||||
a[2] + (b[2] - a[2]) * t
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 3> sample_lut(const LutImage &image, const std::array<float, 3> &color, int z_offset)
|
||||
{
|
||||
const float xf = clamp01(color[0]) * float(k_lut_dimen - 1);
|
||||
const float yf = clamp01(color[1]) * float(k_lut_dimen - 1);
|
||||
const float zf = clamp01(color[2]) * float(k_lut_dimen - 1);
|
||||
|
||||
const int x0 = int(std::floor(xf));
|
||||
const int y0 = int(std::floor(yf));
|
||||
const int z0 = int(std::floor(zf));
|
||||
const int x1 = std::min(x0 + 1, k_lut_dimen - 1);
|
||||
const int y1 = std::min(y0 + 1, k_lut_dimen - 1);
|
||||
const int z1 = std::min(z0 + 1, k_lut_dimen - 1);
|
||||
|
||||
const float tx = xf - float(x0);
|
||||
const float ty = yf - float(y0);
|
||||
const float tz = zf - float(z0);
|
||||
|
||||
const std::array<float, 3> c000 = sample_lut_texel(image, x0, y0, z0 + z_offset);
|
||||
const std::array<float, 3> c100 = sample_lut_texel(image, x1, y0, z0 + z_offset);
|
||||
const std::array<float, 3> c010 = sample_lut_texel(image, x0, y1, z0 + z_offset);
|
||||
const std::array<float, 3> c110 = sample_lut_texel(image, x1, y1, z0 + z_offset);
|
||||
const std::array<float, 3> c001 = sample_lut_texel(image, x0, y0, z1 + z_offset);
|
||||
const std::array<float, 3> c101 = sample_lut_texel(image, x1, y0, z1 + z_offset);
|
||||
const std::array<float, 3> c011 = sample_lut_texel(image, x0, y1, z1 + z_offset);
|
||||
const std::array<float, 3> c111 = sample_lut_texel(image, x1, y1, z1 + z_offset);
|
||||
|
||||
const std::array<float, 3> c00 = lerp_color(c000, c100, tx);
|
||||
const std::array<float, 3> c10 = lerp_color(c010, c110, tx);
|
||||
const std::array<float, 3> c01 = lerp_color(c001, c101, tx);
|
||||
const std::array<float, 3> c11 = lerp_color(c011, c111, tx);
|
||||
const std::array<float, 3> c0 = lerp_color(c00, c10, ty);
|
||||
const std::array<float, 3> c1 = lerp_color(c01, c11, ty);
|
||||
return lerp_color(c0, c1, tz);
|
||||
}
|
||||
|
||||
std::array<float, 4> color_to_pigment(const LutImage &image, const std::array<float, 3> &color)
|
||||
{
|
||||
const std::array<float, 3> sampled = sample_lut(image, color, 0);
|
||||
std::array<float, 4> pigment {
|
||||
sampled[0],
|
||||
sampled[1],
|
||||
sampled[2],
|
||||
1.f - sampled[0] - sampled[1] - sampled[2]
|
||||
};
|
||||
|
||||
const float total = pigment[0] + pigment[1] + pigment[2] + pigment[3];
|
||||
if (total > 0.0001f) {
|
||||
const float inv_total = 1.f / total;
|
||||
for (float &value : pigment)
|
||||
value *= inv_total;
|
||||
}
|
||||
|
||||
return pigment;
|
||||
}
|
||||
|
||||
std::array<float, 3> pigment_to_color(const LutImage &image, const std::array<float, 4> &pigment)
|
||||
{
|
||||
return sample_lut(image, { pigment[0], pigment[1], pigment[2] }, k_lut_dimen);
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_with_lut(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights,
|
||||
const LutImage &image)
|
||||
{
|
||||
std::array<float, 4> mixed_pigment { 0.f, 0.f, 0.f, 0.f };
|
||||
std::array<float, 3> error { 0.f, 0.f, 0.f };
|
||||
float total_weight = 0.f;
|
||||
|
||||
for (const float weight : weights) {
|
||||
if (std::isfinite(weight) && weight > 0.f)
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if (total_weight <= 0.f)
|
||||
return colors.front();
|
||||
|
||||
const float inv_total_weight = 1.f / total_weight;
|
||||
for (size_t idx = 0; idx < colors.size(); ++idx) {
|
||||
const float raw_weight = weights[idx];
|
||||
if (!std::isfinite(raw_weight) || raw_weight <= 0.f)
|
||||
continue;
|
||||
|
||||
const float weight = raw_weight * inv_total_weight;
|
||||
const std::array<float, 4> pigment = color_to_pigment(image, colors[idx]);
|
||||
const std::array<float, 3> reconstructed = pigment_to_color(image, pigment);
|
||||
|
||||
for (size_t channel = 0; channel < 4; ++channel)
|
||||
mixed_pigment[channel] += pigment[channel] * weight;
|
||||
for (size_t channel = 0; channel < 3; ++channel)
|
||||
error[channel] += (clamp01(colors[idx][channel]) - reconstructed[channel]) * weight;
|
||||
}
|
||||
|
||||
const float pigment_total = mixed_pigment[0] + mixed_pigment[1] + mixed_pigment[2] + mixed_pigment[3];
|
||||
if (pigment_total > 0.0001f) {
|
||||
const float inv_pigment_total = 1.f / pigment_total;
|
||||
for (float &value : mixed_pigment)
|
||||
value *= inv_pigment_total;
|
||||
}
|
||||
|
||||
std::array<float, 3> mixed_color = pigment_to_color(image, mixed_pigment);
|
||||
for (size_t channel = 0; channel < 3; ++channel)
|
||||
mixed_color[channel] = clamp01(mixed_color[channel] + error[channel]);
|
||||
|
||||
return mixed_color;
|
||||
}
|
||||
|
||||
float reflectance_to_ks(float reflectance)
|
||||
{
|
||||
constexpr float k_min_reflectance = 0.02f;
|
||||
const float r = std::clamp(reflectance, k_min_reflectance, 1.f);
|
||||
return ((1.f - r) * (1.f - r)) / (2.f * r);
|
||||
}
|
||||
|
||||
float ks_to_reflectance(float ks)
|
||||
{
|
||||
const float ratio = std::max(0.f, ks);
|
||||
return clamp01(1.f + ratio - std::sqrt(std::max(0.f, ratio * ratio + 2.f * ratio)));
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_linear_reflectance(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights)
|
||||
{
|
||||
if (colors.empty() || colors.size() != weights.size())
|
||||
return { 0.f, 0.f, 0.f };
|
||||
|
||||
if (const LutImage *image = lut_image(); image != nullptr)
|
||||
return mix_with_lut(colors, weights, *image);
|
||||
|
||||
std::array<float, 3> accumulated_ks { 0.f, 0.f, 0.f };
|
||||
float total_weight = 0.f;
|
||||
for (size_t idx = 0; idx < colors.size(); ++idx) {
|
||||
const float weight = std::max(0.f, weights[idx]);
|
||||
if (!std::isfinite(weight) || weight <= 0.f)
|
||||
continue;
|
||||
|
||||
accumulated_ks[0] += reflectance_to_ks(srgb_to_linear(colors[idx][0])) * weight;
|
||||
accumulated_ks[1] += reflectance_to_ks(srgb_to_linear(colors[idx][1])) * weight;
|
||||
accumulated_ks[2] += reflectance_to_ks(srgb_to_linear(colors[idx][2])) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if (total_weight <= 0.f)
|
||||
return colors.front();
|
||||
|
||||
const float inv_total = 1.f / total_weight;
|
||||
return {
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[0] * inv_total)),
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[1] * inv_total)),
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[2] * inv_total))
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights)
|
||||
{
|
||||
return mix_linear_reflectance(colors, weights);
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<int> &weights)
|
||||
{
|
||||
std::vector<float> float_weights;
|
||||
float_weights.reserve(weights.size());
|
||||
for (const int weight : weights)
|
||||
float_weights.emplace_back(float(std::max(0, weight)));
|
||||
return mix_linear_reflectance(colors, float_weights);
|
||||
}
|
||||
|
||||
} // namespace pigment_painter
|
||||
17
deps_src/pigment-painter/pigment_painter_mixer.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef PIGMENT_PAINTER_MIXER_HPP
|
||||
#define PIGMENT_PAINTER_MIXER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace pigment_painter {
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights);
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<int> &weights);
|
||||
|
||||
} // namespace pigment_painter
|
||||
|
||||
#endif
|
||||
10
deps_src/prusa-fdm-mixer/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(prusa_fdm_mixer)
|
||||
|
||||
add_library(prusa_fdm_mixer STATIC
|
||||
prusa_fdm_mixer.cpp
|
||||
prusa_fdm_mixer.hpp
|
||||
)
|
||||
|
||||
target_include_directories(prusa_fdm_mixer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(prusa_fdm_mixer PUBLIC cxx_std_17)
|
||||
21
deps_src/prusa-fdm-mixer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Ondrej Bartas (Prusa Research s.r.o.) and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
351
deps_src/prusa-fdm-mixer/prusa_fdm_mixer.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* prusa_fdm_mixer.cpp — Implementation of the prusa-fdm-mixer color mixing model.
|
||||
*
|
||||
* Copyright (c) Prusa Research s.r.o.
|
||||
* MIT License — see LICENSE.
|
||||
*/
|
||||
|
||||
#include "prusa_fdm_mixer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace prusa_fdm_mixer {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* sRGB <-> linear RGB
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
inline double srgb_to_linear(double c) {
|
||||
c = c / 255.0;
|
||||
if (c <= 0.04045) return c / 12.92;
|
||||
return std::pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
inline double linear_to_srgb(double c) {
|
||||
c = std::clamp(c, 0.0, 1.0);
|
||||
double v;
|
||||
if (c <= 0.0031308) v = 12.92 * c;
|
||||
else v = 1.055 * std::pow(c, 1.0 / 2.4) - 0.055;
|
||||
return v * 255.0;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* sRGB <-> XYZ (D65)
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
struct XYZ { double x, y, z; };
|
||||
|
||||
inline XYZ rgb_to_xyz(const RGB& rgb) {
|
||||
const double r = srgb_to_linear(rgb.r);
|
||||
const double g = srgb_to_linear(rgb.g);
|
||||
const double b = srgb_to_linear(rgb.b);
|
||||
return XYZ{
|
||||
(r * 0.4124564 + g * 0.3575761 + b * 0.1804375) * 100.0,
|
||||
(r * 0.2126729 + g * 0.7151522 + b * 0.0721750) * 100.0,
|
||||
(r * 0.0193339 + g * 0.1191920 + b * 0.9503041) * 100.0,
|
||||
};
|
||||
}
|
||||
|
||||
inline RGB xyz_to_rgb(const XYZ& xyz) {
|
||||
const double x = xyz.x / 100.0;
|
||||
const double y = xyz.y / 100.0;
|
||||
const double z = xyz.z / 100.0;
|
||||
const double r = linear_to_srgb(x * 3.2404542 + y * -1.5371385 + z * -0.4985314);
|
||||
const double g = linear_to_srgb(x * -0.9692660 + y * 1.8760108 + z * 0.0415560);
|
||||
const double b = linear_to_srgb(x * 0.0556434 + y * -0.2040259 + z * 1.0572252);
|
||||
auto clamp_round = [](double v) -> std::uint8_t {
|
||||
const long iv = std::lround(v);
|
||||
return static_cast<std::uint8_t>(std::clamp<long>(iv, 0, 255));
|
||||
};
|
||||
return RGB{ clamp_round(r), clamp_round(g), clamp_round(b) };
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* XYZ <-> CIELAB (D65)
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
constexpr double XN = 95.047;
|
||||
constexpr double YN = 100.0;
|
||||
constexpr double ZN = 108.883;
|
||||
|
||||
inline double lab_f(double t) {
|
||||
return (t > 0.008856) ? std::cbrt(t) : (7.787 * t + 16.0 / 116.0);
|
||||
}
|
||||
|
||||
inline double lab_f_inv(double t) {
|
||||
return (t > 0.206893) ? (t * t * t) : ((t - 16.0 / 116.0) / 7.787);
|
||||
}
|
||||
|
||||
inline LAB xyz_to_lab(const XYZ& xyz) {
|
||||
const double fx = lab_f(xyz.x / XN);
|
||||
const double fy = lab_f(xyz.y / YN);
|
||||
const double fz = lab_f(xyz.z / ZN);
|
||||
return LAB{ 116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz) };
|
||||
}
|
||||
|
||||
inline XYZ lab_to_xyz(const LAB& lab) {
|
||||
const double fy = (lab.L + 16.0) / 116.0;
|
||||
const double fx = lab.a / 500.0 + fy;
|
||||
const double fz = fy - lab.b / 200.0;
|
||||
return XYZ{ XN * lab_f_inv(fx), YN * lab_f_inv(fy), ZN * lab_f_inv(fz) };
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* ============================================================================
|
||||
* Public color-space helpers
|
||||
* ============================================================================ */
|
||||
|
||||
RGB hex_to_rgb(const std::string& hex) {
|
||||
std::string s = hex;
|
||||
if (!s.empty() && s[0] == '#') s.erase(0, 1);
|
||||
if (s.size() != 6) {
|
||||
throw std::invalid_argument("prusa_fdm_mixer::hex_to_rgb: expected 6 hex digits, got: " + hex);
|
||||
}
|
||||
auto parse_byte = [&](size_t off) -> std::uint8_t {
|
||||
unsigned int v = 0;
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
char c = s[off + i];
|
||||
unsigned int d;
|
||||
if (c >= '0' && c <= '9') d = static_cast<unsigned int>(c - '0');
|
||||
else if (c >= 'a' && c <= 'f') d = 10u + static_cast<unsigned int>(c - 'a');
|
||||
else if (c >= 'A' && c <= 'F') d = 10u + static_cast<unsigned int>(c - 'A');
|
||||
else throw std::invalid_argument("prusa_fdm_mixer::hex_to_rgb: invalid hex char in: " + hex);
|
||||
v = (v << 4) | d;
|
||||
}
|
||||
return static_cast<std::uint8_t>(v);
|
||||
};
|
||||
return RGB{ parse_byte(0), parse_byte(2), parse_byte(4) };
|
||||
}
|
||||
|
||||
std::string rgb_to_hex(const RGB& rgb) {
|
||||
char buf[8];
|
||||
std::snprintf(buf, sizeof(buf), "#%02x%02x%02x",
|
||||
static_cast<unsigned int>(rgb.r),
|
||||
static_cast<unsigned int>(rgb.g),
|
||||
static_cast<unsigned int>(rgb.b));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
LAB rgb_to_lab(const RGB& rgb) {
|
||||
return xyz_to_lab(rgb_to_xyz(rgb));
|
||||
}
|
||||
|
||||
RGB lab_to_rgb(const LAB& lab) {
|
||||
return xyz_to_rgb(lab_to_xyz(lab));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* prusa-fdm-mixer model
|
||||
* ============================================================================ */
|
||||
|
||||
namespace {
|
||||
|
||||
// Empirically fit constants — see companion repo for derivation.
|
||||
constexpr double YN_EXPONENT = 3.0;
|
||||
constexpr double L_SLOPE = -0.0477;
|
||||
constexpr double L_INTERCEPT = -2.112;
|
||||
constexpr double L_KINK_THRESHOLD = 15.0;
|
||||
constexpr double L_KINK_SLOPE = -0.060;
|
||||
constexpr double C_SLOPE = 0.2780;
|
||||
constexpr double C_INTERCEPT = -15.580;
|
||||
constexpr double PEAK_STRENGTH = 1.375;
|
||||
constexpr double HUE_PEAK_DEG = 10.38;
|
||||
constexpr double HUE_CENTER_DEG = 210.0;
|
||||
constexpr double HUE_HALF_WIDTH_DEG = 30.0;
|
||||
|
||||
LAB predict_lab(const std::vector<Part>& parts) {
|
||||
if (parts.empty()) {
|
||||
throw std::invalid_argument("prusa_fdm_mixer::mix: empty parts list");
|
||||
}
|
||||
|
||||
// ---- Gradient safety: if any single part has ratio >= ~1, return it directly. ----
|
||||
// Without this guard, the constant lightness correction (-2.112 * cs) would
|
||||
// perturb the predicted color even at the endpoints of a gradient.
|
||||
for (const Part& p : parts) {
|
||||
if (p.ratio >= 0.9999) {
|
||||
return rgb_to_lab(hex_to_rgb(p.hex));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Step 1: Yule-Nielsen base prediction ----
|
||||
double r_acc = 0.0, g_acc = 0.0, b_acc = 0.0;
|
||||
std::vector<double> component_Ls;
|
||||
component_Ls.reserve(parts.size());
|
||||
|
||||
const double inv_n = 1.0 / YN_EXPONENT;
|
||||
for (const Part& p : parts) {
|
||||
const RGB rgb = hex_to_rgb(p.hex);
|
||||
const double r_lin = srgb_to_linear(rgb.r);
|
||||
const double g_lin = srgb_to_linear(rgb.g);
|
||||
const double b_lin = srgb_to_linear(rgb.b);
|
||||
r_acc += std::pow(r_lin, inv_n) * p.ratio;
|
||||
g_acc += std::pow(g_lin, inv_n) * p.ratio;
|
||||
b_acc += std::pow(b_lin, inv_n) * p.ratio;
|
||||
component_Ls.push_back(rgb_to_lab(rgb).L);
|
||||
}
|
||||
|
||||
auto pow_clamped = [](double v, double exp) {
|
||||
return std::pow(std::max(0.0, v), exp);
|
||||
};
|
||||
const double yn_r = linear_to_srgb(pow_clamped(r_acc, YN_EXPONENT));
|
||||
const double yn_g = linear_to_srgb(pow_clamped(g_acc, YN_EXPONENT));
|
||||
const double yn_b = linear_to_srgb(pow_clamped(b_acc, YN_EXPONENT));
|
||||
|
||||
auto clamp_round = [](double v) -> std::uint8_t {
|
||||
const long iv = std::lround(v);
|
||||
return static_cast<std::uint8_t>(std::clamp<long>(iv, 0, 255));
|
||||
};
|
||||
const RGB yn_rgb{ clamp_round(yn_r), clamp_round(yn_g), clamp_round(yn_b) };
|
||||
const LAB base = rgb_to_lab(yn_rgb);
|
||||
|
||||
// ---- Bell-curve weight ----
|
||||
// For N components: w = N^N * prod(ratios)
|
||||
// = 1 when ratios are equal (peak correction zone)
|
||||
// = 0 at any endpoint (single-component, no correction needed)
|
||||
double w = 1.0;
|
||||
for (const Part& p : parts) w *= p.ratio;
|
||||
w *= std::pow(static_cast<double>(parts.size()),
|
||||
static_cast<double>(parts.size()));
|
||||
w = std::clamp(w, 0.0, 1.0);
|
||||
const double cs = w * PEAK_STRENGTH;
|
||||
|
||||
// ---- Step 2: piecewise lightness correction ----
|
||||
const double L_min = *std::min_element(component_Ls.begin(), component_Ls.end());
|
||||
const double L_max = *std::max_element(component_Ls.begin(), component_Ls.end());
|
||||
const double L_gap = L_max - L_min;
|
||||
|
||||
double L_corr = L_SLOPE * L_gap + L_INTERCEPT;
|
||||
if (L_gap > L_KINK_THRESHOLD) {
|
||||
L_corr += L_KINK_SLOPE * (L_gap - L_KINK_THRESHOLD);
|
||||
}
|
||||
L_corr *= cs;
|
||||
const double L_new = base.L + L_corr;
|
||||
|
||||
// ---- Step 3: chroma correction ----
|
||||
double a_out = base.a;
|
||||
double b_out = base.b;
|
||||
const double pred_C = std::hypot(base.a, base.b);
|
||||
if (pred_C >= 0.01) {
|
||||
const double target_dC = (C_SLOPE * L_new + C_INTERCEPT) * cs;
|
||||
const double new_C = std::max(0.0, pred_C + target_dC);
|
||||
const double scale = new_C / pred_C;
|
||||
a_out = base.a * scale;
|
||||
b_out = base.b * scale;
|
||||
}
|
||||
|
||||
// ---- Step 4: cyan-band hue rotation ----
|
||||
const double new_C_final = std::hypot(a_out, b_out);
|
||||
if (new_C_final >= 1.0) {
|
||||
double pred_h = std::atan2(b_out, a_out) * 180.0 / PI;
|
||||
if (pred_h < 0.0) pred_h += 360.0;
|
||||
|
||||
double h_corr = 0.0;
|
||||
if (pred_h >= HUE_CENTER_DEG - HUE_HALF_WIDTH_DEG &&
|
||||
pred_h < HUE_CENTER_DEG + HUE_HALF_WIDTH_DEG) {
|
||||
const double dist = std::abs(pred_h - HUE_CENTER_DEG);
|
||||
const double falloff = std::max(0.0, 1.0 - dist / HUE_HALF_WIDTH_DEG);
|
||||
h_corr = HUE_PEAK_DEG * falloff * w;
|
||||
}
|
||||
if (h_corr != 0.0) {
|
||||
double new_h = std::fmod(pred_h + h_corr, 360.0);
|
||||
if (new_h < 0.0) new_h += 360.0;
|
||||
const double new_h_rad = new_h * PI / 180.0;
|
||||
a_out = new_C_final * std::cos(new_h_rad);
|
||||
b_out = new_C_final * std::sin(new_h_rad);
|
||||
}
|
||||
}
|
||||
|
||||
return LAB{ L_new, a_out, b_out };
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RGB mix_rgb(const std::vector<Part>& parts) {
|
||||
return lab_to_rgb(predict_lab(parts));
|
||||
}
|
||||
|
||||
std::string mix(const std::vector<Part>& parts) {
|
||||
return rgb_to_hex(mix_rgb(parts));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* ΔE2000
|
||||
* ============================================================================ */
|
||||
|
||||
double delta_e_2000(const LAB& lab1, const LAB& lab2) {
|
||||
const double L1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
|
||||
const double L2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
|
||||
|
||||
const double avg_L = (L1 + L2) / 2.0;
|
||||
const double C1 = std::hypot(a1, b1);
|
||||
const double C2 = std::hypot(a2, b2);
|
||||
const double avg_C = (C1 + C2) / 2.0;
|
||||
const double avg_C7 = std::pow(avg_C, 7.0);
|
||||
const double pow25_7 = std::pow(25.0, 7.0);
|
||||
const double G = 0.5 * (1.0 - std::sqrt(avg_C7 / (avg_C7 + pow25_7)));
|
||||
|
||||
const double a1p = a1 * (1.0 + G);
|
||||
const double a2p = a2 * (1.0 + G);
|
||||
const double C1p = std::hypot(a1p, b1);
|
||||
const double C2p = std::hypot(a2p, b2);
|
||||
const double avg_Cp = (C1p + C2p) / 2.0;
|
||||
|
||||
auto deg = [](double x) {
|
||||
double d = x * 180.0 / PI;
|
||||
if (d < 0.0) d += 360.0;
|
||||
return d;
|
||||
};
|
||||
const double h1p = deg(std::atan2(b1, a1p));
|
||||
const double h2p = deg(std::atan2(b2, a2p));
|
||||
|
||||
double dHp_diff = h2p - h1p;
|
||||
double dhp = 0.0;
|
||||
if (C1p * C2p != 0.0) {
|
||||
if (std::abs(dHp_diff) <= 180.0) dhp = dHp_diff;
|
||||
else if (dHp_diff > 180.0) dhp = dHp_diff - 360.0;
|
||||
else dhp = dHp_diff + 360.0;
|
||||
}
|
||||
|
||||
double avg_Hp;
|
||||
if (C1p * C2p == 0.0) avg_Hp = h1p + h2p;
|
||||
else if (std::abs(h1p - h2p) <= 180.0) avg_Hp = (h1p + h2p) / 2.0;
|
||||
else if (h1p + h2p < 360.0) avg_Hp = (h1p + h2p + 360.0) / 2.0;
|
||||
else avg_Hp = (h1p + h2p - 360.0) / 2.0;
|
||||
|
||||
const double T = 1.0
|
||||
- 0.17 * std::cos((avg_Hp - 30.0) * PI / 180.0)
|
||||
+ 0.24 * std::cos((2.0 * avg_Hp) * PI / 180.0)
|
||||
+ 0.32 * std::cos((3.0 * avg_Hp + 6.0) * PI / 180.0)
|
||||
- 0.20 * std::cos((4.0 * avg_Hp - 63.0) * PI / 180.0);
|
||||
|
||||
const double dLp = L2 - L1;
|
||||
const double dCp = C2p - C1p;
|
||||
const double dHpFinal = 2.0 * std::sqrt(C1p * C2p) * std::sin(dhp * PI / 360.0);
|
||||
|
||||
const double SL = 1.0 + (0.015 * std::pow(avg_L - 50.0, 2.0))
|
||||
/ std::sqrt(20.0 + std::pow(avg_L - 50.0, 2.0));
|
||||
const double SC = 1.0 + 0.045 * avg_Cp;
|
||||
const double SH = 1.0 + 0.015 * avg_Cp * T;
|
||||
|
||||
const double dTheta = 30.0 * std::exp(-std::pow((avg_Hp - 275.0) / 25.0, 2.0));
|
||||
const double avg_Cp7 = std::pow(avg_Cp, 7.0);
|
||||
const double RC = 2.0 * std::sqrt(avg_Cp7 / (avg_Cp7 + pow25_7));
|
||||
const double RT = -RC * std::sin(2.0 * dTheta * PI / 180.0);
|
||||
|
||||
return std::sqrt(
|
||||
std::pow(dLp / SL, 2.0) +
|
||||
std::pow(dCp / SC, 2.0) +
|
||||
std::pow(dHpFinal / SH, 2.0) +
|
||||
RT * (dCp / SC) * (dHpFinal / SH)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace prusa_fdm_mixer
|
||||
99
deps_src/prusa-fdm-mixer/prusa_fdm_mixer.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* prusa_fdm_mixer.hpp — Predicts the apparent color of FDM filament mixes.
|
||||
*
|
||||
* The prusa-fdm-mixer model, calibrated against measured prints. Produces
|
||||
* predictions with a median ΔE2000 of ~5.7 against real samples
|
||||
* (Linear RGB ~14.5 for comparison).
|
||||
*
|
||||
* Drop-in usage in PrusaSlicer / OrcaSlicer / any C++17 project:
|
||||
*
|
||||
* #include "prusa_fdm_mixer.hpp"
|
||||
*
|
||||
* std::vector<prusa_fdm_mixer::Part> parts = {
|
||||
* { "#007a9d", 0.75 }, // 75% azure blue
|
||||
* { "#f6b921", 0.25 }, // 25% yellow
|
||||
* };
|
||||
* std::string mixed_hex = prusa_fdm_mixer::mix(parts);
|
||||
* // mixed_hex == "#XXXXXX" — predicted apparent color
|
||||
*
|
||||
* Properties:
|
||||
* - Gradient-safe: a part with ratio 1.0 returns that part's hex exactly.
|
||||
* - Smooth: small ratio changes produce small color changes.
|
||||
* - Works for 2 or 3+ components; ratios should sum to 1.
|
||||
*
|
||||
* No external dependencies beyond the C++ standard library.
|
||||
*
|
||||
* Copyright (c) Prusa Research s.r.o.
|
||||
* MIT License — see LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef PRUSA_FDM_MIXER_HPP
|
||||
#define PRUSA_FDM_MIXER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace prusa_fdm_mixer {
|
||||
|
||||
/** A single filament component in a mix. */
|
||||
struct Part {
|
||||
/** 6-digit hex color including leading '#', e.g. "#aabbcc". */
|
||||
std::string hex;
|
||||
/** Mixing ratio in [0, 1]. Ratios across all parts should sum to 1. */
|
||||
double ratio;
|
||||
};
|
||||
|
||||
/** RGB triple, channel values in [0, 255]. */
|
||||
struct RGB {
|
||||
std::uint8_t r;
|
||||
std::uint8_t g;
|
||||
std::uint8_t b;
|
||||
};
|
||||
|
||||
/** CIELAB triple — L in [0, 100], a/b unbounded but typically [-128, 127]. */
|
||||
struct LAB {
|
||||
double L;
|
||||
double a;
|
||||
double b;
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Public API
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Predict the apparent color when filaments are mixed in the given ratios.
|
||||
* @param parts Two or more parts; ratios should sum to ~1.
|
||||
* @return Predicted hex color, e.g. "#aabbcc".
|
||||
*/
|
||||
std::string mix(const std::vector<Part>& parts);
|
||||
|
||||
/**
|
||||
* Same as mix() but returns an RGB struct.
|
||||
*/
|
||||
RGB mix_rgb(const std::vector<Part>& parts);
|
||||
|
||||
/* ============================================================================
|
||||
* Color-space conversions (exposed for callers who want them)
|
||||
* ============================================================================ */
|
||||
|
||||
/** Parse a "#rrggbb" hex string. Throws std::invalid_argument on bad input. */
|
||||
RGB hex_to_rgb(const std::string& hex);
|
||||
|
||||
/** Format an RGB as "#rrggbb" lowercase. */
|
||||
std::string rgb_to_hex(const RGB& rgb);
|
||||
|
||||
/** Convert sRGB (0-255 channels) to CIELAB (D65 white point). */
|
||||
LAB rgb_to_lab(const RGB& rgb);
|
||||
|
||||
/** Convert CIELAB (D65 white point) back to sRGB (0-255 channels, clamped). */
|
||||
RGB lab_to_rgb(const LAB& lab);
|
||||
|
||||
/** ΔE2000 perceptual color difference between two LAB colors. */
|
||||
double delta_e_2000(const LAB& lab1, const LAB& lab2);
|
||||
|
||||
} // namespace prusa_fdm_mixer
|
||||
|
||||
#endif // PRUSA_FDM_MIXER_HPP
|
||||
8
deps_src/tinygltf/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
add_library(tinygltf_v3 STATIC tiny_gltf_v3.c)
|
||||
|
||||
target_include_directories(tinygltf_v3 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set_target_properties(tinygltf_v3 PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_STANDARD_REQUIRED ON
|
||||
)
|
||||
21
deps_src/tinygltf/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Syoyo Fujita, Aurélien Chatelain and many contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3278
deps_src/tinygltf/tiny_gltf_v3.c
Normal file
4477
deps_src/tinygltf/tiny_gltf_v3.h
Normal file
1024
deps_src/tinygltf/tinygltf_json_c.h
Normal file
356
docs/filament-preset-bridge-guide.md
Normal 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) |
|
||||
@@ -5638,7 +5638,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -6126,8 +6126,8 @@ msgstr "Desa el projecte com a"
|
||||
msgid "Save current project as"
|
||||
msgstr "Desar el projecte actual com"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar un model"
|
||||
|
||||
@@ -6048,8 +6048,8 @@ msgstr "Uložit projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Uložit aktuální projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Načíst model"
|
||||
|
||||
@@ -6219,8 +6219,8 @@ msgstr "Projekt speichern als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Aktuelles Projekt speichern als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Lade ein Modell"
|
||||
|
||||
@@ -5695,7 +5695,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -6206,8 +6206,8 @@ msgstr "Guardar proyecto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Guardar el proyecto actual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Cargar un modelo"
|
||||
|
||||
@@ -6227,8 +6227,8 @@ msgstr "Enregistrer le projet sous"
|
||||
msgid "Save current project as"
|
||||
msgstr "Enregistrer le projet actuel sous"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Charger un modèle"
|
||||
|
||||
@@ -6124,8 +6124,8 @@ msgstr "Projekt mentése másként"
|
||||
msgid "Save current project as"
|
||||
msgstr "Jelenlegi projekt mentése másként"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Modell betöltése"
|
||||
|
||||
@@ -6179,8 +6179,8 @@ msgstr "Salva progetto con nome"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salva progetto corrente con nome"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carica modello"
|
||||
|
||||
@@ -5981,8 +5981,8 @@ msgstr "プロジェクトを名前を付けて保存"
|
||||
msgid "Save current project as"
|
||||
msgstr "プロジェクトを名前を付けて保存"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMFをインポート"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMFをインポート"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "モデルを読み込む"
|
||||
|
||||
@@ -5959,8 +5959,8 @@ msgstr "프로젝트 다른 이름으로 저장"
|
||||
msgid "Save current project as"
|
||||
msgstr "현재 프로젝트 다른 이름으로 저장"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF 가져오기"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF 가져오기"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "모델 불러오기"
|
||||
|
||||
@@ -6085,8 +6085,8 @@ msgstr "Išsaugoti projektą kaip"
|
||||
msgid "Save current project as"
|
||||
msgstr "Išsaugoti dabartinį projektą kaip"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Įkelti modelį"
|
||||
|
||||
@@ -6072,8 +6072,8 @@ msgstr "Bewaar project als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Bewaar huidig project als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF importeren"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF importeren"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Laad een model"
|
||||
|
||||
@@ -6043,8 +6043,8 @@ msgstr "Zapisz projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Zapisz bieżący projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Wczytaj model"
|
||||
|
||||
@@ -6143,8 +6143,8 @@ msgstr "Salvar projeto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salvar o projeto atual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar um modelo"
|
||||
|
||||
@@ -6265,8 +6265,8 @@ msgstr "Сохранить проект как"
|
||||
msgid "Save current project as"
|
||||
msgstr "Сохранить текущий проект как"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Загрузка модели"
|
||||
|
||||
@@ -6041,8 +6041,8 @@ msgstr "Spara Projekt som"
|
||||
msgid "Save current project as"
|
||||
msgstr "Spara nuvarande projekt som"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Ladda modell"
|
||||
|
||||
@@ -6062,8 +6062,8 @@ msgstr "Projeyi farklı kaydet"
|
||||
msgid "Save current project as"
|
||||
msgstr "Mevcut projeyi farklı kaydet"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF'yi içe aktar"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF'yi içe aktar"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Model yükle"
|
||||
|
||||
@@ -5950,8 +5950,8 @@ msgstr "Зберегти проєкт як"
|
||||
msgid "Save current project as"
|
||||
msgstr "Зберегти поточний проєкт як"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Завантажте модель"
|
||||
|
||||
@@ -6003,8 +6003,8 @@ msgstr "Lưu dự án thành"
|
||||
msgid "Save current project as"
|
||||
msgstr "Lưu dự án hiện tại thành"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Tải model"
|
||||
|
||||
@@ -5848,8 +5848,8 @@ msgstr "项目另存为"
|
||||
msgid "Save current project as"
|
||||
msgstr "项目另存为"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "加载模型"
|
||||
|
||||
@@ -5916,8 +5916,8 @@ msgstr "另存專案為"
|
||||
msgid "Save current project as"
|
||||
msgstr "將目前專案另存為"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "載入模型"
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 16 KiB |
BIN
resources/images/OrcaSlicer_gradient.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/OrcaSlicer_gradient_circle.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/OrcaSlicer_gradient_narrow.png
Normal file
|
After Width: | Height: | Size: 46 KiB |