Merge branch 'main' into feat/plugin-feature

Resolve five conflicts, all of which needed both sides rather than a pick:

- BackgroundSlicingProcess: ours was a pure tabs->spaces reformat of base, so
  keep main's per-filament volume/nozzle map read-back (its only change here).
- GUI_App: main's #12506 else-if attached to an `if` this branch deleted;
  re-expressed onto the same-agent early-return path (the agent factory caches
  per id, so pointer equality is the same predicate).
- MainFrame: both sides relocated Sync Presets independently; keep main's
  push_notification plus the branch's Plugins menu items.
- Tab: the "TODO: Orca: Support hybrid" blocks were unchanged base, not a branch
  decision; take main's enabled Hybrid to match the already auto-merged siblings.
- test_config: union of both sides' cases (6 plugin + 9 multi-nozzle).
This commit is contained in:
SoftFever
2026-07-15 17:18:46 +08:00
986 changed files with 210610 additions and 18572 deletions

View File

@@ -122,54 +122,97 @@ jobs:
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' }}
# One test job per built arch, on the runner that built it.
unit_tests_linux_x86_64:
name: Linux x86_64
needs: build_linux
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
artifact: ${{ github.sha }}-tests-linux-x86_64
unit_tests_linux_aarch64:
name: Linux aarch64
needs: build_linux
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: ubuntu-24.04-arm
artifact: ${{ github.sha }}-tests-linux-aarch64
unit_tests_windows_x64:
name: Windows x64
needs: build_windows
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }}
artifact: ${{ github.sha }}-tests-windows-x64
unit_tests_windows_arm64:
name: Windows arm64
needs: build_windows
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: windows-11-arm
artifact: ${{ github.sha }}-tests-windows-arm64
test-dir: build-arm64/tests
unit_tests_macos_arm64:
name: macOS arm64
needs: build_macos_arch
if: ${{ !cancelled() && success() }}
uses: ./.github/workflows/unit_tests.yml
with:
os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }}
artifact: ${{ github.sha }}-tests-macos-arm64
test-dir: build/arm64/tests
# Slice a two-colour cube through every shipped printer with this PR's engine
# so all custom g-code (change_filament_gcode, machine start/end, etc.) is
# expanded - catches slicing regressions the static profile checks and unit
# tests can't see. Profile-only PRs are covered by check_profiles.yml (nightly
# binary); this covers src/engine PRs with the PR-built binary. Runs in
# parallel off build_linux's artifact so it doesn't lengthen the build leg.
slice_check_linux:
name: Slice check (Linux x86_64)
needs: build_linux
if: ${{ !cancelled() && success() }}
runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }}
steps:
- name: Checkout
- name: Checkout repository
uses: actions/checkout@v7
with:
sparse-checkout: |
.github
scripts
tests
- name: Apt-Install Dependencies
if: ${{ !vars.SELF_HOSTED }}
uses: ./.github/actions/apt-install-deps
- name: Restore Test Artifact
- name: Download profile validator
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
name: ${{ github.sha }}-profile-validator-linux-x86_64
path: validator-bin
- name: Validate slice (expand custom g-code)
timeout-minutes: 60
run: |
tar -xvf build_tests.tar
scripts/run_unit_tests.sh
- name: Upload Test Logs
uses: actions/upload-artifact@v7
if: ${{ failure() }}
chmod +x validator-bin/OrcaSlicer_profile_validator
./validator-bin/OrcaSlicer_profile_validator -p "${{ github.workspace }}/resources/profiles" -s -l 2
publish_test_results:
name: Publish Test Results
needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64]
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Download Test Results
uses: actions/download-artifact@v8
with:
name: unit-test-logs
path: build/tests/**/*.log
pattern: test-results-*
path: test-results
# Best-effort: a read-only token (e.g. fork PRs) can't write the check, so
# don't let a publish failure fail the run. The test jobs gate correctness.
- name: Publish Test Results
if: always()
continue-on-error: true
uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: "ctest_results.xml"
- name: Delete Test Artifact
files: "test-results/**/*.xml"
- name: Delete Test Results
if: success()
uses: geekyeggo/delete-artifact@v6
with:
name: ${{ github.sha }}-tests
name: test-results-*
failOnError: false
flatpak:
name: "Flatpak"
container:

View File

@@ -141,8 +141,26 @@ jobs:
- name: Build slicer mac
if: runner.os == 'macOS' && !inputs.macos-combine-only
working-directory: ${{ github.workspace }}
# arm64 only: build the tests here; the unit_tests_macos job runs them.
env:
ORCA_TESTS_BUILD_ONLY: ${{ inputs.arch == 'arm64' && '1' || '' }}
run: |
./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 ${{ inputs.arch == 'arm64' && '-T' || '' }}
- name: Pack unit tests mac
if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64'
working-directory: ${{ github.workspace }}
run: tar -cvf build_tests.tar build/arm64/tests
- name: Upload Test Artifact mac
if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64'
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-tests-macos-arm64
overwrite: true
path: build_tests.tar
retention-days: 5
if-no-files-found: error
- name: Pack macOS app bundle ${{ inputs.arch }}
if: runner.os == 'macOS' && !inputs.macos-combine-only
@@ -340,11 +358,28 @@ jobs:
# env:
# WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\'
# WindowsSDKVersion: '10.0.26100.0\'
# "tests" builds the unit tests too; the unit_tests_windows_* jobs run them.
run: |
$arch = "${{ inputs.arch }}"
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 } else { .\build_release_vs.bat slicer }
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
shell: pwsh
- name: Pack unit tests Win
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
shell: pwsh
run: tar -cvf build_tests.tar ${{ env.BUILD_DIR }}/tests
- name: Upload Test Artifact Win
if: runner.os == 'Windows'
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-tests-windows-${{ inputs.arch }}
overwrite: true
path: build_tests.tar
retention-days: 5
if-no-files-found: error
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
# x86 emulation, packaging the native arm64 payload from build-arm64.
- name: Create installer Win
@@ -457,31 +492,41 @@ jobs:
if: runner.os == 'Linux'
shell: bash
run: |
# Build + tar the unit tests (-t) only on the leg that runs them: the
# aarch64 leg by default (faster GitHub arm runner), or amd64 when using
# self-hosted runners (no arm self-hosted server). unit_tests downloads
# this tarball. The profile validator is built with -s, so amd64 keeps it.
tests=${{ (!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64') }}
if $tests; then flags=-istrlL; else flags=-isrlL; fi
./build_linux.sh "$flags"
# Build + tar the unit tests (-t) on both Linux legs so each arch
# (x86_64 + aarch64) gets tested by its own unit_tests_linux_* job.
./build_linux.sh -istrlL
./scripts/check_appimage_libs.sh ./build/package ./build/package/bin/orca-slicer
appimage=./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage
mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage "$appimage"
chmod +x "$appimage"
if $tests; then tar -cvpf build_tests.tar build/tests; fi
tar -cvf build_tests.tar build/tests
# Use tar because upload-artifacts won't always preserve directory structure
# and doesn't preserve file permissions
- name: Upload Test Artifact
if: runner.os == 'Linux' && ((!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64'))
if: runner.os == 'Linux'
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-tests
name: ${{ github.sha }}-tests-linux-${{ inputs.arch == 'aarch64' && 'aarch64' || 'x86_64' }}
overwrite: true
path: build_tests.tar
retention-days: 5
if-no-files-found: error
# Ship the freshly-built validator so the parallel slice_check_linux job
# (build_all.yml) can slice-sweep the shipped profiles with this PR's
# engine. Stable sha-based name mirrors the tests artifact above so the
# downstream job downloads it by exact name.
- name: Upload profile validator (for slice check)
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
uses: actions/upload-artifact@v7
with:
name: ${{ github.sha }}-profile-validator-linux-x86_64
overwrite: true
path: ./build/src/Release/OrcaSlicer_profile_validator
retention-days: 5
if-no-files-found: error
- name: Run external slicer regression tests
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
timeout-minutes: 20

View File

@@ -41,7 +41,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
@@ -49,6 +49,15 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_system.log
exit ${PIPESTATUS[0]}
# Slice a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode)
# is expanded - catches undefined-placeholder / invalid-flow bugs the static checks above cannot see.
- name: validate slice (expand custom g-code)
id: validate_slice
continue-on-error: true
run: |
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -s -l 2 2>&1 | tee ${{ runner.temp }}/validate_slice.log
exit ${PIPESTATUS[0]}
# For now run filament subtype check only for BBL profiles until we fix other vendors' profiles.
- name: validate filament subtype check for BBL profiles
id: validate_filament_subtypes
@@ -57,20 +66,6 @@ jobs:
set +e
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -v BBL -f 2>&1 | tee ${{ runner.temp }}/validate_filament_subtypes.log
exit ${PIPESTATUS[0]}
# Flag inherits/compatible_printers/compatible_prints references that point at a deleted or
# renamed preset. Opt-in per vendor for now (via -r); enabled for BBL and Qidi until other
# vendors' profiles are cleaned up. Runs before the custom-preset injection below.
- name: validate preset references for BBL and Qidi profiles
id: validate_preset_references
continue-on-error: true
run: |
set +e
rc=0
for v in BBL Qidi; do
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 -v "$v" -r 2>&1 | tee -a ${{ runner.temp }}/validate_preset_references.log
[ ${PIPESTATUS[0]} -ne 0 ] && rc=1
done
exit $rc
- name: validate custom presets
id: validate_custom
@@ -180,7 +175,7 @@ jobs:
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_preset_references.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
{
# Marker matched by check_profiles_comment.yml to delete prior comments.
@@ -206,6 +201,15 @@ jobs:
echo ""
fi
if [ "${{ steps.validate_slice.outcome }}" = "failure" ]; then
echo "### Slice Validation Failed (custom g-code expansion)"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_slice.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_filament_subtypes.outcome }}" = "failure" ]; then
echo "### BBL Filament Subtype Validation Failed"
echo ""
@@ -215,15 +219,6 @@ jobs:
echo ""
fi
if [ "${{ steps.validate_preset_references.outcome }}" = "failure" ]; then
echo "### BBL/Qidi Preset Reference Validation Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/validate_preset_references.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_custom.outcome }}" = "failure" ]; then
echo "### Custom Preset Validation Failed"
echo ""
@@ -246,7 +241,7 @@ jobs:
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_preset_references.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_slice.outcome == 'failure' || steps.validate_filament_subtypes.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -27,7 +27,7 @@ jobs:
with:
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model claude-sonnet-4-5-20250929"
model: "claude-sonnet-4-5-20250929"
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -79,6 +79,92 @@ jobs:
throw error;
}
localization-pr:
if: github.event_name == 'pull_request_target'
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-latest
steps:
- name: Auto-label and remind about the localization glossary
uses: actions/github-script@v9
with:
script: |
function isPermissionDenied(error) {
return error && error.status === 403 && /Resource not accessible by integration/i.test(error.message || '');
}
const pr = context.payload.pull_request;
// List changed files once (mirrors the `localization/**` paths filter in check_locale.yml)
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
});
const touchesLocalization = files.some((file) => file.filename.startsWith('localization/'));
const onlyPoFiles = files.length > 0 && files.every((file) => file.filename.endsWith('.po'));
// If the PR changes only .po files, automatically apply the Localization label
if (onlyPoFiles) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['Localization']
});
core.info('Applied Localization label (PR changes only .po files).');
} catch (error) {
if (isPermissionDenied(error)) {
core.warning('Cannot add Localization label because token cannot write.');
} else {
throw error;
}
}
}
if (!touchesLocalization) {
core.info('No localization changes detected; skipping glossary reminder.');
return;
}
// Avoid posting the reminder twice (e.g. on reopen)
const marker = '<!-- localization-glossary-reminder -->';
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
per_page: 100
});
if (comments.some((comment) => (comment.body || '').includes(marker))) {
core.info('Glossary reminder already present; skipping.');
return;
}
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body:
`${marker}\n` +
`Hi @${pr.user.login}, this PR changes translations (\`localization/**\`).\n\n` +
`Please make sure recurring terms follow the [Localization glossary](https://www.orcaslicer.com/wiki/localization_glossary), ` +
`so the same English term is always rendered the same way within a language and terms that must stay in English ` +
`(brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.`
});
} catch (error) {
if (isPermissionDenied(error)) {
core.warning('Skipping glossary reminder because token cannot write comments.');
return;
}
throw error;
}
apply-label:
if: github.event_name == 'issue_comment'
permissions:

77
.github/workflows/unit_tests.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Unit Tests
# Download a platform's test artifact, run ctest, and upload the JUnit
# results for aggregation. Called once per arch from build_all.yml.
on:
workflow_call:
inputs:
os:
required: true
type: string
artifact:
description: Test artifact uploaded by the build leg
required: true
type: string
test-dir:
description: Built tests dir; defaults to build/tests, override for arch-separated builds
required: false
type: string
default: build/tests
jobs:
unit_tests:
# Static "Unit Tests"; the per-arch label is the caller's job name, so the
# graph shows e.g. "Windows x64 / Unit Tests".
name: Unit Tests
runs-on: ${{ inputs.os }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
# Tests reach outside tests/ at runtime: tests/data (TEST_DATA_DIR) and
# resources/profiles (PROFILES_DIR) by baked-in absolute path, plus
# resources/info (nozzle data) via resources_dir() during a real slice.
# Check out all of resources/ so no test hits a missing-file path.
sparse-checkout: |
.github
scripts
tests
resources
- name: Apt-Install Dependencies
if: runner.os == 'Linux' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps
- name: Restore Test Artifact
uses: actions/download-artifact@v8
with:
name: ${{ inputs.artifact }}
- uses: lukka/get-cmake@latest
with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true
useCloudCache: true
- name: Unpackage and Run Unit Tests
timeout-minutes: 20
shell: bash
run: |
tar -xvf build_tests.tar
# Multi-config generators (Windows/macOS) need a config; Linux is single-config.
scripts/run_unit_tests.sh "${{ inputs.test-dir }}" "${{ runner.os != 'Linux' && 'Release' || '' }}"
- name: Upload Test Logs
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: unit-test-logs-${{ inputs.artifact }}
path: ${{ inputs.test-dir }}/**/*.log
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-${{ inputs.artifact }}
path: ctest_results.xml
retention-days: 5
if-no-files-found: warn
- name: Delete Test Artifact
if: success()
uses: geekyeggo/delete-artifact@v6
with:
name: ${{ inputs.artifact }}