Compare commits

...

18 Commits

Author SHA1 Message Date
ExPikaPaka
70bb888794 Demo 2026-07-03 09:31:58 +02:00
raistlin7447
adc8763099 fix: crash in Measure tool when a plain edge is the first selection (#14538)
* fix: crash in Measure tool when a plain edge is the first selection

The SPHERE_2 gripper raycaster called get_feature_offset() on
.first.feature instead of .second.feature (copy-pasted from the SPHERE_1
block). Plain planar-border edges store no extra point, so the Edge
branch dereferenced an empty optional behind a release-stripped assert,
aborting on Flatpak and undefined behavior elsewhere.

Point the SPHERE_2 raycaster at .second.feature and fall the Edge branch
back to the edge midpoint.

Fixes #14018
2026-07-03 11:29:28 +08:00
raistlin7447
036bd7bcec feat: add {first_object_name} filename placeholder (#14497)
{input_filename_base} is meant to be the saved project's file name. Before
#13753 a bug made it fall back to the first object's name when a project was
saved; #13753 fixed it to use the project name. Some users relied on the old
behavior to get the part name into their output file name and had no
placeholder to recover it ({model_name} is the 3mf designer metadata, blank
for plain STL imports).

Add {first_object_name} as a dedicated placeholder for the first printable
object on the current plate, populated in update_object_placeholders()
independently of {input_filename_base}.

Closes #14493
2026-07-03 00:14:32 +08:00
raistlin7447
d24e7f75ef fix: crash when rotating the prime tower (#14499)
Selecting the prime tower and rotating it (PageUp/PageDown) crashed.
Selection::notify_instance_update() indexed m_model->objects with the
wipe tower's synthetic id (>= 1000), which is not a ModelObject index,
so the lookup returned garbage and dereferencing it segfaulted.

do_rotate/do_scale/do_mirror already skip the wipe tower in their own
loops but all call this shared helper, so scale and mirror hit the same
fault. Selection::drop() had the same latent bug via a direct index.
Guard both with the >= 1000 check already used throughout the file.

Fixes #14498
2026-07-03 00:08:15 +08:00
sharanchius
0f88b88f3b Updated and fixed the Lithuanian translation v2.4.1 (#14532)
Atnaujintas lietuvių kalbos vertimas v2.4.1
2026-07-02 23:51:55 +08:00
Noisyfox
464a81d585 Support accessing coFloatsOrPercents values in gcode template (#14526)
* Support accessing `coFloatsOrPercents` values in gcode template (OrcaSlicer/OrcaSlicer#14522)

* Vector option values are separated by comma

* Fix wrong cast used for checking nullability
2026-07-02 23:51:20 +08:00
dependabot[bot]
4c58d0adf8 Bump actions/checkout from 6 to 7 (#14517)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-07-02 23:50:31 +08:00
dependabot[bot]
ce6c2ec7ce Bump actions/cache from 5 to 6 (#14516)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 23:50:06 +08:00
Gabriel Monteiro
dbe99d0d6f feat(log): enable console logging in RelWithDebInfo builds (#14439)
* feat(log): enable console logging in RelWithDebInfo builds

* perf(log): make console logging async to avoid blocking startup

* fix(log): gate console sink to RelWithDebInfo builds

---------

Co-authored-by: Gabriel <bielpaess912@gmail.com>
2026-07-02 21:49:51 +08:00
ExPikaPaka
5bccc25705 Fix "plug-in file may be in use" install failure when migrating from older versions (#14373) (#14528)
Fix network plug-in install failing when the plug-in DLL is in use (#14373)

Switching or reinstalling the Bambu network plug-in from a running
OrcaSlicer failed with "The plug-in file may be in use". install_plugin()
deleted each existing file before extracting the new one, and on Windows a
currently-loaded DLL (BambuSource.dll, or the legacy networking library)
cannot be removed or overwritten in place, so the whole install aborted.

Rename an in-use file aside to "<name>.old" before writing the new one: the
running module keeps mapping the renamed file while the new version is
extracted, so the install succeeds without having to unload the plug-in
first. Stale ".old" files are cleaned up at the start of on_init_network(),
before the plug-in is (re)loaded, so they do not accumulate.
2026-07-02 18:58:23 +08:00
SoftFever
a6ccbced03 Feat/update preset validator (#14507)
# Description

This PR expands profile validation so we can catch backward
compatibility issues with custom presets generated by older OrcaSlicer
releases. It also adds missing `renamed_from` metadata for presets that
were renamed or moved, so older user presets can resolve their original
parent names against the current system profiles.

## Background

Many users have reported missing preset issues after upgrading past
2.4.1. Investigation showed two common causes:

- preset lookup and compatibility checks did not always account for
`renamed_from`
- some renamed base presets were missing the old preset name in their
`renamed_from` metadata

The existing profile workflow validates the current system profile tree
and a single nightly-generated custom preset bundle. That is useful for
catching current profile errors, but it does not validate user presets
generated by older OrcaSlicer versions against the current system
profiles. As a result, older missing-parent compatibility gaps can slip
through.

## Changes

- Update `check_profiles.yml` to validate historical custom preset
fixtures from `OrcaSlicer/OrcaSlicer-profile-validator`.
- Download the fixture manifest from the public `fixture-archive`
release.
- Validate each `orca_custom_presets_<version>.zip` fixture
independently against the current PR's `resources/profiles`.
- Generate per-version validation logs and upload them as workflow
artifacts.
- Fail profile validation if any historical fixture version fails.
- Add missing `renamed_from` aliases for renamed/moved presets found by
the historical fixture validation.

## Profile Compatibility Fixes

This PR adds aliases for older parent names including:

- `0.20mm Bambu Support W @BBL X1C` -> `0.20mm Standard @BBL X1C`
- `Bambu PLA Impact @BBL X1C` -> `Bambu PLA Impact @System`
- `Ginger Generic rPLA` -> `Ginger Generic PLA`
- `Ginger Generic rPETG` -> `Ginger Generic PETG`
- legacy `Panchroma PLA Stain` BBL filament names -> current `Panchroma
PLA Satin` names
- legacy Elegoo casing/name variants such as `Elegoo RAPID PLA+`,
`Elegoo RAPID PETG`, `Elegoo RAPID PETG+`, and `Elegoo PETG Pro @System`

## Validation Flow

The custom preset validation step now:

1. Downloads `manifest.json` from the `fixture-archive` release.
2. Iterates over every fixture listed in the manifest.
3. Copies the current branch's `resources/profiles` into a temporary
profile tree.
4. Removes any existing `user` directory from that temporary tree.
5. Unzips exactly one historical fixture into the temporary tree.
6. Runs `OrcaSlicer_profile_validator -p <temp profile tree> -l 2`.
7. Writes a version-specific log and a consolidated summary.

This keeps validation scoped per fixture version and avoids mixing
generated user presets from different OrcaSlicer releases.

## Fixture Source

Historical fixtures are stored as public release assets in:

`OrcaSlicer/OrcaSlicer-profile-validator`, release tag `fixture-archive`

Each release asset is expected to be named like:

```text
orca_custom_presets_v2.4.1.zip
```

## Testing

Validated locally with:

- current system profile validation
- BBL filament subtype validation
- historical custom preset fixture validation
- extra profile JSON check in a clean profile tree

The affected historical fixture set passed after adding the missing
`renamed_from` aliases.

The release manifest controls which fixture versions are validated.
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-07-02 18:47:49 +08:00
SoftFever
c7b28565ef don't upload logs 2026-07-02 18:44:45 +08:00
SoftFever
66b3e27af3 fix "Bambu PLA Impact @BBL X1C" 2026-07-02 18:20:56 +08:00
SoftFever
3bc2c51fe9 revert change in resources\profiles\OrcaFilamentLibrary\filament\Bambu\Bambu PLA Impact @System.json 2026-07-02 17:49:48 +08:00
SoftFever
187beb68c3 fix voron 2026-07-02 17:42:47 +08:00
Ian Chua
7d62ded630 fix: add renamed_from for missing names from 1.9.0 -> 2.2.0 2026-07-02 11:25:22 +08:00
Ian Chua
43a83397d4 fix: restore presets that were renamed based on errors logged by workflow 2026-07-01 18:53:36 +08:00
Ian Chua
ced980e6a8 feat: update preset validator to validate against older generated presets 2026-07-01 18:34:07 +08:00
48 changed files with 10938 additions and 7025 deletions

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Bun
uses: oven-sh/setup-bun@v2

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Bun
uses: oven-sh/setup-bun@v2

View File

@@ -131,7 +131,7 @@ jobs:
if: ${{ !cancelled() && success() }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
sparse-checkout: |
.github
@@ -204,7 +204,7 @@ jobs:
- 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
- uses: actions/checkout@v7
- name: Get the version and date
run: |
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
@@ -223,14 +223,14 @@ jobs:
# 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
uses: actions/cache/restore@v6
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
uses: actions/cache@v6
with:
path: .flatpak-builder
key: flatpak-builder-${{ matrix.variant.arch }}-${{ github.sha }}

View File

@@ -26,7 +26,7 @@ jobs:
valid-cache: ${{ steps.cache_deps.outputs.cache-hit }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
@@ -46,7 +46,7 @@ jobs:
- name: load cache
id: cache_deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ steps.set_outputs.outputs.cache-path }}
key: ${{ steps.set_outputs.outputs.cache-key }}

View File

@@ -34,12 +34,12 @@ jobs:
# Setup the environment
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
- name: load cached deps
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}

View File

@@ -37,13 +37,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
lfs: 'false'
- name: load cached deps
if: ${{ !(runner.os == 'macOS' && inputs.macos-combine-only) }}
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ inputs.cache-path }}
key: ${{ inputs.cache-key }}

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install gettext
run: |

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Run extra JSON check
id: extra_json_check
@@ -77,11 +77,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' }}

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@beta

View File

@@ -26,7 +26,7 @@ jobs:
remove-existing-swap-files: true
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Install Doxygen and Graphviz
run: |

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Cache shellcheck download
id: cache-shellcheck-v0_11
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/shellcheck
key: ${{ runner.os }}-shellcheck-v0_11
@@ -36,7 +36,7 @@ jobs:
tar -xvf ~/sc.tar.xz -C ~
mv ~/shellcheck-"${INPUT_VERSION}"/shellcheck ~/shellcheck
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 1

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup Python
uses: actions/setup-python@v6

238
README.md
View File

@@ -1,237 +1 @@
<div align="center">
<picture>
<img alt="OrcaSlicer logo" src="resources/images/OrcaSlicer.png" width="15%" height="15%">
</picture>
<a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
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
#### Official Website:
<a href="https://www.orcaslicer.com/" style="font-size:2em;">OrcaSlicer.com</a>
#### Github Repository:
<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:
<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>
#### 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>
<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>
</div>
# Main features
- **[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/).
# 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.
- **[Access the wiki here](https://www.orcaslicer.com/wiki)**
- **[Contribute to the wiki](https://www.orcaslicer.com/wiki/how_to_wiki)**
# Download
## Stable Release
📥 **[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
![mac_cant_open](./SoftFever_doc/mac_cant_open.png)
- Step 2: in `System Settings` -> `Privacy & Security`, click `Open Anyway`:
![mac_security_setting](./SoftFever_doc/mac_security_setting.png)
</details>
## Linux
### Flathub (Recommended)
OrcaSlicer is available through FlatHub:
<a href='https://flathub.org/apps/com.orcaslicer.OrcaSlicer'><img width='240' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
Install from the command line:
```shell
flatpak install flathub com.orcaslicer.OrcaSlicer
flatpak run com.orcaslicer.OrcaSlicer
```
It can also be installed through graphical software managers (KDE Discover, GNOME Software, etc.) when Flathub is enabled. Search for **OrcaSlicer** in your software center.
### AppImage
AppImages are published for both **x86_64** and **aarch64** (ARM64). Pick the file matching your CPU — the ARM64 build has `aarch64` in its name (e.g. `OrcaSlicer_Linux_AppImage_Ubuntu2404_aarch64_*.AppImage`).
1. Download App image from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
2. Double click the downloaded file to run it.
3. If you run into trouble executing it, try this command in the terminal:
`chmod +x /path_to_appimage/OrcaSlicer_Linux.AppImage`
# How to Compile
All updated build instructions for Windows, macOS, and Linux are now available on the official [OrcaSlicer Wiki - How to build](https://www.orcaslicer.com/wiki/how_to_build) page.
Please refer to the wiki to ensure you're following the latest and most accurate steps for your platform.
# Klipper Note
If you're running Klipper, it's recommended to add the following configuration to your `printer.cfg` file.
```gcode
# Enable object exclusion
[exclude_object]
# Enable arcs support
[gcode_arcs]
resolution: 0.1
```
# Supports
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
Thank you! :)
## Sponsors
<table>
<tr>
<td>
<a href="https://qidi3d.com/" style="display:inline-block; border-radius:8px; background:#fff;">
<img src="SoftFever_doc\sponsor_logos\QIDI.png" alt="QIDI" width="100" height="100">
</a>
</td>
<td>
<a href="https://bigtree-tech.com/" style="display:inline-block; border-radius:8px; background:#222;">
<img src="SoftFever_doc\sponsor_logos\BigTreeTech.png" alt="BIGTREE TECH" width="100" height="100">
</a>
</td>
</tr>
</table>
## Backers:
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
## Support me
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
<a href="https://paypal.me/softfever3d"><img src="https://img.shields.io/badge/PayPal-003087?style=flat&logo=paypal&logoColor=fff" height="50"></a>
## Some Background
Open-source slicing has always been built on a tradition of collaboration and attribution. [Slic3r](https://github.com/Slic3r/Slic3r), created by Alessandro Ranellucci and the RepRap community, laid the foundation. [PrusaSlicer](https://github.com/prusa3d/PrusaSlicer) by Prusa Research built on Slic3r and acknowledged that heritage. [Bambu Studio](https://github.com/bambulab/BambuStudio) in turn forked from PrusaSlicer, and [SuperSlicer](https://github.com/supermerill/SuperSlicer) by @supermerill extended PrusaSlicer with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before.
OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and ideas inspired by CuraSlicer and SuperSlicer. But it has since grown far beyond its origins. Through relentless innovation — introducing advanced calibration tools, precise wall and seam control, tree supports, adaptive slicing, and hundreds of other features — OrcaSlicer has become the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry.
The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
- The **GNU Affero General Public License**, version 3 ensures that if you use any part of this software in any way (even behind a web server), your software must be released under the same license.
- OrcaSlicer includes a **pressure advance calibration pattern test** adapted from Andrew Ellis' generator, which is licensed under GNU General Public License, version 3. Ellis' generator is itself adapted from a generator developed by Sineos for Marlin, which is licensed under GNU General Public License, version 3.
- The **Bambu networking plugin** is based on non-free libraries from BambuLab. It is optional to the OrcaSlicer and provides extended functionalities for Bambulab printer users.
Remove this branch and PR later

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.20mm Optimal 0.6 nozzle @Anker",
"renamed_from": "0.20mm Optimal 0.6 nozzle @Anker.json",
"inherits": "fdm_process_anker_common_0_6",
"from": "system",
"setting_id": "re5qmcOFJ1OJP3Ip",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Bambu PLA Tough @BBL X1C",
"renamed_from": "Bambu PLA Impact @BBL X1C",
"inherits": "Bambu PLA Tough @base",
"from": "system",
"setting_id": "GFSA09_02",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Panchroma PLA Satin @BBL A1",
"renamed_from": "Panchroma PLA Stain @BBL A1",
"inherits": "Panchroma PLA Satin @base",
"from": "system",
"setting_id": "GFSPM005_00",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Panchroma PLA Satin @BBL A1M",
"renamed_from": "Panchroma PLA Stain @BBL A1M",
"inherits": "Panchroma PLA Satin @base",
"from": "system",
"setting_id": "GFSPM005_02",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Panchroma PLA Satin @BBL P1P",
"renamed_from": "Panchroma PLA Stain @BBL P1P",
"inherits": "Panchroma PLA Satin @base",
"from": "system",
"setting_id": "GFSPM005_04",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Panchroma PLA Satin @BBL X1",
"renamed_from": "Panchroma PLA Stain @BBL X1",
"inherits": "Panchroma PLA Satin @base",
"from": "system",
"setting_id": "GFSPM005_06",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.20mm Standard @BBL X1C",
"renamed_from": "0.20mm Bambu Support W @BBL X1C",
"inherits": "fdm_process_single_0.20",
"from": "system",
"setting_id": "GP004",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.12mm Fine @Creality Ender3V3SE 0.4",
"renamed_from": "0.12mm Fine @Creality Ender3V3SE",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "W68mSPdmat2rCXuD",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.16mm Optimal @Creality Ender3V3SE 0.4",
"renamed_from": "0.16mm Optimal @Creality Ender3V3SE",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "jvnrh3jh6Btbs1Ja",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.20mm Standard @Creality Ender3V3SE 0.4",
"renamed_from": "0.20mm Standard @Creality Ender3V3SE",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "YLkw9eyyK7cm97ek",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.20mm Standard @Creality K1 SE",
"renamed_from": "0.20mm Fast @Creality K1 SE 0.4",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "eR9pRC1qPENNx8U9",
@@ -264,4 +265,4 @@
"wipe_tower_extra_spacing": "100%",
"wipe_tower_rotation_angle": "0",
"wiping_volumes_extruders": "70,70,70,70,70,70,70,70,70,70"
}
}

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.24mm Draft @Creality Ender3V3SE 0.4",
"renamed_from": "0.24mm Draft @Creality Ender3V3SE",
"inherits": "fdm_process_creality_common",
"from": "system",
"setting_id": "Hg10EUNCLMEYYBN1",

View File

@@ -1,6 +1,7 @@
{
"type": "process",
"name": "0.48mm Draft @Creality K1C",
"renamed_from": "0.48mm Draft @Creality K1C (0.8 nozzle)",
"inherits": "fdm_process_common_klipper",
"from": "system",
"setting_id": "qaiff3f8gSQ1GVj1",

View File

@@ -2,6 +2,7 @@
"type": "filament",
"setting_id": "pKzSR8XeyyUDbrNW",
"name": "Generic PETG PRO @Elegoo",
"renamed_from": "Elegoo Generic PETG PRO",
"from": "system",
"instantiation": "true",
"inherits": "Generic PETG @base",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Ginger Generic PETG",
"renamed_from": "Ginger Generic rPETG",
"inherits": "fdm_filament_common",
"from": "system",
"setting_id": "ue95N2e65rdp5K6c",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Ginger Generic PLA",
"renamed_from": "Ginger Generic rPLA",
"inherits": "fdm_filament_common",
"from": "system",
"setting_id": "Z1scjKDBFoDaTa2C",

View File

@@ -18,5 +18,5 @@
"5"
],
"compatible_printers": [],
"renamed_from": "Elegoo PETG PRO"
"renamed_from": "Elegoo PETG PRO;Elegoo PETG Pro @System"
}

View File

@@ -33,5 +33,5 @@
"250"
],
"compatible_printers": [],
"renamed_from": "Elegoo Rapid PETG;Elegoo Rapid PETG+"
"renamed_from": "Elegoo Rapid PETG;Elegoo Rapid PETG+;Elegoo RAPID PETG;Elegoo RAPID PETG+"
}

View File

@@ -45,5 +45,5 @@
"; filament start gcode\n{if (bed_temperature[current_extruder] >55)||(bed_temperature_initial_layer[current_extruder] >55)}M106 P3 S200\n{elsif(bed_temperature[current_extruder] >50)||(bed_temperature_initial_layer[current_extruder] >50)}M106 P3 S150\n{elsif(bed_temperature[current_extruder] >45)||(bed_temperature_initial_layer[current_extruder] >45)}M106 P3 S50\n{endif}\n\n{if activate_air_filtration[current_extruder] && support_air_filtration}\nM106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]} \n{endif}"
],
"compatible_printers": [],
"renamed_from": "Elegoo Rapid PLA+"
"renamed_from": "Elegoo Rapid PLA+;Elegoo RAPID PLA+"
}

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Peopoly Generic PLA",
"renamed_from": "Peopoly Generic PLA 0.8 nozzle",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "KNsVV4dvEWAAkzDE",

View File

@@ -1,6 +1,7 @@
{
"type": "filament",
"name": "Snapmaker PLA",
"renamed_from": "PolyLite PLA",
"inherits": "Snapmaker PLA @base",
"from": "system",
"setting_id": "cW1b4nGxE9yXIXJP",

View File

@@ -206,6 +206,10 @@
"name": "0.24mm Fine 0.8 nozzle @Voron",
"sub_path": "process/0.24mm Fine 0.8 nozzle @Voron.json"
},
{
"name": "0.32mm Optimal 0.8 nozzle @Voron",
"sub_path": "process/0.32mm Optimal 0.8 nozzle @Voron.json"
},
{
"name": "0.40mm Standard 0.8 nozzle @Voron",
"sub_path": "process/0.40mm Standard 0.8 nozzle @Voron.json"

View File

@@ -0,0 +1,11 @@
{
"type": "process",
"name": "0.32mm Optimal 0.8 nozzle @Voron",
"inherits": "fdm_process_voron_common_0_8",
"from": "system",
"setting_id": "ivS6U4AuIoj1cJhZ",
"instantiation": "true",
"bottom_shell_layers": "3",
"top_shell_layers": "3",
"layer_height": "0.32"
}

View File

@@ -184,9 +184,12 @@ if (WIN32)
if(MSVC)
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
endif()
target_compile_definitions(OrcaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE)
target_compile_definitions(OrcaSlicer_app_gui PRIVATE "$<$<NOT:$<CONFIG:RelWithDebInfo>>:SLIC3R_WRAPPER_NOCONSOLE>")
add_dependencies(OrcaSlicer_app_gui OrcaSlicer)
set_target_properties(OrcaSlicer_app_gui PROPERTIES OUTPUT_NAME "orca-slicer")
set_target_properties(OrcaSlicer_app_gui PROPERTIES
OUTPUT_NAME "orca-slicer"
WIN32_EXECUTABLE "$<NOT:$<CONFIG:RelWithDebInfo>>"
)
target_link_libraries(OrcaSlicer_app_gui PRIVATE boost_headeronly)
endif ()

View File

@@ -534,6 +534,7 @@ endif ()
encoding_check(libslic3r)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})

View File

@@ -836,9 +836,11 @@ namespace client
static std::map<std::string, std::string> tag_to_error_message;
size_t get_extruder_id() const {
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
return filament_map_opt->values[current_extruder_id];
if (external_config != nullptr) {
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
return filament_map_opt->values[current_extruder_id];
}
}
return 0;
}
@@ -1096,27 +1098,109 @@ namespace client
const ConfigOptionVectorBase* vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
if (vec->empty())
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
// Helper to resolve a FloatOrPercent value (handles ratio_over chain for percent values).
// elem_index: the element index used to access this vector element, so that
// parent vectors (via ratio_over) use the same index rather than the current extruder.
auto resolve_float_or_percent = [ctx, &opt, &output](const FloatOrPercent &fop, size_t elem_index) {
std::string opt_key(opt.it_range.begin(), opt.it_range.end());
if (boost::ends_with(opt_key, "line_width")) {
// Line width supports defaults and a complex graph of dependencies.
output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast<unsigned int>(ctx->current_extruder_id)));
} else if (! fop.percent) {
// Not a percent, just return the value.
output.set_d(fop.value);
} else {
// Resolve dependencies using the "ratio_over" link to a parent value.
const ConfigOptionDef *opt_def = print_config_def.get(opt_key);
assert(opt_def != nullptr);
double v = fop.value * 0.01; // percent to ratio
for (;;) {
const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over);
if (opt_parent == nullptr)
ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range);
if (boost::ends_with(opt_def->ratio_over, "line_width")) {
// Line width supports defaults and a complex graph of dependencies.
assert(opt_parent->type() == coFloatOrPercent);
v *= Flow::extrusion_width(opt_def->ratio_over, static_cast<const ConfigOptionFloatOrPercent*>(opt_parent), *ctx, static_cast<unsigned int>(ctx->current_extruder_id));
break;
}
if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) {
v *= opt_parent->getFloat();
if (opt_parent->type() == coFloat || ! static_cast<const ConfigOptionFloatOrPercent*>(opt_parent)->percent)
break;
v *= 0.01; // percent to ratio
} else if (opt_parent->type() == coFloats) {
// Vector parent: extract the value for the current extruder.
const ConfigOptionFloatsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt_parent);
if (parent_nullable) {
v *= (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
} else {
const ConfigOptionFloats *parent_vec = static_cast<const ConfigOptionFloats *>(opt_parent);
v *= (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
}
break;
} else if (opt_parent->type() == coFloatsOrPercents) {
// Vector parent with percent support: extract the FloatOrPercent for the current extruder.
const ConfigOptionFloatsOrPercentsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt_parent);
if (parent_nullable) {
const FloatOrPercent &parent_fop = (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
if (! parent_fop.percent) {
v *= parent_fop.value;
break;
}
v *= parent_fop.value * 0.01; // percent to ratio
} else {
const ConfigOptionFloatsOrPercents *parent_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt_parent);
const FloatOrPercent &parent_fop = (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
if (! parent_fop.percent) {
v *= parent_fop.value;
break;
}
v *= parent_fop.value * 0.01; // percent to ratio
}
}
// Continue one level up in the "ratio_over" hierarchy.
opt_def = print_config_def.get(opt_def->ratio_over);
assert(opt_def != nullptr);
}
output.set_d(v);
}
};
if (!opt.has_index()) {
// Allow omitting extruder id when referencing vectors
switch (opt.opt->type()) {
case coFloats: {
const ConfigOptionFloatsNullable* opt_floatsnullable = static_cast<const ConfigOptionFloatsNullable *>(opt.opt);
const ConfigOptionFloatsNullable* opt_floatsnullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt.opt);
if (opt_floatsnullable) {
if (opt_floatsnullable->size() == 1) { // old version
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(0));
output.set_d(opt_floatsnullable->get_at(0));
} else {
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(ctx->get_extruder_id()));
output.set_d(opt_floatsnullable->get_at(ctx->get_extruder_id()));
}
} else {
const ConfigOptionFloats* opt_floats = static_cast<const ConfigOptionFloats*>(opt.opt);
if (opt_floats->size() == 1) { // old version
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(0));
output.set_d(opt_floats->get_at(0));
} else {
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(ctx->get_extruder_id()));
output.set_d(opt_floats->get_at(ctx->get_extruder_id()));
}
}
break;
}
case coFloatsOrPercents: {
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
if (opt_vec_nullable) {
size_t elem_index = (opt_vec_nullable->size() == 1) ? 0 : ctx->get_extruder_id();
resolve_float_or_percent(opt_vec_nullable->get_at(elem_index), elem_index);
} else {
const ConfigOptionFloatsOrPercents *opt_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt);
size_t elem_index = (opt_vec->size() == 1) ? 0 : ctx->get_extruder_id();
resolve_float_or_percent(opt_vec->get_at(elem_index), elem_index);
}
break;
}
default: ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
}
} else {
@@ -1131,6 +1215,15 @@ namespace client
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints*>(opt.opt)->values[idx])); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools*>(opt.opt)->values[idx] != 0); break;
case coEnums: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
case coFloatsOrPercents: {
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
if (opt_vec_nullable) {
resolve_float_or_percent(opt_vec_nullable->values[idx], idx);
} else {
resolve_float_or_percent(static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt)->values[idx], idx);
}
break;
}
default:
ctx->throw_exception("Unsupported vector variable type", opt.it_range);
}

View File

@@ -21,11 +21,12 @@ void PrintTryCancel::operator()()
size_t PrintStateBase::g_last_timestamp = 0;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects.
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
{
// get the first input file name
std::string input_file;
std::string first_object_name;
std::vector<std::string> v_scale;
int num_objects = 0;
int num_instances = 0;
@@ -38,6 +39,8 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
}
if (printable) {
++ num_objects;
if (num_objects == 1)
first_object_name = model_object->name;
// CHECK_ME -> Is the following correct ?
v_scale.push_back("x:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(X) * 100) +
"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) +
@@ -51,6 +54,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
config.set_key_value("num_instances", new ConfigOptionInt(num_instances));
config.set_key_value("scale", new ConfigOptionStrings(v_scale));
config.set_key_value("first_object_name", new ConfigOptionString(first_object_name));
if (! input_file.empty()) {
// get basename with and without suffix
const std::string input_filename = boost::filesystem::path(input_file).filename().string();

View File

@@ -194,6 +194,7 @@ std::string debug_out_path(const char *name, ...);
// smaller level means less log. level=5 means saving all logs.
void set_log_path_and_level(const std::string& file, unsigned int level);
void flush_logs();
void shutdown_console_logging();
boost::filesystem::path get_log_file_name();
// A special type for strings encoded in the local Windows 8-bit code page.

View File

@@ -5,6 +5,7 @@
#include <locale>
#include <ctime>
#include <cstdarg>
#include <iostream>
#include <stdio.h>
#include <filesystem>
@@ -46,14 +47,19 @@
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/async_frontend.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/core/null_deleter.hpp>
#include <boost/locale.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
@@ -174,6 +180,7 @@ unsigned get_logging_level()
}
boost::shared_ptr<boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend>> g_log_sink;
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> g_console_log_sink;
// Force set_logging_level(<=error) after loading of the DLL.
// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter
@@ -346,6 +353,19 @@ namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace keywords = boost::log::keywords;
namespace attrs = boost::log::attributes;
namespace sinks = boost::log::sinks;
void shutdown_console_logging()
{
if (!g_console_log_sink)
return;
auto console_sink = g_console_log_sink;
boost::log::core::get()->remove_sink(console_sink);
console_sink->stop();
g_console_log_sink.reset();
}
void set_log_path_and_level(const std::string& file, unsigned int level)
{
#ifdef __APPLE__
@@ -377,6 +397,24 @@ void set_log_path_and_level(const std::string& file, unsigned int level)
keywords::auto_flush = true
);
shutdown_console_logging();
#ifdef SLIC3R_CONSOLE_LOG
auto console_backend = boost::make_shared<sinks::text_ostream_backend>();
console_backend->add_stream(boost::shared_ptr<std::ostream>(&std::cout, boost::null_deleter()));
console_backend->auto_flush(true);
g_console_log_sink = boost::make_shared<sinks::asynchronous_sink<sinks::text_ostream_backend>>(console_backend);
g_console_log_sink->set_formatter(
expr::stream
<< "[" << expr::attr< logging::trivial::severity_level >("Severity") << "]\t"
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f") << " "
<<"[Thread " << expr::attr<attrs::current_thread_id::value_type>("ThreadID") << "]"
<< ": " << expr::smessage
);
boost::log::core::get()->add_sink(g_console_log_sink);
#endif
logging::add_common_attributes();
set_logging_level(level);

View File

@@ -1458,8 +1458,32 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
boost::filesystem::create_directories(dest_path.parent_path());
std::string dest_zip_file = encode_path(dest_path.string().c_str());
try {
if (fs::exists(dest_path))
fs::remove(dest_path);
if (fs::exists(dest_path)) {
boost::system::error_code ec;
fs::remove(dest_path, ec);
if (ec) {
// On Windows a currently-loaded DLL (e.g. BambuSource.dll, or the
// networking library in legacy mode) cannot be deleted or overwritten
// in place, which failed the whole install with "The plug-in file may
// be in use" (issue #14373). It CAN however be renamed aside: the
// running module keeps mapping the renamed file while we write the new
// one. The stale ".old" copy is cleared on the next install/launch.
boost::filesystem::path aside = dest_path;
aside += ".old";
boost::system::error_code ec2;
fs::remove(aside, ec2);
fs::rename(dest_path, aside, ec2);
if (ec2) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[install_plugin] cannot replace in-use file "
<< dest_path.string() << ": " << ec2.message();
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
BOOST_LOG_TRIVIAL(warning) << "[install_plugin] " << dest_path.filename().string()
<< " was in use, renamed aside to .old";
}
}
mz_bool res = 0;
#ifndef WIN32
if (S_ISLNK(stat.m_external_attr >> 16)) {
@@ -2237,6 +2261,7 @@ GUI_App::~GUI_App()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
shutdown_console_logging();
}
bool GUI_App::is_blocking_printing(MachineObject *obj_)
@@ -3345,6 +3370,26 @@ void GUI_App::copy_network_if_available()
bool GUI_App::on_init_network(bool try_backup)
{
// Clean up stale ".old" files left by install_plugin() when it had to rename an in-use
// DLL aside (see the rename-aside path in install_plugin). This runs before the plug-in
// is (re)loaded - at startup nothing is mapped yet, and on a hot reload the previous
// module has already been unloaded - so the previously locked files can now be removed.
{
boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir()) / "plugins";
boost::system::error_code ec;
if (boost::filesystem::is_directory(plugin_folder, ec)) {
for (boost::filesystem::directory_iterator it(plugin_folder, ec), end; !ec && it != end; it.increment(ec)) {
if (it->path().extension() == ".old") {
boost::system::error_code rm_ec;
boost::filesystem::remove(it->path(), rm_ec);
if (rm_ec)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": could not remove stale " << it->path().filename().string()
<< " (" << rm_ec.message() << "), will retry next launch";
}
}
}
}
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
std::string config_version = app_config->get_network_plugin_version();

View File

@@ -91,9 +91,14 @@ Vec3d GLGizmoMeasure::get_feature_offset(const Measure::SurfaceFeature &feature)
}
case Measure::SurfaceFeatureType::Edge:
{
std::optional<Vec3d> p = feature.get_extra_point();
assert(p.has_value());
ret = *p;
// Only polygon edges store an extra point (the polygon centre); plain edges have none.
const std::optional<Vec3d> extra = feature.get_extra_point();
if (extra.has_value())
ret = *extra;
else {
const auto [pt1, pt2] = feature.get_edge();
ret = 0.5 * (pt1 + pt2);
}
break;
}
case Measure::SurfaceFeatureType::Point:
@@ -1065,7 +1070,7 @@ void GLGizmoMeasure::on_render()
if (requires_raycaster_update) {
if (m_gripper_id_raycast_map.find(GripperType::SPHERE_2) != m_gripper_id_raycast_map.end()) {
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) *
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.second.feature)) *
Geometry::scale_transform(inv_zoom));
}
}

View File

@@ -535,6 +535,9 @@ void Selection::drop()
for (unsigned int i : m_list) {
GLVolume& volume = *(*m_volumes)[i];
// Skip the wipe tower: its synthetic id (>= 1000) is not an index into m_model->objects.
if (volume.object_idx() >= 1000)
continue;
ModelObject* model_object = m_model->objects[volume.object_idx()];
if (model_object != nullptr) {
@@ -1848,6 +1851,9 @@ void Selection::notify_instance_update(int object_idx, int instance_idx)
for (unsigned int i : m_list)
{
int obj_index = (*m_volumes)[i]->object_idx();
// Skip the wipe tower: its synthetic id (>= 1000) is not an index into m_model->objects.
if (obj_index >= 1000)
continue;
//-1 means all the instance in this object
if (instance_idx == -1)
{

View File

@@ -183,6 +183,74 @@ void trigger_precise_wall_warning(DynamicPrintConfig& c)
} // namespace
// ---------------------------------------------------------------------------
// {first_object_name} filename placeholder
// ---------------------------------------------------------------------------
namespace {
// Add a printable 20mm cube named `name` to `model`; returns it so the caller can tweak it.
ModelObject* add_named_cube(Model& model, const std::string& name)
{
ModelObject* obj = model.add_object();
obj->name = name;
obj->add_volume(make_cube(20.0, 20.0, 20.0));
obj->add_instance();
obj->ensure_on_bed();
return obj;
}
// Resolve `format` to an output file name for a print of `model`. `filename_base`, when set,
// is the saved-project name passed to output_filename().
std::string resolved_output_name(Model& model, const std::string& format, const std::string& filename_base = {})
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_key_value("filename_format", new ConfigOptionString(format));
Print print;
for (ModelObject* obj : model.objects)
print.auto_assign_extruders(obj);
print.apply(model, config);
return print.output_filename(filename_base);
}
} // namespace
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
{
Model model;
SECTION("uses the object's name") {
add_named_cube(model, "WidgetPart");
CHECK(resolved_output_name(model, "{first_object_name}") == "WidgetPart.gcode");
}
SECTION("picks the first when several objects are printable") {
add_named_cube(model, "FirstPart");
add_named_cube(model, "SecondPart");
CHECK(resolved_output_name(model, "{first_object_name}") == "FirstPart.gcode");
}
SECTION("skips objects outside the print volume (e.g. on another plate)") {
// First in model order, but not on the current plate, so is_printable() is false.
add_named_cube(model, "OtherPlatePart")->instances.front()->print_volume_state = ModelInstancePVS_Fully_Outside;
add_named_cube(model, "OnPlatePart");
CHECK(resolved_output_name(model, "{first_object_name}") == "OnPlatePart.gcode");
}
SECTION("is empty when the object has no name") {
add_named_cube(model, "");
CHECK(resolved_output_name(model, "part_{first_object_name}") == "part_.gcode");
}
}
TEST_CASE("Print: {first_object_name} is not replaced by the saved-project file name", "[Print]")
{
// Passing a saved-project file name as the filename_base must not change {first_object_name}.
Model model;
add_named_cube(model, "WidgetPart");
CHECK(resolved_output_name(model, "{first_object_name}", "SavedProject") == "WidgetPart.gcode");
}
TEST_CASE("Print::validate stacks independent warnings", "[Print][validate]")
{
// Two unrelated checks (region precise-wall + machine acceleration) must each

View File

@@ -11,8 +11,8 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
config.set_deserialize_strict( {
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "nozzle_temperature", "357;359;363;378" }
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
{ "nozzle_temperature", "357,359,363,378" }
});
// To test the "min_width_top_surface" over "inner_wall_line_width".
config.option<ConfigOptionFloatOrPercent>("inner_wall_line_width")->value = 150.;
@@ -121,8 +121,8 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
config.set_deserialize_strict({
{ "filament_notes", "testnotes" },
{ "enable_pressure_advance", "1" },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "nozzle_temperature", "357;359;363;378" }
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
{ "nozzle_temperature", "357,359,363,378" }
});
PlaceholderParser::ContextData context_with_global_dict;
@@ -241,3 +241,97 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
}
SECTION("if else completely empty") { REQUIRE(parser.process("{if false then elsif false then else endif}", 0, nullptr, nullptr, nullptr) == ""); }
}
SCENARIO("Placeholder parser coFloatsOrPercents vector access", "[PlaceholderParser]") {
PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config();
// outer_wall_speed is the ratio_over target for small_perimeter_speed.
// Different values per extruder to verify parent resolves at the same element index.
config.set_deserialize_strict({
{ "outer_wall_speed", "60,70,80,90" },
{ "nozzle_diameter", "0.4,0.4,0.4,0.4" },
{ "pressure_advance", "1.5,2.0,3.0,4.0" } // coFloats non-nullable
});
// small_perimeter_speed:
// [0] = 50% of outer_wall_speed[0] (= 60) → 30
// [1] = 80% of outer_wall_speed[1] (= 70) → 56
// [2] = 0 absolute
// [3] = 50% of outer_wall_speed[3] (= 90) → 45
config.option<ConfigOptionFloatsOrPercentsNullable>("small_perimeter_speed")->values = {
FloatOrPercent{50.0, true}, // 50% of outer_wall_speed[0] (60) = 30
FloatOrPercent{80.0, true}, // 80% of outer_wall_speed[1] (70) = 56
FloatOrPercent{0.0, false}, // absolute: 0
FloatOrPercent{50.0, true}, // 50% of outer_wall_speed[3] (90) = 45
};
parser.apply_config(config);
parser.set("foo", 0);
parser.set("bar", 1);
parser.set("baz", 3);
parser.set("num_extruders", 4);
SECTION("Indexed access - percent resolved against parent at same index [0]") {
// 50% of outer_wall_speed[0] (60) = 30
REQUIRE(std::stod(parser.process("{small_perimeter_speed[0]}")) == Catch::Approx(30.0));
}
SECTION("Indexed access - percent resolved against parent at same index [1]") {
// 80% of outer_wall_speed[1] (70) = 56
REQUIRE(std::stod(parser.process("{small_perimeter_speed[1]}")) == Catch::Approx(56.0));
}
SECTION("Indexed access - percent resolved against parent at same index [3]") {
// 50% of outer_wall_speed[3] (90) = 45
REQUIRE(std::stod(parser.process("{small_perimeter_speed[3]}")) == Catch::Approx(45.0));
}
SECTION("Variable-indexed access via foo (=0) - percent value") {
// 50% of outer_wall_speed[0] (60) = 30
REQUIRE(std::stod(parser.process("{small_perimeter_speed[foo]}")) == Catch::Approx(30.0));
}
SECTION("Variable-indexed access via bar (=1) - percent value") {
// 80% of outer_wall_speed[1] (70) = 56
REQUIRE(std::stod(parser.process("{small_perimeter_speed[bar]}")) == Catch::Approx(56.0));
}
SECTION("Variable-indexed access via baz (=3) - percent value") {
// 50% of outer_wall_speed[3] (90) = 45
REQUIRE(std::stod(parser.process("{small_perimeter_speed[baz]}")) == Catch::Approx(45.0));
}
SECTION("Literal-indexed access - absolute value") {
REQUIRE(std::stod(parser.process("{small_perimeter_speed[2]}")) == Catch::Approx(0.0));
}
SECTION("No-index (extruder-based) access - percent resolved via current extruder") {
// Extruder 0 = 50% of outer_wall_speed[0] (60) = 30
REQUIRE(std::stod(parser.process("{small_perimeter_speed}")) == Catch::Approx(30.0));
}
SECTION("Out-of-range index clamps to index 0") {
// Index 99 is out of range, clamps to 0: 50% of outer_wall_speed[0] (60) = 30
REQUIRE(std::stod(parser.process("{small_perimeter_speed[99]}")) == Catch::Approx(30.0));
}
SECTION("coFloats no-index access - nullable (outer_wall_speed)") {
// outer_wall_speed is ConfigOptionFloatsNullable, exercises the 'if' branch
REQUIRE(std::stod(parser.process("{outer_wall_speed}")) == Catch::Approx(60.0));
}
SECTION("coFloats indexed access - nullable") {
// outer_wall_speed[2] = 80
REQUIRE(std::stod(parser.process("{outer_wall_speed[2]}")) == Catch::Approx(80.0));
}
SECTION("coFloats no-index access - non-nullable (pressure_advance)") {
// pressure_advance is ConfigOptionFloats (non-nullable), exercises the 'else' branch
REQUIRE(std::stod(parser.process("{pressure_advance}")) == Catch::Approx(1.5));
}
SECTION("coFloats indexed access - non-nullable") {
// pressure_advance[2] = 3.0
REQUIRE(std::stod(parser.process("{pressure_advance[2]}")) == Catch::Approx(3.0));
}
}