mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Merge branch 'main' into fix/plugin-install-state
This commit is contained in:
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
|
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.
|
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
|
||||||
<h3>
|
|
||||||
|
|
||||||
# Official links and community
|
# Official links and community
|
||||||
|
|
||||||
@@ -90,11 +89,22 @@ Visit our GitHub Releases page for the latest stable version of OrcaSlicer, reco
|
|||||||
🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/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.
|
Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated.
|
||||||
|
|
||||||
|
### Belt Printer Builds
|
||||||
|
|
||||||
|
The [nightly release](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds) ships **two parallel builds**: the standard build and a belt-printer build. Both are attached to the same release — tell them apart by the filename suffix:
|
||||||
|
|
||||||
|
- **Standard** — no suffix (e.g. `OrcaSlicer_Windows_Installer_x64_nightly.exe`)
|
||||||
|
- **Belt** — `_belt` suffix (e.g. `OrcaSlicer_Windows_Installer_x64_nightly_belt.exe`)
|
||||||
|
|
||||||
|
The `_belt` builds add **experimental support for belt / conveyor (infinite-Z) printers**, where the model is sliced against a tilted belt surface instead of a flat horizontal bed. They include ready-to-use belt printer profiles, the full belt slicing pipeline (mesh rotation and G-code transforms), belt-aware support generation, and a tilted-bed preview.
|
||||||
|
|
||||||
|
> ⚠️ Belt printer support is under active development and is **not yet merged into `main`** — it currently ships only in these parallel `_belt` builds, produced from the [`belt-printer`](https://github.com/OrcaSlicer/OrcaSlicer/tree/belt-printer) branch. See tracking PR [#14394](https://github.com/OrcaSlicer/OrcaSlicer/pull/14394) and the original documentation in [#12998](https://github.com/OrcaSlicer/OrcaSlicer/pull/12998).
|
||||||
|
|
||||||
# How to install
|
# How to install
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
|
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases). Both `x64` and `arm64` installers are published — pick the one matching your CPU.
|
||||||
|
|
||||||
- *For convenience there is also a portable build available.*
|
- *For convenience there is also a portable build available.*
|
||||||
<details>
|
<details>
|
||||||
@@ -109,7 +119,7 @@ Download the **Windows Installer exe** for your preferred version from the [rel
|
|||||||
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
|
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
Windows Package Manager
|
### Windows Package Manager
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
winget install --id=SoftFever.OrcaSlicer -e
|
winget install --id=SoftFever.OrcaSlicer -e
|
||||||
@@ -117,7 +127,7 @@ winget install --id=SoftFever.OrcaSlicer -e
|
|||||||
|
|
||||||
## Mac
|
## Mac
|
||||||
|
|
||||||
1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU.
|
1. Download the universal DMG, which runs on both Apple Silicon and Intel Macs.
|
||||||
2. Drag OrcaSlicer.app to Application folder.
|
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:*
|
3. *If you want to run a build from a PR, you also need to follow the instructions below:*
|
||||||
|
|
||||||
@@ -142,6 +152,14 @@ winget install --id=SoftFever.OrcaSlicer -e
|
|||||||

|

|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
### Homebrew Cask
|
||||||
|
|
||||||
|
```shell
|
||||||
|
brew install --cask orcaslicer
|
||||||
|
```
|
||||||
|
|
||||||
|
The [Homebrew cask](https://formulae.brew.sh/cask/orcaslicer) installs the official macOS DMG from [GitHub Releases](https://github.com/OrcaSlicer/OrcaSlicer/releases).
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
### Flathub (Recommended)
|
### Flathub (Recommended)
|
||||||
@@ -190,8 +208,8 @@ resolution: 0.1
|
|||||||
|
|
||||||
# Supports
|
# Supports
|
||||||
|
|
||||||
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
|
**OrcaSlicer** is an open-source project, and we're deeply grateful to all our sponsors and backers.
|
||||||
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
|
Their generous support helps fund filaments and other essential 3D printing materials for the project.
|
||||||
Thank you! :)
|
Thank you! :)
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
@@ -215,7 +233,7 @@ Thank you! :)
|
|||||||
|
|
||||||
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
|
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
|
||||||
|
|
||||||
## Support me
|
## Support the project
|
||||||
|
|
||||||
<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://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://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>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,22 +18,22 @@ msgstr ""
|
|||||||
"X-Generator: Poedit 3.9\n"
|
"X-Generator: Poedit 3.9\n"
|
||||||
|
|
||||||
msgid "Main Extruder"
|
msgid "Main Extruder"
|
||||||
msgstr ""
|
msgstr "主挤出机"
|
||||||
|
|
||||||
msgid "Main extruder"
|
msgid "Main extruder"
|
||||||
msgstr ""
|
msgstr "主挤出机"
|
||||||
|
|
||||||
msgid "main extruder"
|
msgid "main extruder"
|
||||||
msgstr ""
|
msgstr "主挤出机"
|
||||||
|
|
||||||
msgid "Auxiliary Extruder"
|
msgid "Auxiliary Extruder"
|
||||||
msgstr ""
|
msgstr "辅助挤出机"
|
||||||
|
|
||||||
msgid "Auxiliary extruder"
|
msgid "Auxiliary extruder"
|
||||||
msgstr ""
|
msgstr "辅助挤出机"
|
||||||
|
|
||||||
msgid "auxiliary extruder"
|
msgid "auxiliary extruder"
|
||||||
msgstr ""
|
msgstr "辅助挤出机"
|
||||||
|
|
||||||
msgid "Left Extruder"
|
msgid "Left Extruder"
|
||||||
msgstr "左挤出机"
|
msgstr "左挤出机"
|
||||||
@@ -54,22 +54,22 @@ msgid "right extruder"
|
|||||||
msgstr "右侧挤出机"
|
msgstr "右侧挤出机"
|
||||||
|
|
||||||
msgid "Main Nozzle"
|
msgid "Main Nozzle"
|
||||||
msgstr ""
|
msgstr "主喷嘴"
|
||||||
|
|
||||||
msgid "Main nozzle"
|
msgid "Main nozzle"
|
||||||
msgstr ""
|
msgstr "主喷嘴"
|
||||||
|
|
||||||
msgid "main nozzle"
|
msgid "main nozzle"
|
||||||
msgstr ""
|
msgstr "主喷嘴"
|
||||||
|
|
||||||
msgid "Auxiliary Nozzle"
|
msgid "Auxiliary Nozzle"
|
||||||
msgstr ""
|
msgstr "辅助喷嘴"
|
||||||
|
|
||||||
msgid "Auxiliary nozzle"
|
msgid "Auxiliary nozzle"
|
||||||
msgstr ""
|
msgstr "辅助喷嘴"
|
||||||
|
|
||||||
msgid "auxiliary nozzle"
|
msgid "auxiliary nozzle"
|
||||||
msgstr ""
|
msgstr "辅助喷嘴"
|
||||||
|
|
||||||
msgid "Left Nozzle"
|
msgid "Left Nozzle"
|
||||||
msgstr "左喷嘴"
|
msgstr "左喷嘴"
|
||||||
@@ -90,52 +90,52 @@ msgid "right nozzle"
|
|||||||
msgstr "右喷嘴"
|
msgstr "右喷嘴"
|
||||||
|
|
||||||
msgid "Main Hotend"
|
msgid "Main Hotend"
|
||||||
msgstr ""
|
msgstr "主热端"
|
||||||
|
|
||||||
msgid "Main hotend"
|
msgid "Main hotend"
|
||||||
msgstr ""
|
msgstr "主热端"
|
||||||
|
|
||||||
msgid "main hotend"
|
msgid "main hotend"
|
||||||
msgstr ""
|
msgstr "主热端"
|
||||||
|
|
||||||
msgid "Auxiliary Hotend"
|
msgid "Auxiliary Hotend"
|
||||||
msgstr ""
|
msgstr "辅助热端"
|
||||||
|
|
||||||
msgid "Auxiliary hotend"
|
msgid "Auxiliary hotend"
|
||||||
msgstr ""
|
msgstr "辅助热端"
|
||||||
|
|
||||||
msgid "auxiliary hotend"
|
msgid "auxiliary hotend"
|
||||||
msgstr ""
|
msgstr "辅助热端"
|
||||||
|
|
||||||
msgid "Left Hotend"
|
msgid "Left Hotend"
|
||||||
msgstr ""
|
msgstr "左热端"
|
||||||
|
|
||||||
msgid "Left hotend"
|
msgid "Left hotend"
|
||||||
msgstr ""
|
msgstr "左热端"
|
||||||
|
|
||||||
msgid "left hotend"
|
msgid "left hotend"
|
||||||
msgstr ""
|
msgstr "左热端"
|
||||||
|
|
||||||
msgid "Right Hotend"
|
msgid "Right Hotend"
|
||||||
msgstr ""
|
msgstr "右热端"
|
||||||
|
|
||||||
msgid "Right hotend"
|
msgid "Right hotend"
|
||||||
msgstr ""
|
msgstr "右热端"
|
||||||
|
|
||||||
msgid "right hotend"
|
msgid "right hotend"
|
||||||
msgstr ""
|
msgstr "右热端"
|
||||||
|
|
||||||
msgid "main"
|
msgid "main"
|
||||||
msgstr ""
|
msgstr "主"
|
||||||
|
|
||||||
msgid "auxiliary"
|
msgid "auxiliary"
|
||||||
msgstr ""
|
msgstr "辅助"
|
||||||
|
|
||||||
msgid "Main"
|
msgid "Main"
|
||||||
msgstr ""
|
msgstr "主"
|
||||||
|
|
||||||
msgid "Auxiliary"
|
msgid "Auxiliary"
|
||||||
msgstr ""
|
msgstr "辅助"
|
||||||
|
|
||||||
msgid "left"
|
msgid "left"
|
||||||
msgstr "左"
|
msgstr "左"
|
||||||
@@ -153,28 +153,28 @@ msgid "extruder"
|
|||||||
msgstr "挤出机"
|
msgstr "挤出机"
|
||||||
|
|
||||||
msgid "TPU is not supported by AMS."
|
msgid "TPU is not supported by AMS."
|
||||||
msgstr "AMS不支持TPU耗材。"
|
msgstr "AMS 不支持 TPU 耗材。"
|
||||||
|
|
||||||
msgid "AMS does not support 'Bambu Lab PET-CF'."
|
msgid "AMS does not support 'Bambu Lab PET-CF'."
|
||||||
msgstr "AMS不支持Bambu Lab PET-CF耗材。"
|
msgstr "AMS不支持Bambu Lab PET-CF耗材。"
|
||||||
|
|
||||||
msgid "The current filament doesn't support the E3D high-flow nozzle and can't be used."
|
msgid "The current filament doesn't support the E3D high-flow nozzle and can't be used."
|
||||||
msgstr ""
|
msgstr "当前的耗材不支持 E3D 高流量喷嘴,因此无法使用。"
|
||||||
|
|
||||||
msgid "The current filament doesn't support the TPU high-flow nozzle and can't be used."
|
msgid "The current filament doesn't support the TPU high-flow nozzle and can't be used."
|
||||||
msgstr ""
|
msgstr "当前的耗材不支持 TPU 高流量喷嘴,因此无法使用。"
|
||||||
|
|
||||||
msgid "Auto dynamic flow calibration is not supported for TPU filament."
|
msgid "Auto dynamic flow calibration is not supported for TPU filament."
|
||||||
msgstr ""
|
msgstr "自动流量校准不支持 TPU 耗材"
|
||||||
|
|
||||||
msgid "Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles."
|
msgid "Bambu TPU 85A is not supported for printing with 0.4 mm Standard or High Flow nozzles."
|
||||||
msgstr ""
|
msgstr "Bambu TPU 85A 不支持使用 0.4 毫米标准或高流动性喷嘴进行打印。"
|
||||||
|
|
||||||
msgid "How to feed TPU filament."
|
msgid "How to feed TPU filament."
|
||||||
msgstr ""
|
msgstr "如何装入 TPU 耗材。"
|
||||||
|
|
||||||
msgid "How to feed TPU filament on X2D."
|
msgid "How to feed TPU filament on X2D."
|
||||||
msgstr ""
|
msgstr "如何为 X2D 装载 TPU 耗材。"
|
||||||
|
|
||||||
msgid "Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer."
|
msgid "Please cold pull before printing TPU to avoid clogging. You may use cold pull maintenance on the printer."
|
||||||
msgstr "打印TPU前请先执行冷拉以避免堵塞。您可以使用打印机上的冷拉维护功能。"
|
msgstr "打印TPU前请先执行冷拉以避免堵塞。您可以使用打印机上的冷拉维护功能。"
|
||||||
@@ -189,7 +189,7 @@ msgid "The rough surface of PLA Glow can accelerate wear on the AMS system, part
|
|||||||
msgstr "泛光PLA的粗糙表面可能会加速AMS的磨损,尤其是AMS Lite的内部组件。"
|
msgstr "泛光PLA的粗糙表面可能会加速AMS的磨损,尤其是AMS Lite的内部组件。"
|
||||||
|
|
||||||
msgid "PLA Glow may wear the AMS first stage feeder. Use an external spool instead."
|
msgid "PLA Glow may wear the AMS first stage feeder. Use an external spool instead."
|
||||||
msgstr ""
|
msgstr "PLA Glow 可能导致 AMS 一级进料器过度磨损,建议改用外置料盘。"
|
||||||
|
|
||||||
msgid "CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution."
|
msgid "CF/GF filaments are hard and brittle, it's easy to break or get stuck in AMS, please use with caution."
|
||||||
msgstr "CF/GF耗材丝又硬又脆,在AMS中很容易断裂或卡住,请谨慎使用。"
|
msgstr "CF/GF耗材丝又硬又脆,在AMS中很容易断裂或卡住,请谨慎使用。"
|
||||||
@@ -201,23 +201,23 @@ msgid "PPA-CF is brittle and could break in bended PTFE tube above Toolhead."
|
|||||||
msgstr "PPA-CF材质较脆,用在工具头上方的弯曲PTFE管中可能会断裂。"
|
msgstr "PPA-CF材质较脆,用在工具头上方的弯曲PTFE管中可能会断裂。"
|
||||||
|
|
||||||
msgid "Default settings may affect print quality. Adjust as needed for best results."
|
msgid "Default settings may affect print quality. Adjust as needed for best results."
|
||||||
msgstr ""
|
msgstr "默认参数可能影响打印质量,请根据需要进行调整,以获得最佳效果。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."
|
msgid "%s has a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."
|
||||||
msgstr ""
|
msgstr "%s 在使用 0.4 mm 高流量喷嘴时存在堵头风险,谨慎使用。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."
|
msgid "%s filaments are hard and brittle and could break in AMS, and there is also a risk of nozzle clogging when using 0.4mm high-flow nozzles. Use with caution."
|
||||||
msgstr ""
|
msgstr "%s 耗材质地较硬且较脆,在 AMS 中使用时可能发生断裂,使用 0.4 mm 高流量喷嘴时还存在堵头风险,谨慎使用。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution."
|
msgid "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution."
|
||||||
msgstr ""
|
msgstr "%s 在使用 0.4 / 0.6 / 0.8 mm 高流量喷嘴时存在堵头风险,谨慎使用。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue."
|
msgid "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue."
|
||||||
msgstr ""
|
msgstr "%s 可能因耗材变轨器而无法装入或退料。如果要继续。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s is not supported by %s extruder."
|
msgid "%s is not supported by %s extruder."
|
||||||
@@ -225,19 +225,19 @@ msgstr "%s 不被 %s 挤出机支持。"
|
|||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"
|
msgid "There may be critical print quality issues when printing '%s' with %s Bowden extruder. Use with caution!"
|
||||||
msgstr ""
|
msgstr "'%s' 在 %s 远程挤出机上打印时,可能会出现严重的打印质量问题。谨慎使用!"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"
|
msgid "There may be critical print quality issues when printing '%s' with %s extruder. Use with caution!"
|
||||||
msgstr ""
|
msgstr "'%s' 在 %s 挤出机上打印时,可能会出现严重的打印质量问题。谨慎使用!"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."
|
msgid "There may be print quality issues when printing '%s' with %s Bowden extruder. Use with caution."
|
||||||
msgstr ""
|
msgstr "'%s' 在 %s 远程挤出机上打印时,可能会出现打印质量问题。谨慎使用。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "There may be print quality issues when printing '%s' with %s extruder. Use with caution."
|
msgid "There may be print quality issues when printing '%s' with %s extruder. Use with caution."
|
||||||
msgstr ""
|
msgstr "'%s' 在 %s 挤出机上打印时,可能会出现打印质量问题。谨慎使用。"
|
||||||
|
|
||||||
msgid "High Flow"
|
msgid "High Flow"
|
||||||
msgstr "高流量"
|
msgstr "高流量"
|
||||||
@@ -246,7 +246,7 @@ msgid "Standard"
|
|||||||
msgstr "标准"
|
msgstr "标准"
|
||||||
|
|
||||||
msgid "TPU High Flow"
|
msgid "TPU High Flow"
|
||||||
msgstr "TPU高流量"
|
msgstr "TPU 高流量"
|
||||||
|
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "未定义"
|
msgstr "未定义"
|
||||||
@@ -282,7 +282,7 @@ msgid "During the hotend upgrade, the toolhead will move. Don't reach into the c
|
|||||||
msgstr "在热端升级过程中,工具头会移动。请勿将手伸入机箱内。"
|
msgstr "在热端升级过程中,工具头会移动。请勿将手伸入机箱内。"
|
||||||
|
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr ""
|
msgstr "更新"
|
||||||
|
|
||||||
msgid "Current AMS humidity"
|
msgid "Current AMS humidity"
|
||||||
msgstr "当前AMS湿度"
|
msgstr "当前AMS湿度"
|
||||||
@@ -345,7 +345,7 @@ msgid "Please wait"
|
|||||||
msgstr "请稍候"
|
msgstr "请稍候"
|
||||||
|
|
||||||
msgid "Reading"
|
msgid "Reading"
|
||||||
msgstr ""
|
msgstr "读取中"
|
||||||
|
|
||||||
msgid "Running..."
|
msgid "Running..."
|
||||||
msgstr "运行中..."
|
msgstr "运行中..."
|
||||||
@@ -369,7 +369,7 @@ msgid "Refresh"
|
|||||||
msgstr "刷新"
|
msgstr "刷新"
|
||||||
|
|
||||||
msgid "Refreshing"
|
msgid "Refreshing"
|
||||||
msgstr "清爽"
|
msgstr "刷新中"
|
||||||
|
|
||||||
msgid "Hotend status abnormal, unavailable at present. Please upgrade the firmware and try again."
|
msgid "Hotend status abnormal, unavailable at present. Please upgrade the firmware and try again."
|
||||||
msgstr "热端状态异常,目前不可用。请升级固件后重试。"
|
msgstr "热端状态异常,目前不可用。请升级固件后重试。"
|
||||||
@@ -645,7 +645,7 @@ msgstr "错误:请先关闭所有工具栏菜单"
|
|||||||
|
|
||||||
msgctxt "inches"
|
msgctxt "inches"
|
||||||
msgid "in"
|
msgid "in"
|
||||||
msgstr ""
|
msgstr "in"
|
||||||
|
|
||||||
msgid "mm"
|
msgid "mm"
|
||||||
msgstr "mm"
|
msgstr "mm"
|
||||||
@@ -718,13 +718,13 @@ msgid "Part"
|
|||||||
msgstr "零件"
|
msgstr "零件"
|
||||||
|
|
||||||
msgid "Relative"
|
msgid "Relative"
|
||||||
msgstr ""
|
msgstr "相对"
|
||||||
|
|
||||||
msgid "Coordinate system used for transform actions."
|
msgid "Coordinate system used for transform actions."
|
||||||
msgstr ""
|
msgstr "用于变换操作的坐标系"
|
||||||
|
|
||||||
msgid "Absolute"
|
msgid "Absolute"
|
||||||
msgstr ""
|
msgstr "绝对"
|
||||||
|
|
||||||
msgid "Reset current rotation to the value when open the rotation tool."
|
msgid "Reset current rotation to the value when open the rotation tool."
|
||||||
msgstr "重置当前旋转为打开旋转工具时的值"
|
msgstr "重置当前旋转为打开旋转工具时的值"
|
||||||
@@ -2191,31 +2191,39 @@ msgid "The version of Orca Slicer is too low and needs to be updated to the late
|
|||||||
msgstr "此逆戟鲸切片器的版本过低,需更新至最新版本方可正常使用"
|
msgstr "此逆戟鲸切片器的版本过低,需更新至最新版本方可正常使用"
|
||||||
|
|
||||||
msgid "Cloud sync conflict:"
|
msgid "Cloud sync conflict:"
|
||||||
msgstr ""
|
msgstr "云同步冲突:"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Cloud sync conflict for preset \"%s\":"
|
msgid "Cloud sync conflict for preset \"%s\":"
|
||||||
msgstr ""
|
msgstr "预设 \"%s\" 云同步冲突:"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"This preset has a newer version in OrcaCloud.\n"
|
"This preset has a newer version in OrcaCloud.\n"
|
||||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"该预设在 OrcaCloud 中有更新版本。\n"
|
||||||
|
"拉取会下载云端副本。强制推送会用您的本地预设覆盖该副本"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"A preset with this name already exists in OrcaCloud.\n"
|
"A preset with this name already exists in OrcaCloud.\n"
|
||||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"OrcaCloud 中已经存在一个同名的预设。\n"
|
||||||
|
"拉取会下载云端副本。强制推送会用您的本地预设覆盖该副本。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"A preset with the same name was previously deleted from the cloud.\n"
|
"A preset with the same name was previously deleted from the cloud.\n"
|
||||||
"Delete will delete your local preset. Force push overwrites it with your local preset."
|
"Delete will delete your local preset. Force push overwrites it with your local preset."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"之前已从云端删除了一个同名的预设。\n"
|
||||||
|
"删除将删除您的本地预设。强制推送会用您的本地预设覆盖该预设。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"There was an unexpected or unidentified preset conflict.\n"
|
"There was an unexpected or unidentified preset conflict.\n"
|
||||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
"Pull downloads the cloud copy. Force push overwrites it with your local preset."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"发生了一个意料之外或无法识别的预设冲突。\n"
|
||||||
|
"拉取会下载云端副本。强制推送会用您的本地预设覆盖该副本。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Force push will overwrite the cloud copy with your local preset changes.\n"
|
"Force push will overwrite the cloud copy with your local preset changes.\n"
|
||||||
@@ -2229,21 +2237,23 @@ msgid ""
|
|||||||
"Force push will overwrite the cloud copy of preset \"%s\" with your local changes.\n"
|
"Force push will overwrite the cloud copy of preset \"%s\" with your local changes.\n"
|
||||||
"Do you want to continue?"
|
"Do you want to continue?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"强制推送将用您的本地更改覆盖预设 \"%s\" 的云端副本。\n"
|
||||||
|
"你希望继续吗?"
|
||||||
|
|
||||||
msgid "Resolve cloud sync conflict"
|
msgid "Resolve cloud sync conflict"
|
||||||
msgstr "解决云同步冲突"
|
msgstr "解决云同步冲突"
|
||||||
|
|
||||||
msgid "Syncing your account…"
|
msgid "Syncing your account…"
|
||||||
msgstr ""
|
msgstr "同步账号中..."
|
||||||
|
|
||||||
msgid "Migrating presets…"
|
msgid "Migrating presets…"
|
||||||
msgstr ""
|
msgstr "迁移预设中..."
|
||||||
|
|
||||||
msgid "Fetching plugins…"
|
msgid "Fetching plugins…"
|
||||||
msgstr ""
|
msgstr "获取插件中..."
|
||||||
|
|
||||||
msgid "Loading user presets…"
|
msgid "Loading user presets…"
|
||||||
msgstr ""
|
msgstr "加载用户预设中..."
|
||||||
|
|
||||||
msgid "Retrieving printer information, please try again later."
|
msgid "Retrieving printer information, please try again later."
|
||||||
msgstr "正在获取打印机信息,请稍后重试。"
|
msgstr "正在获取打印机信息,请稍后重试。"
|
||||||
@@ -2345,7 +2355,7 @@ msgid "Loading user preset"
|
|||||||
msgstr "正在加载用户预设"
|
msgstr "正在加载用户预设"
|
||||||
|
|
||||||
msgid "There is an update available. Open the preset bundle dialog to update it."
|
msgid "There is an update available. Open the preset bundle dialog to update it."
|
||||||
msgstr ""
|
msgstr "有更新可用。打开预设包对话框进行更新。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s has been removed."
|
msgid "%s has been removed."
|
||||||
@@ -2362,7 +2372,7 @@ msgstr "语言"
|
|||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Switching Orca Slicer to language %s failed."
|
msgid "Switching Orca Slicer to language %s failed."
|
||||||
msgstr ""
|
msgstr "切换 Orca Slicer 语言到 %s 失败。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
@@ -2370,25 +2380,27 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Orca Slicer - Switching language failed"
|
msgid "Orca Slicer - Switching language failed"
|
||||||
msgstr ""
|
msgstr "Orca Slicer - 切换语言失败"
|
||||||
|
|
||||||
msgid "*"
|
msgid "*"
|
||||||
msgstr "*"
|
msgstr "*"
|
||||||
|
|
||||||
msgid "Plugins"
|
msgid "Plugins"
|
||||||
msgstr ""
|
msgstr "插件"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Failed to open the Plugins dialog:\n"
|
"Failed to open the Plugins dialog:\n"
|
||||||
"%s"
|
"%s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"无法打开插件对话框:\n"
|
||||||
|
"%s"
|
||||||
|
|
||||||
msgid "Failed to open the Plugins dialog (unknown error)."
|
msgid "Failed to open the Plugins dialog (unknown error)."
|
||||||
msgstr ""
|
msgstr "无法打开插件对话框(未知错误)。"
|
||||||
|
|
||||||
msgid "Plugin Terminal"
|
msgid "Plugin Terminal"
|
||||||
msgstr ""
|
msgstr "插件终端"
|
||||||
|
|
||||||
msgid "Changing application language"
|
msgid "Changing application language"
|
||||||
msgstr "正在为应用程序切换语言"
|
msgstr "正在为应用程序切换语言"
|
||||||
@@ -2575,7 +2587,7 @@ msgid "OrcaSliced Combo"
|
|||||||
msgstr "OrcaSliced 复合模型"
|
msgstr "OrcaSliced 复合模型"
|
||||||
|
|
||||||
msgid "Orca Badge"
|
msgid "Orca Badge"
|
||||||
msgstr ""
|
msgstr "Orca 徽章"
|
||||||
|
|
||||||
msgid "Orca Tolerance Test"
|
msgid "Orca Tolerance Test"
|
||||||
msgstr "Orca误差测试"
|
msgstr "Orca误差测试"
|
||||||
@@ -2794,13 +2806,13 @@ msgid "Edit text"
|
|||||||
msgstr "编辑文字"
|
msgstr "编辑文字"
|
||||||
|
|
||||||
msgid "Ability to change text, font, size, ..."
|
msgid "Ability to change text, font, size, ..."
|
||||||
msgstr "能够更改文本、字体、大小。。。"
|
msgstr "能够更改文本、字体、大小 ..."
|
||||||
|
|
||||||
msgid "Edit SVG"
|
msgid "Edit SVG"
|
||||||
msgstr "编辑SVG"
|
msgstr "编辑SVG"
|
||||||
|
|
||||||
msgid "Change SVG source file, projection, size, ..."
|
msgid "Change SVG source file, projection, size, ..."
|
||||||
msgstr "改变SVG源文件,投影,大小,..."
|
msgstr "改变SVG源文件、投影、大小 ..."
|
||||||
|
|
||||||
msgid "Invalidate cut info"
|
msgid "Invalidate cut info"
|
||||||
msgstr "解除切割关系"
|
msgstr "解除切割关系"
|
||||||
@@ -3021,43 +3033,43 @@ msgid "Click the icon to shift this object to the bed"
|
|||||||
msgstr "点击这个图标可将对象移动到热床上"
|
msgstr "点击这个图标可将对象移动到热床上"
|
||||||
|
|
||||||
msgid "Rename Object"
|
msgid "Rename Object"
|
||||||
msgstr ""
|
msgstr "重命名对象"
|
||||||
|
|
||||||
msgid "Rename Part"
|
msgid "Rename Part"
|
||||||
msgstr ""
|
msgstr "重命名零件"
|
||||||
|
|
||||||
msgid "Paste settings"
|
msgid "Paste settings"
|
||||||
msgstr ""
|
msgstr "粘贴设置"
|
||||||
|
|
||||||
msgid "Shift objects to bed"
|
msgid "Shift objects to bed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Object order changed"
|
msgid "Object order changed"
|
||||||
msgstr ""
|
msgstr "对象顺序已更改"
|
||||||
|
|
||||||
msgid "Layer setting added"
|
msgid "Layer setting added"
|
||||||
msgstr ""
|
msgstr "层设置已添加"
|
||||||
|
|
||||||
msgid "Part setting added"
|
msgid "Part setting added"
|
||||||
msgstr ""
|
msgstr "零件设置已添加"
|
||||||
|
|
||||||
msgid "Object setting added"
|
msgid "Object setting added"
|
||||||
msgstr ""
|
msgstr "对象设置已添加"
|
||||||
|
|
||||||
msgid "Height range settings added"
|
msgid "Height range settings added"
|
||||||
msgstr ""
|
msgstr "高度范围设置已添加"
|
||||||
|
|
||||||
msgid "Part settings added"
|
msgid "Part settings added"
|
||||||
msgstr ""
|
msgstr "零件设置已添加"
|
||||||
|
|
||||||
msgid "Object settings added"
|
msgid "Object settings added"
|
||||||
msgstr ""
|
msgstr "对象设置已添加"
|
||||||
|
|
||||||
msgid "Load Part"
|
msgid "Load Part"
|
||||||
msgstr ""
|
msgstr "加载零件"
|
||||||
|
|
||||||
msgid "Load Modifier"
|
msgid "Load Modifier"
|
||||||
msgstr ""
|
msgstr "加载修改器"
|
||||||
|
|
||||||
msgid "Loading file"
|
msgid "Loading file"
|
||||||
msgstr "载入文件中"
|
msgstr "载入文件中"
|
||||||
@@ -3084,7 +3096,7 @@ msgid "Remove paint-on fuzzy skin"
|
|||||||
msgstr "移除手绘绒毛表面"
|
msgstr "移除手绘绒毛表面"
|
||||||
|
|
||||||
msgid "Delete Settings"
|
msgid "Delete Settings"
|
||||||
msgstr ""
|
msgstr "删除设置"
|
||||||
|
|
||||||
msgid "Remove height range"
|
msgid "Remove height range"
|
||||||
msgstr "移除高度范围"
|
msgstr "移除高度范围"
|
||||||
@@ -3119,7 +3131,7 @@ msgid "Deleting the last solid part is not allowed."
|
|||||||
msgstr "不允许删除对象的最后一个实体零件。"
|
msgstr "不允许删除对象的最后一个实体零件。"
|
||||||
|
|
||||||
msgid "Delete part"
|
msgid "Delete part"
|
||||||
msgstr ""
|
msgstr "删除零件"
|
||||||
|
|
||||||
msgid "The target object contains only one part and can not be split."
|
msgid "The target object contains only one part and can not be split."
|
||||||
msgstr "目标对象只包含一个零件,无法分割"
|
msgstr "目标对象只包含一个零件,无法分割"
|
||||||
@@ -3131,10 +3143,10 @@ msgid "Assembly"
|
|||||||
msgstr "组合体"
|
msgstr "组合体"
|
||||||
|
|
||||||
msgid "Merge parts to an object"
|
msgid "Merge parts to an object"
|
||||||
msgstr ""
|
msgstr "合并零件到对象"
|
||||||
|
|
||||||
msgid "Add layers"
|
msgid "Add layers"
|
||||||
msgstr ""
|
msgstr "添加层"
|
||||||
|
|
||||||
msgid "Cut Connectors information"
|
msgid "Cut Connectors information"
|
||||||
msgstr "切割连接件信息"
|
msgstr "切割连接件信息"
|
||||||
@@ -3170,7 +3182,7 @@ msgid "Settings for height range"
|
|||||||
msgstr "高度范围设置"
|
msgstr "高度范围设置"
|
||||||
|
|
||||||
msgid "Delete selected"
|
msgid "Delete selected"
|
||||||
msgstr ""
|
msgstr "删除选中"
|
||||||
|
|
||||||
msgid "Layer"
|
msgid "Layer"
|
||||||
msgstr "层"
|
msgstr "层"
|
||||||
@@ -3195,7 +3207,7 @@ msgid "Choose part type"
|
|||||||
msgstr "选择零件类型"
|
msgstr "选择零件类型"
|
||||||
|
|
||||||
msgid "Instances to Separated Objects"
|
msgid "Instances to Separated Objects"
|
||||||
msgstr ""
|
msgstr "实例拆分为对象"
|
||||||
|
|
||||||
msgid "Enter new name"
|
msgid "Enter new name"
|
||||||
msgstr "输入新名称"
|
msgstr "输入新名称"
|
||||||
@@ -3223,7 +3235,7 @@ msgid "\"%s\" part's mesh contains errors. Please repair it first."
|
|||||||
msgstr "\"%s\" 零件的网格包含错误,请先修复。"
|
msgstr "\"%s\" 零件的网格包含错误,请先修复。"
|
||||||
|
|
||||||
msgid "Change Filaments"
|
msgid "Change Filaments"
|
||||||
msgstr ""
|
msgstr "更改耗材"
|
||||||
|
|
||||||
msgid "Additional process preset"
|
msgid "Additional process preset"
|
||||||
msgstr "附加工艺预设"
|
msgstr "附加工艺预设"
|
||||||
@@ -3445,7 +3457,7 @@ msgid "Filament type is unknown which is required to perform this action. Please
|
|||||||
msgstr "因耗材类型未知,所以需要执行此操作。请指定目标耗材的信息。"
|
msgstr "因耗材类型未知,所以需要执行此操作。请指定目标耗材的信息。"
|
||||||
|
|
||||||
msgid "AMS has not been initialized. Please initialize it before use."
|
msgid "AMS has not been initialized. Please initialize it before use."
|
||||||
msgstr ""
|
msgstr "AMS 尚未初始化。请在使用前进行初始化。"
|
||||||
|
|
||||||
msgid "Changing fan speed during printing may affect print quality, please choose carefully."
|
msgid "Changing fan speed during printing may affect print quality, please choose carefully."
|
||||||
msgstr "在打印过程中更改风扇速度可能影响打印质量,请谨慎选择。"
|
msgstr "在打印过程中更改风扇速度可能影响打印质量,请谨慎选择。"
|
||||||
@@ -3570,22 +3582,22 @@ msgid "Check filament location"
|
|||||||
msgstr "检查耗材丝位置"
|
msgstr "检查耗材丝位置"
|
||||||
|
|
||||||
msgid "Switch"
|
msgid "Switch"
|
||||||
msgstr ""
|
msgstr "切换"
|
||||||
|
|
||||||
msgid "hotend"
|
msgid "hotend"
|
||||||
msgstr ""
|
msgstr "热端"
|
||||||
|
|
||||||
msgid "Wait for AMS cooling"
|
msgid "Wait for AMS cooling"
|
||||||
msgstr ""
|
msgstr "等待 AMS 冷却"
|
||||||
|
|
||||||
msgid "Switch current filament at Filament Track Switch"
|
msgid "Switch current filament at Filament Track Switch"
|
||||||
msgstr ""
|
msgstr "切换变轨器的当前耗材"
|
||||||
|
|
||||||
msgid "Pull back current filament at Filament Track Switch"
|
msgid "Pull back current filament at Filament Track Switch"
|
||||||
msgstr ""
|
msgstr "退回变轨器的当前耗材"
|
||||||
|
|
||||||
msgid "Switch track at Filament Track Switch"
|
msgid "Switch track at Filament Track Switch"
|
||||||
msgstr ""
|
msgstr "变轨器开关切换"
|
||||||
|
|
||||||
msgid "The maximum temperature cannot exceed "
|
msgid "The maximum temperature cannot exceed "
|
||||||
msgstr "最高温度不可超过 "
|
msgstr "最高温度不可超过 "
|
||||||
@@ -3640,7 +3652,7 @@ msgid "Launch troubleshoot center"
|
|||||||
msgstr "启动故障排查中心"
|
msgstr "启动故障排查中心"
|
||||||
|
|
||||||
msgid "Set nozzle count"
|
msgid "Set nozzle count"
|
||||||
msgstr ""
|
msgstr "设置喷嘴数量"
|
||||||
|
|
||||||
msgid "Please set nozzle count"
|
msgid "Please set nozzle count"
|
||||||
msgstr "请设置喷嘴数量"
|
msgstr "请设置喷嘴数量"
|
||||||
@@ -3693,7 +3705,7 @@ msgid "Ignore"
|
|||||||
msgstr "忽略"
|
msgstr "忽略"
|
||||||
|
|
||||||
msgid "Done."
|
msgid "Done."
|
||||||
msgstr ""
|
msgstr "完成"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"All the selected objects are on a locked plate.\n"
|
"All the selected objects are on a locked plate.\n"
|
||||||
@@ -4126,7 +4138,7 @@ msgid "Bed temperature"
|
|||||||
msgstr "热床温度"
|
msgstr "热床温度"
|
||||||
|
|
||||||
msgid "mm³"
|
msgid "mm³"
|
||||||
msgstr "立方毫米"
|
msgstr "mm³"
|
||||||
|
|
||||||
# TODO: Review, changed by lang refactor. PR 14254
|
# TODO: Review, changed by lang refactor. PR 14254
|
||||||
msgid "Start"
|
msgid "Start"
|
||||||
@@ -7390,7 +7402,7 @@ msgid "Bottom"
|
|||||||
msgstr "底部"
|
msgstr "底部"
|
||||||
|
|
||||||
msgid "Plugin Selection"
|
msgid "Plugin Selection"
|
||||||
msgstr ""
|
msgstr "插件选择"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"No plugins capabilities available for this type.\n"
|
"No plugins capabilities available for this type.\n"
|
||||||
@@ -7401,13 +7413,13 @@ msgid "There is stringing-prone filament in the current print job. Enabling nozz
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Enable Nozzle Clumping Detection"
|
msgid "Enable Nozzle Clumping Detection"
|
||||||
msgstr ""
|
msgstr "裹头检测"
|
||||||
|
|
||||||
msgid "When enabled, the printer will automatically capture photos of printed parts and upload them to the cloud. Would you like to enable this option?"
|
msgid "When enabled, the printer will automatically capture photos of printed parts and upload them to the cloud. Would you like to enable this option?"
|
||||||
msgstr ""
|
msgstr "启用此功能后,打印机将自动拍摄打印件的照片并将其上传至云端。是否要启用此选项?"
|
||||||
|
|
||||||
msgid "Confirm Enable Print Status Snapshot"
|
msgid "Confirm Enable Print Status Snapshot"
|
||||||
msgstr ""
|
msgstr "确认启用打印状态快照"
|
||||||
|
|
||||||
msgid "Enable detection of build plate position"
|
msgid "Enable detection of build plate position"
|
||||||
msgstr "启用打印板位置检测"
|
msgstr "启用打印板位置检测"
|
||||||
@@ -7423,19 +7435,19 @@ msgid "Identifies the type and position of the build plate on the heatbed. Pausi
|
|||||||
msgstr "识别打印平台板在热床上的类型和位置。如果检测到不匹配,则暂停打印。"
|
msgstr "识别打印平台板在热床上的类型和位置。如果检测到不匹配,则暂停打印。"
|
||||||
|
|
||||||
msgid "Purifies the chamber air as the print finishes, based on the selected mode."
|
msgid "Purifies the chamber air as the print finishes, based on the selected mode."
|
||||||
msgstr ""
|
msgstr "每次打印结束时,根据所选方式净化打印机的腔体空气。"
|
||||||
|
|
||||||
msgid "Purifies the chamber air through internal circulation as each print finishes."
|
msgid "Purifies the chamber air through internal circulation as each print finishes."
|
||||||
msgstr ""
|
msgstr "每次打印结束时,通过内循环净化打印机的腔体空气。"
|
||||||
|
|
||||||
msgid "Automatically match the corresponding switch strategy for leak-prone filaments (disable blob detection) and regular filaments (enable blob detection)."
|
msgid "Automatically match the corresponding switch strategy for leak-prone filaments (disable blob detection) and regular filaments (enable blob detection)."
|
||||||
msgstr ""
|
msgstr "对易拉丝耗材(关闭裹头检测)与常规耗材(开启裹头检测)自动匹配相应的开关策略。"
|
||||||
|
|
||||||
msgid "Detect whether the nozzle is wrapped by filament or other foreign matter."
|
msgid "Detect whether the nozzle is wrapped by filament or other foreign matter."
|
||||||
msgstr ""
|
msgstr "检测喷嘴是否被耗材或其他异物包裹住。"
|
||||||
|
|
||||||
msgid "After disabling, nozzle wrapping cannot be detected, which may lead to print failure or nozzle damage."
|
msgid "After disabling, nozzle wrapping cannot be detected, which may lead to print failure or nozzle damage."
|
||||||
msgstr ""
|
msgstr "关闭后无法发现喷嘴包裹,可能导致打印失败或喷嘴损坏。"
|
||||||
|
|
||||||
msgid "AI Detections"
|
msgid "AI Detections"
|
||||||
msgstr "人工智能检测"
|
msgstr "人工智能检测"
|
||||||
@@ -7481,7 +7493,7 @@ msgid "Store Sent Files on External Storage"
|
|||||||
msgstr "将发送的文件存储在外部存储器上"
|
msgstr "将发送的文件存储在外部存储器上"
|
||||||
|
|
||||||
msgid "Save the printing files sent from the slicer and other apps on External Storage"
|
msgid "Save the printing files sent from the slicer and other apps on External Storage"
|
||||||
msgstr ""
|
msgstr "将切片软件和其他应用程序发送的打印文件保存到外部存储中"
|
||||||
|
|
||||||
msgid "Allow Prompt Sound"
|
msgid "Allow Prompt Sound"
|
||||||
msgstr "允许提示音"
|
msgstr "允许提示音"
|
||||||
@@ -7494,31 +7506,31 @@ msgid "Check if the nozzle is clumping by filament or other foreign objects."
|
|||||||
msgstr "检查喷嘴是否被耗材丝或其他异物裹住"
|
msgstr "检查喷嘴是否被耗材丝或其他异物裹住"
|
||||||
|
|
||||||
msgid "Purify Air at Print End"
|
msgid "Purify Air at Print End"
|
||||||
msgstr ""
|
msgstr "打印结束时净化空气"
|
||||||
|
|
||||||
msgid "Internal Circulation"
|
msgid "Internal Circulation"
|
||||||
msgstr ""
|
msgstr "内循环"
|
||||||
|
|
||||||
msgid "Alignment Detection"
|
msgid "Alignment Detection"
|
||||||
msgstr ""
|
msgstr "偏移检测"
|
||||||
|
|
||||||
msgid "Pauses printing when build plate misalignment is detected."
|
msgid "Pauses printing when build plate misalignment is detected."
|
||||||
msgstr ""
|
msgstr "若检测到打印板偏移,则暂停打印。"
|
||||||
|
|
||||||
msgid "Foreign Object Detection"
|
msgid "Foreign Object Detection"
|
||||||
msgstr ""
|
msgstr "异物检测"
|
||||||
|
|
||||||
msgid "Checks for any objects on the build plate at the start of a print to avoid collisions."
|
msgid "Checks for any objects on the build plate at the start of a print to avoid collisions."
|
||||||
msgstr ""
|
msgstr "打印开始时,检测打印板上是否有异物,避免打印过程中出现碰撞。"
|
||||||
|
|
||||||
msgid "Printed Part Displacement Detection"
|
msgid "Printed Part Displacement Detection"
|
||||||
msgstr ""
|
msgstr "打印件位移检测"
|
||||||
|
|
||||||
msgid "Monitors the printed part during printing and alerts immediately if it shifts or collapses."
|
msgid "Monitors the printed part during printing and alerts immediately if it shifts or collapses."
|
||||||
msgstr ""
|
msgstr "检测打印中的打印件状态,若发生移动或倒塌则立即提醒。"
|
||||||
|
|
||||||
msgid "Checks if the nozzle is clumping by filament or other foreign objects."
|
msgid "Checks if the nozzle is clumping by filament or other foreign objects."
|
||||||
msgstr ""
|
msgstr "检查喷嘴是否被耗材或其他异物堵塞。"
|
||||||
|
|
||||||
msgid "On"
|
msgid "On"
|
||||||
msgstr "开"
|
msgstr "开"
|
||||||
@@ -7533,10 +7545,10 @@ msgid "Pause printing"
|
|||||||
msgstr "暂停打印"
|
msgstr "暂停打印"
|
||||||
|
|
||||||
msgid "Print Status Snapshot"
|
msgid "Print Status Snapshot"
|
||||||
msgstr ""
|
msgstr "打印状态快照"
|
||||||
|
|
||||||
msgid "Automatically capture and upload print photos, showing defects during printing and the final result for remote viewing."
|
msgid "Automatically capture and upload print photos, showing defects during printing and the final result for remote viewing."
|
||||||
msgstr ""
|
msgstr "自动拍摄打印件照片并上传云端,包括打印过程中检测到的缺陷和任务结束时的打印件状态,便于远程查看。"
|
||||||
|
|
||||||
msgctxt "Nozzle Type"
|
msgctxt "Nozzle Type"
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
@@ -7560,7 +7572,7 @@ msgid "High flow"
|
|||||||
msgstr "高流量"
|
msgstr "高流量"
|
||||||
|
|
||||||
msgid "TPU High flow"
|
msgid "TPU High flow"
|
||||||
msgstr ""
|
msgstr "TPU 高流量"
|
||||||
|
|
||||||
msgid "No wiki link available for this printer."
|
msgid "No wiki link available for this printer."
|
||||||
msgstr "没有适用于此打印机的 wiki 链接。"
|
msgstr "没有适用于此打印机的 wiki 链接。"
|
||||||
@@ -7679,7 +7691,7 @@ msgid "Not installed"
|
|||||||
msgstr "未安装"
|
msgstr "未安装"
|
||||||
|
|
||||||
msgid "The software does not support using different diameter of nozzles for one print. If the left and right nozzles are inconsistent, we can only proceed with single-head printing. Please confirm which nozzle you would like to use for this project."
|
msgid "The software does not support using different diameter of nozzles for one print. If the left and right nozzles are inconsistent, we can only proceed with single-head printing. Please confirm which nozzle you would like to use for this project."
|
||||||
msgstr "该软件不支持在一次打印中使用不同直径的喷嘴。如果左右喷头不一致,就只能进行单头打印。请确认您想在该项目中使用哪种喷嘴。"
|
msgstr "该软件不支持在一次打印中使用不同直径的喷嘴。如果左右喷嘴不一致,就只能进行单头打印。请确认您想在该项目中使用哪种喷嘴。"
|
||||||
|
|
||||||
msgid "Switch diameter"
|
msgid "Switch diameter"
|
||||||
msgstr "开关直径"
|
msgstr "开关直径"
|
||||||
@@ -8002,19 +8014,19 @@ msgid ""
|
|||||||
msgstr "您正尝试删除切割对象的一部分,这将破坏切割对应关系,删除之后,将无法再保证模型的一致性。"
|
msgstr "您正尝试删除切割对象的一部分,这将破坏切割对应关系,删除之后,将无法再保证模型的一致性。"
|
||||||
|
|
||||||
msgid "Delete Object"
|
msgid "Delete Object"
|
||||||
msgstr ""
|
msgstr "删除对象"
|
||||||
|
|
||||||
msgid "Delete All Objects"
|
msgid "Delete All Objects"
|
||||||
msgstr ""
|
msgstr "删除所有对象"
|
||||||
|
|
||||||
msgid "Reset Project"
|
msgid "Reset Project"
|
||||||
msgstr ""
|
msgstr "重置项目"
|
||||||
|
|
||||||
msgid "The selected object couldn't be split."
|
msgid "The selected object couldn't be split."
|
||||||
msgstr "选中的模型不可分裂。"
|
msgstr "选中的模型不可分裂。"
|
||||||
|
|
||||||
msgid "Split to Objects"
|
msgid "Split to Objects"
|
||||||
msgstr ""
|
msgstr "拆分为对象"
|
||||||
|
|
||||||
msgid "Disable Auto-Drop to preserve Z positioning?\n"
|
msgid "Disable Auto-Drop to preserve Z positioning?\n"
|
||||||
msgstr "禁用自动落板以保留 Z 轴定位?\n"
|
msgstr "禁用自动落板以保留 Z 轴定位?\n"
|
||||||
@@ -8043,7 +8055,7 @@ msgid "File for the replacement wasn't selected"
|
|||||||
msgstr "未选择替换文件"
|
msgstr "未选择替换文件"
|
||||||
|
|
||||||
msgid "Replace with 3D file"
|
msgid "Replace with 3D file"
|
||||||
msgstr ""
|
msgstr "替换为 3D 文件"
|
||||||
|
|
||||||
msgid "Select folder to replace from"
|
msgid "Select folder to replace from"
|
||||||
msgstr "选择要替换的文件夹"
|
msgstr "选择要替换的文件夹"
|
||||||
@@ -8092,7 +8104,7 @@ msgid "Error during reload"
|
|||||||
msgstr "重新加载时发生错误"
|
msgstr "重新加载时发生错误"
|
||||||
|
|
||||||
msgid "Reload all"
|
msgid "Reload all"
|
||||||
msgstr ""
|
msgstr "重新加载全部"
|
||||||
|
|
||||||
msgid "There are warnings after slicing models:"
|
msgid "There are warnings after slicing models:"
|
||||||
msgstr "模型切片警告:"
|
msgstr "模型切片警告:"
|
||||||
@@ -9231,7 +9243,7 @@ msgid "Edit preset"
|
|||||||
msgstr "编辑预设"
|
msgstr "编辑预设"
|
||||||
|
|
||||||
msgid "Change extruder color"
|
msgid "Change extruder color"
|
||||||
msgstr ""
|
msgstr "更改挤出机颜色"
|
||||||
|
|
||||||
msgid "Unspecified"
|
msgid "Unspecified"
|
||||||
msgstr "未指定"
|
msgstr "未指定"
|
||||||
@@ -9457,10 +9469,10 @@ msgid "Your filament grouping method in the sliced file is not optimal."
|
|||||||
msgstr "您在切片文件中的耗材丝分组方法不是最佳的。"
|
msgstr "您在切片文件中的耗材丝分组方法不是最佳的。"
|
||||||
|
|
||||||
msgid "To ensure print quality, the drying temperature will be lowered during printing."
|
msgid "To ensure print quality, the drying temperature will be lowered during printing."
|
||||||
msgstr ""
|
msgstr "为保证打印质量,打印过程中会适当降低烘干温度。"
|
||||||
|
|
||||||
msgid "Select timelapse storage location"
|
msgid "Select timelapse storage location"
|
||||||
msgstr ""
|
msgstr "选择延时摄影存储位置"
|
||||||
|
|
||||||
msgid "Auto Bed Leveling"
|
msgid "Auto Bed Leveling"
|
||||||
msgstr "自动床调平"
|
msgstr "自动床调平"
|
||||||
@@ -9493,7 +9505,7 @@ msgstr ""
|
|||||||
"*自动模式:打印前检查校准情况。如果不需要,请跳过。"
|
"*自动模式:打印前检查校准情况。如果不需要,请跳过。"
|
||||||
|
|
||||||
msgid "Shared PA Profile"
|
msgid "Shared PA Profile"
|
||||||
msgstr ""
|
msgstr "共享 PA 预设"
|
||||||
|
|
||||||
msgid "Nozzles and filaments of the same type share the same PA profile."
|
msgid "Nozzles and filaments of the same type share the same PA profile."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -9511,39 +9523,39 @@ msgid "Extra info"
|
|||||||
msgstr "额外信息"
|
msgstr "额外信息"
|
||||||
|
|
||||||
msgid "The Filament Track Switch installed on the printer does not match the slicing file. Please re-slice to avoid print quality issues."
|
msgid "The Filament Track Switch installed on the printer does not match the slicing file. Please re-slice to avoid print quality issues."
|
||||||
msgstr ""
|
msgstr "打印机上安装的耗材变轨器与切片文件不匹配。请重新切片以避免打印质量问题。"
|
||||||
|
|
||||||
msgid "This print requires a Filament Track Switch. Please install it first."
|
msgid "This print requires a Filament Track Switch. Please install it first."
|
||||||
msgstr ""
|
msgstr "此打印件需要耗材变轨器。请先安装它。"
|
||||||
|
|
||||||
msgid "The Filament Track Switch has not been setup. Please setup it first."
|
msgid "The Filament Track Switch has not been setup. Please setup it first."
|
||||||
msgstr ""
|
msgstr "变轨器尚未初始化,请先进行初始化。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Failed to send nozzle auto-mapping request to printer { code: %d }. Please try to refresh the printer information. If it still does not recover, you can try to rebind the printer and check the network connection."
|
msgid "Failed to send nozzle auto-mapping request to printer { code: %d }. Please try to refresh the printer information. If it still does not recover, you can try to rebind the printer and check the network connection."
|
||||||
msgstr ""
|
msgstr "向打印机发送喷嘴自动映射请求失败 {错误代码:%d}。请尝试刷新打印机信息。若问题仍未解决,可尝试重新绑定打印机并检查网络连接。"
|
||||||
|
|
||||||
msgid "The printer is calculating nozzle mapping."
|
msgid "The printer is calculating nozzle mapping."
|
||||||
msgstr ""
|
msgstr "打印机正在计算喷嘴映射。"
|
||||||
|
|
||||||
msgid "Please wait a moment..."
|
msgid "Please wait a moment..."
|
||||||
msgstr ""
|
msgstr "请稍等片刻..."
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."
|
msgid "Failed to receive nozzle auto-mapping table from printer { msg: %s }. Please refresh the printer information."
|
||||||
msgstr ""
|
msgstr "未能从打印机接收喷嘴自动映射表 {消息:%s}。请刷新打印机信息。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "The printer failed to build the nozzle auto-mapping table { code: %d }. Please refresh nozzle information."
|
msgid "The printer failed to build the nozzle auto-mapping table { code: %d }. Please refresh nozzle information."
|
||||||
msgstr ""
|
msgstr "打印机未能构建喷嘴自动映射表 { code: %d }。请刷新喷嘴信息。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "The current nozzle mapping may produce an extra %0.2f g of waste."
|
msgid "The current nozzle mapping may produce an extra %0.2f g of waste."
|
||||||
msgstr ""
|
msgstr "当前的喷嘴映射可能会产生额外 %0.2f 克的废料。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Recommended filament arrangement saves %s->"
|
msgid "Recommended filament arrangement saves %s->"
|
||||||
msgstr ""
|
msgstr "如果按照推荐摆料可省 %s->"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."
|
msgid "Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."
|
||||||
@@ -9599,31 +9611,31 @@ msgstr "内部"
|
|||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "%s space less than 20MB. Timelapse may not save properly. You can turn it off or"
|
msgid "%s space less than 20MB. Timelapse may not save properly. You can turn it off or"
|
||||||
msgstr ""
|
msgstr "%s空间不足 20MB,延时摄影可能无法正常存储,可选择关闭,或者"
|
||||||
|
|
||||||
msgid "Clean up files"
|
msgid "Clean up files"
|
||||||
msgstr ""
|
msgstr "清理文件"
|
||||||
|
|
||||||
msgid "Low internal storage. This timelapse will overwrite the oldest video files."
|
msgid "Low internal storage. This timelapse will overwrite the oldest video files."
|
||||||
msgstr ""
|
msgstr "机内存储空间不足,本次延时摄影将覆盖最早的视频文件。"
|
||||||
|
|
||||||
msgid "Low external storage. This timelapse will overwrite the oldest video files."
|
msgid "Low external storage. This timelapse will overwrite the oldest video files."
|
||||||
msgstr ""
|
msgstr "外部存储空间不足,本次延时摄影将覆盖最早的视频文件。"
|
||||||
|
|
||||||
msgid "Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."
|
msgid "Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."
|
||||||
msgstr ""
|
msgstr "外部存储空间不足,无法进行延时摄影,请将存储设备连接至电脑,手动删除不需要的文件,或更换大容量内存卡。"
|
||||||
|
|
||||||
msgid "Storage Space Not Enough"
|
msgid "Storage Space Not Enough"
|
||||||
msgstr ""
|
msgstr "存储空间不足"
|
||||||
|
|
||||||
msgid "Confirm & Print"
|
msgid "Confirm & Print"
|
||||||
msgstr ""
|
msgstr "好的,发起打印"
|
||||||
|
|
||||||
msgid "Cancel Timelapse & Print"
|
msgid "Cancel Timelapse & Print"
|
||||||
msgstr ""
|
msgstr "取消延时摄影,继续打印"
|
||||||
|
|
||||||
msgid "Clean Up"
|
msgid "Clean Up"
|
||||||
msgstr ""
|
msgstr "清理"
|
||||||
|
|
||||||
msgid "Preparing print job"
|
msgid "Preparing print job"
|
||||||
msgstr "正在准备打印任务"
|
msgstr "正在准备打印任务"
|
||||||
@@ -9640,32 +9652,32 @@ msgstr "喷嘴"
|
|||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Refreshing information of hotends(%d/%d)."
|
msgid "Refreshing information of hotends(%d/%d)."
|
||||||
msgstr ""
|
msgstr "正在刷新喷嘴信息(%d/%d)。"
|
||||||
|
|
||||||
msgid "There are not enough available hotends currently."
|
msgid "There are not enough available hotends currently."
|
||||||
msgstr ""
|
msgstr "目前可用的热端数量不足。"
|
||||||
|
|
||||||
msgid "Please complete the hotend rack setup and try again."
|
msgid "Please complete the hotend rack setup and try again."
|
||||||
msgstr ""
|
msgstr "请先进行热端挂架初始化后重试。"
|
||||||
|
|
||||||
msgid "Please refresh the nozzle information and try again."
|
msgid "Please refresh the nozzle information and try again."
|
||||||
msgstr ""
|
msgstr "请刷新喷嘴信息后重试。"
|
||||||
|
|
||||||
msgid "Please re-slice to avoid filament waste."
|
msgid "Please re-slice to avoid filament waste."
|
||||||
msgstr ""
|
msgstr "请重新切片以避免耗材浪费。"
|
||||||
|
|
||||||
msgid "The reported hotend information may be unreliable."
|
msgid "The reported hotend information may be unreliable."
|
||||||
msgstr ""
|
msgstr "报告的热端信息可能不可靠。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "The printer has no nozzle matching the slicing file (%s)."
|
msgid "The printer has no nozzle matching the slicing file (%s)."
|
||||||
msgstr ""
|
msgstr "打印机没有与切片文件匹配的喷嘴(%s)"
|
||||||
|
|
||||||
msgid "Please install a matching nozzle in the hotend rack, or set the corresponding printer preset while slicing."
|
msgid "Please install a matching nozzle in the hotend rack, or set the corresponding printer preset while slicing."
|
||||||
msgstr ""
|
msgstr "请在热端架上安装匹配的喷嘴,或在切片时设置相应的打印机预设。"
|
||||||
|
|
||||||
msgid "The toolhead and hotend rack are full. Please remove at least one hotend before printing."
|
msgid "The toolhead and hotend rack are full. Please remove at least one hotend before printing."
|
||||||
msgstr ""
|
msgstr "工具头和热端挂架已满,请卸下至少一个热端后再打印。"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). Please make sure the nozzle installed matches with settings in printer, then set the corresponding printer preset while slicing."
|
msgid "The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). Please make sure the nozzle installed matches with settings in printer, then set the corresponding printer preset while slicing."
|
||||||
@@ -11082,7 +11094,7 @@ msgid "Ext spool"
|
|||||||
msgstr "外置耗材盘"
|
msgstr "外置耗材盘"
|
||||||
|
|
||||||
msgid "Please check whether the nozzle type of the device is the same as the preset nozzle type."
|
msgid "Please check whether the nozzle type of the device is the same as the preset nozzle type."
|
||||||
msgstr "请检查设备的喷头类型是否与预设的喷头类型相同。"
|
msgstr "请检查设备的喷嘴类型是否与预设的喷嘴类型相同。"
|
||||||
|
|
||||||
msgid "Storage is not available or is in read-only mode."
|
msgid "Storage is not available or is in read-only mode."
|
||||||
msgstr "存储不可用或处于只读模式。"
|
msgstr "存储不可用或处于只读模式。"
|
||||||
@@ -20925,7 +20937,7 @@ msgstr ""
|
|||||||
#~ msgstr "选择安装到正确喷嘴的耗材"
|
#~ msgstr "选择安装到正确喷嘴的耗材"
|
||||||
|
|
||||||
#~ msgid "Select Filament && Hotends"
|
#~ msgid "Select Filament && Hotends"
|
||||||
#~ msgstr "选择材料预设和喷头"
|
#~ msgstr "选择材料预设和喷嘴"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
#~ msgid "Note: the filament type(%s) does not match with the filament type(%s) in the slicing file. If you want to use this slot, you can install %s instead of %s and change slot information on the 'Device' page."
|
#~ msgid "Note: the filament type(%s) does not match with the filament type(%s) in the slicing file. If you want to use this slot, you can install %s instead of %s and change slot information on the 'Device' page."
|
||||||
@@ -21449,7 +21461,7 @@ msgstr ""
|
|||||||
#~ msgstr "删除一个耳状Brim"
|
#~ msgstr "删除一个耳状Brim"
|
||||||
|
|
||||||
#~ msgid "Adjust head diameter"
|
#~ msgid "Adjust head diameter"
|
||||||
#~ msgstr "调整喷头直径"
|
#~ msgstr "调整喷嘴直径"
|
||||||
|
|
||||||
#~ msgid "Adjust section view"
|
#~ msgid "Adjust section view"
|
||||||
#~ msgstr "调整剖面视图"
|
#~ msgstr "调整剖面视图"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+20
-3
@@ -24,6 +24,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <atomic>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__LINUX__)
|
#if defined(__linux__) || defined(__LINUX__)
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -7608,6 +7610,9 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
// Guards against a failed allocation inside the dump re-entering the new-handler.
|
||||||
|
static std::atomic<bool> g_dump_in_progress{false};
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
__declspec(dllexport) int __stdcall orcaslicer_main(int argc, wchar_t **argv)
|
__declspec(dllexport) int __stdcall orcaslicer_main(int argc, wchar_t **argv)
|
||||||
{
|
{
|
||||||
@@ -7626,10 +7631,22 @@ extern "C" {
|
|||||||
//AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
|
//AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
|
||||||
SET_DEFULTER_HANDLER();
|
SET_DEFULTER_HANDLER();
|
||||||
#endif
|
#endif
|
||||||
|
// Dump before unwinding, while the stack still names what asked for the memory. Throwing
|
||||||
|
// std::bad_alloc is standard-permitted here and is what reaches generic_exception_handle().
|
||||||
std::set_new_handler([]() {
|
std::set_new_handler([]() {
|
||||||
int *a = nullptr;
|
if (!g_dump_in_progress.exchange(true)) {
|
||||||
*a = 0;
|
try {
|
||||||
});
|
// A null EXCEPTION_POINTERS walks the calling thread as it stands.
|
||||||
|
CBaseException base(GetCurrentProcess(), GetCurrentProcessId(), NULL, nullptr);
|
||||||
|
base.ShowCallstack();
|
||||||
|
} catch (...) {
|
||||||
|
// A failed dump must not displace the std::bad_alloc owed to the caller.
|
||||||
|
}
|
||||||
|
// ObjParser recovers from std::bad_alloc, so let a later one dump again.
|
||||||
|
g_dump_in_progress = false;
|
||||||
|
}
|
||||||
|
throw std::bad_alloc();
|
||||||
|
});
|
||||||
// Call the UTF8 main.
|
// Call the UTF8 main.
|
||||||
return CLI().run(argc, argv_ptrs.data());
|
return CLI().run(argc, argv_ptrs.data());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
|||||||
if (template_string.empty()) {
|
if (template_string.empty()) {
|
||||||
return Geometry::deg2rad(fixed_infill_angle);
|
return Geometry::deg2rad(fixed_infill_angle);
|
||||||
}
|
}
|
||||||
|
// Convert the id to an index. Layer::id() counts the raft layers, object->layers() does not.
|
||||||
|
const size_t first_object_layer_id = object->get_layer(0)->id();
|
||||||
|
layer_id = layer_id > first_object_layer_id ? layer_id - first_object_layer_id : 0;
|
||||||
double angle = 0.0;
|
double angle = 0.0;
|
||||||
ConfigOptionFloats rotate_angles;
|
ConfigOptionFloats rotate_angles;
|
||||||
const std::string search_string = "/NnZz$LlUuQq~^|#";
|
const std::string search_string = "/NnZz$LlUuQq~^|#";
|
||||||
@@ -75,6 +78,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
|||||||
double angle_start = 0;
|
double angle_start = 0;
|
||||||
double limit_fill_z = object->get_layer(0)->bottom_z();
|
double limit_fill_z = object->get_layer(0)->bottom_z();
|
||||||
double start_fill_z = limit_fill_z;
|
double start_fill_z = limit_fill_z;
|
||||||
|
// The raft height, or 0 without a raft.
|
||||||
|
const double print_z_offset = object->slicing_parameters().object_print_z_min;
|
||||||
bool _noop = false;
|
bool _noop = false;
|
||||||
auto fill_form = std::string::npos;
|
auto fill_form = std::string::npos;
|
||||||
bool _absolute = false;
|
bool _absolute = false;
|
||||||
@@ -84,7 +89,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
|||||||
for (int i = 0; i <= layer_id; i++) {
|
for (int i = 0; i <= layer_id; i++) {
|
||||||
double fill_z = object->get_layer(i)->bottom_z();
|
double fill_z = object->get_layer(i)->bottom_z();
|
||||||
|
|
||||||
if (limit_fill_z < object->get_layer(i)->slice_z) {
|
// slice_z is measured from the bottom of the model, limit_fill_z from the build plate.
|
||||||
|
if (limit_fill_z < object->get_layer(i)->slice_z + print_z_offset) {
|
||||||
if (repeats) { // if repeats >0 then restore parameters for new iteration
|
if (repeats) { // if repeats >0 then restore parameters for new iteration
|
||||||
limit_fill_z += limit_fill_z - start_fill_z;
|
limit_fill_z += limit_fill_z - start_fill_z;
|
||||||
start_fill_z = fill_z;
|
start_fill_z = fill_z;
|
||||||
|
|||||||
@@ -730,6 +730,42 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
|||||||
return temp_set_by_gcode;
|
return temp_set_by_gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CustomGCodeMotionStateChanges
|
||||||
|
{
|
||||||
|
bool acceleration = false;
|
||||||
|
bool jerk = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool custom_gcode_line_has_xy_parameter(const std::string &raw)
|
||||||
|
{
|
||||||
|
const size_t comment_pos = raw.find(';');
|
||||||
|
const std::string_view code(raw.data(), comment_pos == std::string::npos ? raw.size() : comment_pos);
|
||||||
|
return code.find_first_of("XxYy") != std::string_view::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CustomGCodeMotionStateChanges custom_gcode_motion_state_changes(const std::string &gcode)
|
||||||
|
{
|
||||||
|
CustomGCodeMotionStateChanges changes;
|
||||||
|
GCodeReader parser;
|
||||||
|
parser.parse_buffer(gcode, [&changes](GCodeReader &parser, const GCodeReader::GCodeLine &line) {
|
||||||
|
const std::string_view cmd = line.cmd();
|
||||||
|
if (boost::iequals(cmd, "M204") || boost::iequals(cmd, "M201") ||
|
||||||
|
boost::iequals(cmd, "M202"))
|
||||||
|
changes.acceleration = true;
|
||||||
|
else if ((boost::iequals(cmd, "M205") || boost::iequals(cmd, "M207") || boost::iequals(cmd, "M566")) &&
|
||||||
|
custom_gcode_line_has_xy_parameter(line.raw()))
|
||||||
|
changes.jerk = true;
|
||||||
|
else if (boost::iequals(cmd, "SET_VELOCITY_LIMIT")) {
|
||||||
|
changes.acceleration |= boost::icontains(line.raw(), "ACCEL=");
|
||||||
|
changes.jerk |= boost::icontains(line.raw(), "SQUARE_CORNER_VELOCITY=");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.acceleration && changes.jerk)
|
||||||
|
parser.quit_parsing();
|
||||||
|
});
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
// start_pos refers to the last position before the wipe_tower.
|
// start_pos refers to the last position before the wipe_tower.
|
||||||
// end_pos refers to the wipe tower's start_pos.
|
// end_pos refers to the wipe tower's start_pos.
|
||||||
@@ -4335,6 +4371,11 @@ PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
|
|||||||
ppi.update_from_gcodewriter(m_writer);
|
ppi.update_from_gcodewriter(m_writer);
|
||||||
std::string output = ppi.parser.process(templ, current_filament_id, config_override, &ppi.output_config, &ppi.context);
|
std::string output = ppi.parser.process(templ, current_filament_id, config_override, &ppi.output_config, &ppi.context);
|
||||||
ppi.validate_output_vector_variables();
|
ppi.validate_output_vector_variables();
|
||||||
|
const CustomGCodeMotionStateChanges motion_state_changes = custom_gcode_motion_state_changes(output);
|
||||||
|
if (motion_state_changes.acceleration)
|
||||||
|
m_writer.invalidate_acceleration();
|
||||||
|
if (motion_state_changes.jerk)
|
||||||
|
m_writer.invalidate_jerk();
|
||||||
|
|
||||||
if (const std::vector<double> &pos = ppi.opt_position->values; ppi.position != pos) {
|
if (const std::vector<double> &pos = ppi.opt_position->values; ppi.position != pos) {
|
||||||
// Update G-code writer.
|
// Update G-code writer.
|
||||||
|
|||||||
@@ -614,6 +614,8 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
|
|||||||
float leftover = 0.0f;
|
float leftover = 0.0f;
|
||||||
for (size_t i = additional_buffer_idx; i < additional_buffer.size(); ++i)
|
for (size_t i = additional_buffer_idx; i < additional_buffer.size(); ++i)
|
||||||
leftover += additional_buffer[i].second;
|
leftover += additional_buffer[i].second;
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "calculate_time(is_final): leftover=" << leftover
|
||||||
|
<< "s from " << (additional_buffer.size() - additional_buffer_idx) << " items";
|
||||||
time += double(leftover);
|
time += double(leftover);
|
||||||
gcode_time.cache += leftover;
|
gcode_time.cache += leftover;
|
||||||
} else {
|
} else {
|
||||||
@@ -3492,6 +3494,7 @@ void GCodeProcessor::reset()
|
|||||||
m_extruder_blocks.clear();
|
m_extruder_blocks.clear();
|
||||||
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
||||||
m_machine_end_gcode_start_line_id = (unsigned int) (-1);
|
m_machine_end_gcode_start_line_id = (unsigned int) (-1);
|
||||||
|
m_skip_end_gcode_delays = false;
|
||||||
m_remaining_volume = std::vector<float>(MAXIMUM_EXTRUDER_NUMBER, 0.f);
|
m_remaining_volume = std::vector<float>(MAXIMUM_EXTRUDER_NUMBER, 0.f);
|
||||||
|
|
||||||
m_line_id = 0;
|
m_line_id = 0;
|
||||||
@@ -4219,6 +4222,14 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// End gcode marker: skip post-print M400 S/P dwells after this point so the M73 estimate reports
|
||||||
|
// print-completion time, not post-print filtration/cooldown. BBS drops the same remainder in
|
||||||
|
// calculate_time(is_final).
|
||||||
|
if (comment == Machine_End_GCode_Start_Tag) {
|
||||||
|
m_skip_end_gcode_delays = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Orca: Integrate filament consumption for purging performed to an external device and controlled via macros
|
// Orca: Integrate filament consumption for purging performed to an external device and controlled via macros
|
||||||
// (eg. Happy Hare) in the filament consumption stats.
|
// (eg. Happy Hare) in the filament consumption stats.
|
||||||
if (boost::starts_with(comment, GCodeProcessor::External_Purge_Tag)) {
|
if (boost::starts_with(comment, GCodeProcessor::External_Purge_Tag)) {
|
||||||
@@ -6401,6 +6412,10 @@ void GCodeProcessor::process_M400(const GCodeReader::GCodeLine& line)
|
|||||||
float value_p = 0.0;
|
float value_p = 0.0;
|
||||||
if (line.has_value('S', value_s) || line.has_value('P', value_p)) {
|
if (line.has_value('S', value_s) || line.has_value('P', value_p)) {
|
||||||
value_s += value_p * 0.001;
|
value_s += value_p * 0.001;
|
||||||
|
// Skip post-print end-gcode dwells so they don't inflate the M73 estimate (see
|
||||||
|
// m_skip_end_gcode_delays). Only omits dwell time — no state is updated here.
|
||||||
|
if (m_skip_end_gcode_delays)
|
||||||
|
return;
|
||||||
simulate_st_synchronize(value_s);
|
simulate_st_synchronize(value_s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1109,6 +1109,10 @@ class Print;
|
|||||||
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
|
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
|
||||||
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
|
unsigned int m_machine_start_gcode_end_line_id{ (unsigned int) (-1) };
|
||||||
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
|
unsigned int m_machine_end_gcode_start_line_id{ (unsigned int) (-1) };
|
||||||
|
// Set when the MACHINE_END_GCODE_START tag is seen during the streaming parse; tells
|
||||||
|
// process_M400 to skip post-print end-gcode dwells (air purification, timelapse, sound)
|
||||||
|
// so they don't inflate the M73 estimate. BBS excludes them in calculate_time(is_final).
|
||||||
|
bool m_skip_end_gcode_delays{ false };
|
||||||
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
|
// Tracks, during the stream, which filament sits in each physical nozzle and which nozzle each
|
||||||
// extruder currently carries. Written by both branches of the two-arg process_filament_change
|
// extruder currently carries. Written by both branches of the two-arg process_filament_change
|
||||||
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model
|
// (the fallback branch does occupancy bookkeeping only); read by the richer change-time model
|
||||||
|
|||||||
@@ -1091,6 +1091,16 @@ namespace Slic3r
|
|||||||
if (layer + 1 < layer_filaments.size()) next_lf = layer_filaments[layer + 1];
|
if (layer + 1 < layer_filaments.size()) next_lf = layer_filaments[layer + 1];
|
||||||
std::vector<unsigned int> filament_used_next_layer = collect_filaments_in_groups<unsigned int>(filament_sets, next_lf);
|
std::vector<unsigned int> filament_used_next_layer = collect_filaments_in_groups<unsigned int>(filament_sets, next_lf);
|
||||||
|
|
||||||
|
// Enable inter-layer forecast: when choosing filament ordering for current layer,
|
||||||
|
// also consider next layer's filament set to minimize inter-layer transition flush.
|
||||||
|
// solve_extruder_order_with_forcast() tries all permutations of curr+next layer
|
||||||
|
// and picks the ordering that minimizes total flush across both layers.
|
||||||
|
// This avoids expensive inter-layer transitions (e.g. ending layer with F2 when
|
||||||
|
// next layer starts with F3, costing flush[F2→F3], instead of ending with F3
|
||||||
|
// which gives flush[F3→F3]=0). Limited to ≤5 filaments due to O(N!×M!) complexity.
|
||||||
|
// The per-nozzle base reorder does not use the inter-layer forecast. This function drives
|
||||||
|
// BBL multi-extruder grouping cost and H2C ordering, so keeping it false avoids perturbing
|
||||||
|
// existing H2D/H2C output.
|
||||||
bool use_forcast = false;
|
bool use_forcast = false;
|
||||||
float tmp_cost = 0;
|
float tmp_cost = 0;
|
||||||
std::vector<unsigned int> sequence;
|
std::vector<unsigned int> sequence;
|
||||||
|
|||||||
@@ -1609,6 +1609,64 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
|||||||
if (max_vol_speed!= 0.f)
|
if (max_vol_speed!= 0.f)
|
||||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||||
|
|
||||||
|
// Vortek H2C: carousel-specific ramming, precool, and reverse travel parameters
|
||||||
|
{
|
||||||
|
// Ramming speed: .first = extruder change, .second = nozzle change (carousel)
|
||||||
|
// Use the dedicated ramming volumetric speed, falling back to max_vol_speed only when
|
||||||
|
// the setting is nil/-1.
|
||||||
|
float ramming_vol_speed = float(config.filament_ramming_volumetric_speed.get_at(idx));
|
||||||
|
if (config.filament_ramming_volumetric_speed.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed.get_at(idx), -1.))
|
||||||
|
ramming_vol_speed = max_vol_speed;
|
||||||
|
m_filpar[idx].max_e_ramming_speed.first = (ramming_vol_speed / filament_area());
|
||||||
|
|
||||||
|
float ramming_vol_speed_nc = float(config.filament_ramming_volumetric_speed_nc.get_at(idx));
|
||||||
|
if (config.filament_ramming_volumetric_speed_nc.is_nil(idx) || is_approx(config.filament_ramming_volumetric_speed_nc.get_at(idx), -1.))
|
||||||
|
ramming_vol_speed_nc = max_vol_speed;
|
||||||
|
m_filpar[idx].max_e_ramming_speed.second = (ramming_vol_speed_nc / filament_area());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Precool target temp: .first = extruder change, .second = nozzle change (carousel)
|
||||||
|
// Precool is only active when enable_pre_heating is on; otherwise no precool temp/timing is
|
||||||
|
// applied and the downstream precool_t stays 0, matching printers with pre-heating disabled.
|
||||||
|
m_filpar[idx].precool_target_temp = {0, 0};
|
||||||
|
if (config.enable_pre_heating.value) {
|
||||||
|
if (!config.filament_pre_cooling_temperature.is_nil(idx) && config.filament_pre_cooling_temperature.get_at(idx) != 0)
|
||||||
|
m_filpar[idx].precool_target_temp.first = config.filament_pre_cooling_temperature.get_at(idx);
|
||||||
|
if (!config.filament_pre_cooling_temperature_nc.is_nil(idx) && config.filament_pre_cooling_temperature_nc.get_at(idx) != 0)
|
||||||
|
m_filpar[idx].precool_target_temp.second = config.filament_pre_cooling_temperature_nc.get_at(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Precool timing: (nozzle_temp - precool_temp) / hotend_cooling_rate
|
||||||
|
int extruder_count = m_is_multi_extruder ? 2 : 1; // H2C = 2 extruders
|
||||||
|
float nozzle_temp = float(config.nozzle_temperature.is_nil(idx) ? 0 : config.nozzle_temperature.get_at(idx));
|
||||||
|
float nozzle_temp_fl = float(config.nozzle_temperature_initial_layer.is_nil(idx) ? nozzle_temp : config.nozzle_temperature_initial_layer.get_at(idx));
|
||||||
|
m_filpar[idx].precool_t.first.resize(extruder_count, 0.f);
|
||||||
|
m_filpar[idx].precool_t.second.resize(extruder_count, 0.f);
|
||||||
|
m_filpar[idx].precool_t_first_layer.first.resize(extruder_count, 0.f);
|
||||||
|
m_filpar[idx].precool_t_first_layer.second.resize(extruder_count, 0.f);
|
||||||
|
std::vector<double> cooling_rates = config.hotend_cooling_rate.values;
|
||||||
|
for (int i = 0; i < extruder_count && i < (int)cooling_rates.size(); i++) {
|
||||||
|
if (cooling_rates[i] < EPSILON) continue;
|
||||||
|
if (m_filpar[idx].precool_target_temp.first != 0) {
|
||||||
|
m_filpar[idx].precool_t.first[i] = std::max(0.f, nozzle_temp - float(m_filpar[idx].precool_target_temp.first)) / float(cooling_rates[i]);
|
||||||
|
m_filpar[idx].precool_t_first_layer.first[i] = std::max(0.f, nozzle_temp_fl - float(m_filpar[idx].precool_target_temp.first)) / float(cooling_rates[i]);
|
||||||
|
}
|
||||||
|
if (m_filpar[idx].precool_target_temp.second != 0) {
|
||||||
|
m_filpar[idx].precool_t.second[i] = std::max(0.f, nozzle_temp - float(m_filpar[idx].precool_target_temp.second)) / float(cooling_rates[i]);
|
||||||
|
m_filpar[idx].precool_t_first_layer.second[i] = std::max(0.f, nozzle_temp_fl - float(m_filpar[idx].precool_target_temp.second)) / float(cooling_rates[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Ramming travel time: .first = extruder change, .second = nozzle change (carousel)
|
||||||
|
m_filpar[idx].ramming_travel_time = {0.f, 0.f};
|
||||||
|
if (!config.filament_ramming_travel_time.is_nil(idx))
|
||||||
|
m_filpar[idx].ramming_travel_time.first = float(config.filament_ramming_travel_time.get_at(idx));
|
||||||
|
if (!config.filament_ramming_travel_time_nc.is_nil(idx))
|
||||||
|
m_filpar[idx].ramming_travel_time.second = float(config.filament_ramming_travel_time_nc.get_at(idx));
|
||||||
|
}
|
||||||
|
|
||||||
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
|
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
|
||||||
m_nozzle_change_perimeter_width = 2*m_perimeter_width;
|
m_nozzle_change_perimeter_width = 2*m_perimeter_width;
|
||||||
// BBS: remove useless config
|
// BBS: remove useless config
|
||||||
@@ -1893,7 +1951,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int
|
|||||||
.set_initial_tool(m_current_tool)
|
.set_initial_tool(m_current_tool)
|
||||||
.set_extrusion_flow(m_extrusion_flow)
|
.set_extrusion_flow(m_extrusion_flow)
|
||||||
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
||||||
.append("; Nozzle change start\n");
|
.append(format_nozzle_change_tag(true, old_filament_id, new_filament_id));
|
||||||
|
|
||||||
box_coordinates cleaning_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width,
|
box_coordinates cleaning_box(Vec2f(m_perimeter_width, m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width,
|
||||||
(new_filament_id != (unsigned int) (-1) ? wipe_depth + m_depth_traversed - m_perimeter_width : m_wipe_tower_depth - m_perimeter_width));
|
(new_filament_id != (unsigned int) (-1) ? wipe_depth + m_depth_traversed - m_perimeter_width : m_wipe_tower_depth - m_perimeter_width));
|
||||||
@@ -1969,7 +2027,7 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.append("; Nozzle change end\n");
|
writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id));
|
||||||
|
|
||||||
result.start_pos = writer.start_pos_rotated();
|
result.start_pos = writer.start_pos_rotated();
|
||||||
result.end_pos = writer.pos();
|
result.end_pos = writer.pos();
|
||||||
@@ -2546,6 +2604,19 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in
|
|||||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
depth += nozzle_change_depth;
|
depth += nozzle_change_depth;
|
||||||
}
|
}
|
||||||
|
if (nozzle_change_depth == 0
|
||||||
|
&& !m_filament_nozzle_map.empty()
|
||||||
|
&& old_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size()
|
||||||
|
&& m_filament_nozzle_map[old_tool] != m_filament_nozzle_map[new_tool]) {
|
||||||
|
double e_flow = nozzle_change_extrusion_flow(layer_height_par);
|
||||||
|
double length = m_filaments_change_length[old_tool] / e_flow;
|
||||||
|
int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1;
|
||||||
|
if (has_tpu_filament())
|
||||||
|
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
|
else
|
||||||
|
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
|
depth += nozzle_change_depth;
|
||||||
|
}
|
||||||
WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume);
|
WipeTowerInfo::ToolChange tool_change = WipeTowerInfo::ToolChange(old_tool, new_tool, depth, 0.f, 0.f, wipe_volume, length_to_extrude, purge_volume);
|
||||||
tool_change.nozzle_change_depth = nozzle_change_depth;
|
tool_change.nozzle_change_depth = nozzle_change_depth;
|
||||||
m_plan.back().tool_changes.push_back(tool_change);
|
m_plan.back().tool_changes.push_back(tool_change);
|
||||||
@@ -2695,6 +2766,13 @@ bool WipeTower::is_petg_filament(int filament_id) const
|
|||||||
return m_filpar[filament_id].material == "PETG";
|
return m_filpar[filament_id].material == "PETG";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WipeTower::is_need_reverse_travel(int filament_id, bool extruder_change) const
|
||||||
|
{
|
||||||
|
if (extruder_change)
|
||||||
|
return m_filpar[filament_id].ramming_travel_time.first > EPSILON;
|
||||||
|
return m_filpar[filament_id].ramming_travel_time.second > EPSILON;
|
||||||
|
}
|
||||||
|
|
||||||
// BBS: consider both soluable and support properties
|
// BBS: consider both soluable and support properties
|
||||||
// Return index of first toolchange that switches to non-soluble and non-support extruder
|
// Return index of first toolchange that switches to non-soluble and non-support extruder
|
||||||
// ot -1 if there is no such toolchange.
|
// ot -1 if there is no such toolchange.
|
||||||
@@ -2819,6 +2897,13 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol
|
|||||||
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
||||||
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
||||||
}
|
}
|
||||||
|
if (m_nozzle_change_result.gcode.empty()
|
||||||
|
&& !m_filament_nozzle_map.empty()
|
||||||
|
&& m_current_tool < m_filament_nozzle_map.size() && new_tool < m_filament_nozzle_map.size()
|
||||||
|
&& m_filament_nozzle_map[m_current_tool] != m_filament_nozzle_map[new_tool]
|
||||||
|
&& is_valid_last_layer(m_current_tool, m_cur_layer_id, m_z_pos)) {
|
||||||
|
m_nozzle_change_result = nozzle_change_new(m_current_tool, new_tool, solid_nozzlechange);
|
||||||
|
}
|
||||||
|
|
||||||
size_t old_tool = m_current_tool;
|
size_t old_tool = m_current_tool;
|
||||||
float wipe_depth = 0.f;
|
float wipe_depth = 0.f;
|
||||||
@@ -2983,20 +3068,40 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
float nz_extrusion_flow = nozzle_change_extrusion_flow(m_layer_height);
|
float nz_extrusion_flow = nozzle_change_extrusion_flow(m_layer_height);
|
||||||
float nozzle_change_speed = 60.0f * m_filpar[m_current_tool].max_e_speed / nz_extrusion_flow;
|
bool extruder_change = !is_in_same_extruder(old_filament_id, new_filament_id);
|
||||||
nozzle_change_speed = solid_infill ? 40.f * 60.f : nozzle_change_speed;//If the contact layers belong to different categories, then reduce the speed.
|
float max_e_ramming = extruder_change
|
||||||
|
? m_filpar[m_current_tool].max_e_ramming_speed.first
|
||||||
|
: m_filpar[m_current_tool].max_e_ramming_speed.second;
|
||||||
|
if (max_e_ramming < EPSILON) max_e_ramming = m_filpar[m_current_tool].max_e_speed; // fallback
|
||||||
|
float nozzle_change_speed = 60.0f * max_e_ramming / nz_extrusion_flow;
|
||||||
|
nozzle_change_speed = solid_infill ? 40.f * 60.f : nozzle_change_speed;
|
||||||
|
|
||||||
if (is_tpu_filament(m_current_tool)) {
|
if (is_tpu_filament(m_current_tool)) {
|
||||||
nozzle_change_speed *= 0.25;
|
nozzle_change_speed *= 0.25;
|
||||||
}
|
}
|
||||||
float bridge_speed = std::min(60.0f * m_filpar[m_current_tool].max_e_speed / nozzle_change_extrusion_flow(0.2), nozzle_change_speed); // limit the bridge speed by add flow
|
float bridge_speed = std::min(60.0f * max_e_ramming / nozzle_change_extrusion_flow(0.2), nozzle_change_speed);
|
||||||
|
|
||||||
WipeTowerWriter writer(m_layer_height, m_nozzle_change_perimeter_width, m_gcode_flavor, m_filpar);
|
WipeTowerWriter writer(m_layer_height, m_nozzle_change_perimeter_width, m_gcode_flavor, m_filpar);
|
||||||
writer.set_extrusion_flow(nz_extrusion_flow)
|
writer.set_extrusion_flow(nz_extrusion_flow)
|
||||||
.set_z(m_z_pos)
|
.set_z(m_z_pos)
|
||||||
.set_initial_tool(m_current_tool)
|
.set_initial_tool(m_current_tool)
|
||||||
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
.set_y_shift(m_y_shift + (new_filament_id != (unsigned int) (-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth() : 0.f))
|
||||||
.append("; Nozzle change start\n");
|
.append(format_nozzle_change_tag(true, old_filament_id, new_filament_id));
|
||||||
|
|
||||||
|
if (!extruder_change && m_is_multiple_nozzle) {
|
||||||
|
writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n");
|
||||||
|
// Use m_physical_extruder_map for heater index (matches format_line_M104 in add_M104_by_requirement)
|
||||||
|
if (m_filpar[m_current_tool].precool_target_temp.second != 0) {
|
||||||
|
int logical_ext = m_filament_map.empty() ? 0 : m_filament_map[m_current_tool] - 1;
|
||||||
|
int phys_ext = (logical_ext >= 0 && logical_ext < (int)m_physical_extruder_map.size())
|
||||||
|
? m_physical_extruder_map[logical_ext] : logical_ext;
|
||||||
|
writer.append("M400\n");
|
||||||
|
writer.append("M104 T" + std::to_string(phys_ext) + " S" +
|
||||||
|
std::to_string(m_filpar[m_current_tool].precool_target_temp.second) + " N0\n");
|
||||||
|
writer.append("M106 S255\n");
|
||||||
|
}
|
||||||
|
writer.append("M633\n");
|
||||||
|
}
|
||||||
|
|
||||||
WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false);
|
WipeTowerBlock* block = get_block_by_category(m_filpar[old_filament_id].category, false);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
@@ -3021,6 +3126,23 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
|||||||
dy = solid_infill ? m_nozzle_change_perimeter_width : dy;
|
dy = solid_infill ? m_nozzle_change_perimeter_width : dy;
|
||||||
nozzle_change_line_count = solid_infill ? std::numeric_limits<int>::max() : nozzle_change_line_count;
|
nozzle_change_line_count = solid_infill ? std::numeric_limits<int>::max() : nozzle_change_line_count;
|
||||||
m_left_to_right = true;
|
m_left_to_right = true;
|
||||||
|
|
||||||
|
if (extruder_change) {
|
||||||
|
float ramming_length = nozzle_change_line_count * (xr - xl);
|
||||||
|
int extruder_id = m_filament_map.empty() ? 0 : m_filament_map[m_current_tool] - 1;
|
||||||
|
float precool_t = (extruder_id >= 0 && extruder_id < (int)m_filpar[m_current_tool].precool_t.first.size())
|
||||||
|
? m_filpar[m_current_tool].precool_t.first[extruder_id] : 0.f;
|
||||||
|
float precool_t_fl = (extruder_id >= 0 && extruder_id < (int)m_filpar[m_current_tool].precool_t_first_layer.first.size())
|
||||||
|
? m_filpar[m_current_tool].precool_t_first_layer.first[extruder_id] : 0.f;
|
||||||
|
float per_cooling_max_speed = nozzle_change_speed;
|
||||||
|
if (is_first_layer() && precool_t_fl > EPSILON)
|
||||||
|
per_cooling_max_speed = ramming_length / precool_t_fl * 60.f;
|
||||||
|
else if (precool_t > EPSILON)
|
||||||
|
per_cooling_max_speed = ramming_length / precool_t * 60.f;
|
||||||
|
if (nozzle_change_speed > per_cooling_max_speed) nozzle_change_speed = per_cooling_max_speed;
|
||||||
|
if (bridge_speed > per_cooling_max_speed) bridge_speed = per_cooling_max_speed;
|
||||||
|
}
|
||||||
|
|
||||||
int real_nozzle_change_line_count = 0;
|
int real_nozzle_change_line_count = 0;
|
||||||
bool need_change_flow = false;
|
bool need_change_flow = false;
|
||||||
for (int i = 0; true; ++i) {
|
for (int i = 0; true; ++i) {
|
||||||
@@ -3053,9 +3175,40 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
|||||||
block->last_nozzle_change_id = old_filament_id;
|
block->last_nozzle_change_id = old_filament_id;
|
||||||
|
|
||||||
NozzleChangeResult result;
|
NozzleChangeResult result;
|
||||||
if (is_tpu_filament(m_current_tool)) {
|
if (!extruder_change && m_is_multiple_nozzle) {
|
||||||
|
writer.append("M632 S" + std::to_string(new_filament_id) + " M N\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_need_reverse_travel(m_current_tool, extruder_change)) {
|
||||||
bool left_to_right = !m_left_to_right;
|
bool left_to_right = !m_left_to_right;
|
||||||
int tpu_line_count = (real_nozzle_change_line_count + 2 - 1) / 2; // nozzle_change_line_count / 2 round up
|
int tpu_line_count = real_nozzle_change_line_count;
|
||||||
|
float reverse_speed = nozzle_change_speed * 2; // reverse travel runs at double the nozzle-change speed
|
||||||
|
float rt_time = extruder_change ? m_filpar[m_current_tool].ramming_travel_time.first
|
||||||
|
: m_filpar[m_current_tool].ramming_travel_time.second;
|
||||||
|
float need_reverse_travel_dis = rt_time * reverse_speed / 60.f;
|
||||||
|
float real_travel_dis = tpu_line_count * (xr - xl - 2 * m_perimeter_width);
|
||||||
|
if (real_travel_dis < need_reverse_travel_dis)
|
||||||
|
reverse_speed *= real_travel_dis / need_reverse_travel_dis;
|
||||||
|
writer.travel(writer.x(), writer.y() + dy/2);
|
||||||
|
|
||||||
|
for (int i = 0; true; ++i) {
|
||||||
|
need_reverse_travel_dis -= (xr - xl - 2 * m_perimeter_width);
|
||||||
|
float offset_dis = 0.f;
|
||||||
|
if (need_reverse_travel_dis < 0)
|
||||||
|
offset_dis = -need_reverse_travel_dis;
|
||||||
|
if (left_to_right)
|
||||||
|
writer.travel(xr - m_perimeter_width - offset_dis, writer.y(), reverse_speed);
|
||||||
|
else
|
||||||
|
writer.travel(xl + m_perimeter_width + offset_dis, writer.y(), reverse_speed);
|
||||||
|
if (need_reverse_travel_dis < EPSILON) break;
|
||||||
|
if (i == tpu_line_count - 1)
|
||||||
|
break;
|
||||||
|
writer.travel(writer.x(), writer.y() - dy);
|
||||||
|
left_to_right = !left_to_right;
|
||||||
|
}
|
||||||
|
} else if (is_tpu_filament(m_current_tool)) {
|
||||||
|
bool left_to_right = !m_left_to_right;
|
||||||
|
int tpu_line_count = (real_nozzle_change_line_count + 2 - 1) / 2;
|
||||||
nozzle_change_speed *= 2;
|
nozzle_change_speed *= 2;
|
||||||
writer.travel(writer.x(), writer.y() - m_nozzle_change_perimeter_width);
|
writer.travel(writer.x(), writer.y() - m_nozzle_change_perimeter_width);
|
||||||
|
|
||||||
@@ -3080,12 +3233,15 @@ WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.append("; Nozzle change end\n");
|
if (!extruder_change && m_is_multiple_nozzle) writer.append("M633\n");
|
||||||
|
|
||||||
|
writer.append(format_nozzle_change_tag(false, old_filament_id, new_filament_id));
|
||||||
|
|
||||||
result.start_pos = writer.start_pos_rotated();
|
result.start_pos = writer.start_pos_rotated();
|
||||||
result.origin_start_pos = initial_position;
|
result.origin_start_pos = initial_position;
|
||||||
result.end_pos = writer.pos_rotated();
|
result.end_pos = writer.pos_rotated();
|
||||||
result.gcode = writer.gcode();
|
result.gcode = writer.gcode();
|
||||||
|
result.is_extruder_change = extruder_change;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3506,29 +3662,26 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat
|
|||||||
// Emit the arriving-hotend pre-heat inside the M632/M633 nozzle-change barrier. `M632 S<tool>[ H<nozzle>]
|
// Emit the arriving-hotend pre-heat inside the M632/M633 nozzle-change barrier. `M632 S<tool>[ H<nozzle>]
|
||||||
// M N` opens the barrier (M = firmware nozzle-change flag, N = slicer generated), the M104 sets the
|
// M N` opens the barrier (M = firmware nozzle-change flag, N = slicer generated), the M104 sets the
|
||||||
// arriving hotend temp, and `M633` closes it. H2C's grouping is static (no dynamic nozzle map), so the
|
// arriving hotend temp, and `M633` closes it. H2C's grouping is static (no dynamic nozzle map), so the
|
||||||
// H<nozzle> field is omitted (a dynamic nozzle map would supply a real nozzle id, a static map -1 => no
|
// H<nozzle> field is omitted (a dynamic nozzle map would supply a real nozzle id, a static map -1 =>no
|
||||||
// H). The counterproductive fan-on (M106 S255) used for departing-tool cooldown is intentionally
|
// H). The counterproductive fan-on (M106 S255) used for departing-tool cooldown is intentionally
|
||||||
// omitted, since this is a pre-HEAT of the arriving tool. The whole helper is only ever called from
|
// omitted, since this is a pre-HEAT of the arriving tool. The whole helper is only ever called from
|
||||||
// add_M104_by_requirement, which is gated on m_is_multiple_nozzle (extruder_max_nozzle_count>1) => H2C
|
// add_M104_by_requirement, which is gated on m_is_multiple_nozzle (extruder_max_nozzle_count>1) => H2C
|
||||||
// only; every other printer's wipe tower is untouched. The M632 M-flag is itself a firmware barrier, so
|
// only; every other printer's wipe tower is untouched.
|
||||||
// a preceding M400 wait is subsumed.
|
// BBS: extruder change preheat uses M400 + M104 WITHOUT M632/M633 barrier.
|
||||||
|
// M632 barriers are only for carousel nozzle changes (emitted in nozzle_change_new/ramming).
|
||||||
auto format_line_M104 = [this](int target_temp, int target_extruder = -1, bool wait_for_moves = true, const std::string &comment = "") {
|
auto format_line_M104 = [this](int target_temp, int target_extruder = -1, bool wait_for_moves = true, const std::string &comment = "") {
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
buffer += "M632 S" + std::to_string(m_current_tool) + " M N\n";
|
if (wait_for_moves)
|
||||||
|
buffer += "M400\n";
|
||||||
buffer += "M104";
|
buffer += "M104";
|
||||||
if (target_extruder != -1 && target_extruder < (int) m_physical_extruder_map.size())
|
if (target_extruder != -1 && target_extruder < (int) m_physical_extruder_map.size())
|
||||||
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
|
buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder]));
|
||||||
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by the slicer
|
buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by the slicer
|
||||||
if (!comment.empty()) buffer += " ;" + comment;
|
if (!comment.empty()) buffer += " ;" + comment;
|
||||||
buffer += '\n';
|
buffer += '\n';
|
||||||
buffer += "M633\n";
|
|
||||||
(void) wait_for_moves; // the M632 M-flag barrier replaces the former M400 wait
|
|
||||||
return buffer;
|
return buffer;
|
||||||
};
|
};
|
||||||
// Suppress the pre-heat M104 on the first layer and on solid (contact) toolchanges (should_heating).
|
// m_is_multiple_nozzle gate needed because Orca calls toolchange_wipe_new for ALL printers (BBS has it H2C-only).
|
||||||
// m_is_multiple_nozzle folds in the H2C gate so single-nozzle output is untouched.
|
|
||||||
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (layer-static) because Orca's
|
|
||||||
// wipe tower is extruder-level rather than tracking a per-layer nozzle map.
|
|
||||||
bool should_heating = m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON &&
|
bool should_heating = m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON &&
|
||||||
!solid_tool_toolchange && !is_first_layer();
|
!solid_tool_toolchange && !is_first_layer();
|
||||||
auto add_M104_by_requirement = [&writer, &format_line_M104, &should_heating, this]() {
|
auto add_M104_by_requirement = [&writer, &format_line_M104, &should_heating, this]() {
|
||||||
@@ -3710,6 +3863,18 @@ bool WipeTower::is_in_same_extruder(int filament_id_1, int filament_id_2)
|
|||||||
return m_filament_map[filament_id_1] == m_filament_map[filament_id_2];
|
return m_filament_map[filament_id_1] == m_filament_map[filament_id_2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string WipeTower::format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const
|
||||||
|
{
|
||||||
|
const std::string &tag = start ? GCodeProcessor::Nozzle_Change_Start_Tag : GCodeProcessor::Nozzle_Change_End_Tag;
|
||||||
|
int old_nozzle = (old_filament_id >= 0 && old_filament_id < (int)m_filament_nozzle_map.size())
|
||||||
|
? m_filament_nozzle_map[old_filament_id] : -1;
|
||||||
|
int new_nozzle = (new_filament_id >= 0 && new_filament_id < (int)m_filament_nozzle_map.size())
|
||||||
|
? m_filament_nozzle_map[new_filament_id] : -1;
|
||||||
|
char buff[96];
|
||||||
|
snprintf(buff, sizeof(buff), ";%s OF%d NF%d ON%d NN%d\n", tag.c_str(), old_filament_id, new_filament_id, old_nozzle, new_nozzle);
|
||||||
|
return std::string(buff);
|
||||||
|
}
|
||||||
|
|
||||||
// Per-extruder printable-height clamp: is an extruder still allowed to print on this wipe-tower layer,
|
// Per-extruder printable-height clamp: is an extruder still allowed to print on this wipe-tower layer,
|
||||||
// or is it its final layer above the extruder's printable height?
|
// or is it its final layer above the extruder's printable height?
|
||||||
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (1-based map, layer-static),
|
// Orca: the arriving extruder id is resolved as m_filament_map[tool]-1 (1-based map, layer-static),
|
||||||
@@ -3908,6 +4073,19 @@ void WipeTower::plan_tower_new()
|
|||||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
depth += nozzle_change_depth;
|
depth += nozzle_change_depth;
|
||||||
}
|
}
|
||||||
|
if (nozzle_change_depth == 0
|
||||||
|
&& !m_filament_nozzle_map.empty()
|
||||||
|
&& toolchange.old_tool < (int)m_filament_nozzle_map.size() && toolchange.new_tool < (int)m_filament_nozzle_map.size()
|
||||||
|
&& m_filament_nozzle_map[toolchange.old_tool] != m_filament_nozzle_map[toolchange.new_tool]) {
|
||||||
|
double e_flow = nozzle_change_extrusion_flow(m_plan[idx].height);
|
||||||
|
double length = m_filaments_change_length[toolchange.old_tool] / e_flow;
|
||||||
|
int nozzle_change_line_count = length / (m_wipe_tower_width - 2*m_nozzle_change_perimeter_width) + 1;
|
||||||
|
if (has_tpu_filament())
|
||||||
|
nozzle_change_depth = m_tpu_fixed_spacing * nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
|
else
|
||||||
|
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||||
|
depth += nozzle_change_depth;
|
||||||
|
}
|
||||||
toolchange.nozzle_change_depth = nozzle_change_depth;
|
toolchange.nozzle_change_depth = nozzle_change_depth;
|
||||||
toolchange.required_depth = depth;
|
toolchange.required_depth = depth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public:
|
|||||||
Vec2f origin_start_pos; // not rotated
|
Vec2f origin_start_pos; // not rotated
|
||||||
|
|
||||||
std::vector<Vec2f> wipe_path;
|
std::vector<Vec2f> wipe_path;
|
||||||
|
bool is_extruder_change{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ToolChangeResult
|
struct ToolChangeResult
|
||||||
@@ -309,6 +310,8 @@ public:
|
|||||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||||
|
|
||||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||||
|
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||||
|
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||||
|
|
||||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||||
@@ -356,6 +359,12 @@ public:
|
|||||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||||
float filament_cooling_before_tower = 0.f;
|
float filament_cooling_before_tower = 0.f;
|
||||||
|
// .first = extruder change, .second = nozzle change (carousel)
|
||||||
|
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||||
|
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||||
|
std::pair<int,int> precool_target_temp{0, 0};
|
||||||
|
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||||
|
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -395,6 +404,8 @@ public:
|
|||||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||||
int get_filament_category(int filament_id);
|
int get_filament_category(int filament_id);
|
||||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||||
|
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||||
|
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||||
void reset_block_status();
|
void reset_block_status();
|
||||||
int get_wall_filament_for_all_layer();
|
int get_wall_filament_for_all_layer();
|
||||||
// for generate new wipe tower
|
// for generate new wipe tower
|
||||||
@@ -453,6 +464,7 @@ private:
|
|||||||
size_t m_cur_layer_id;
|
size_t m_cur_layer_id;
|
||||||
NozzleChangeResult m_nozzle_change_result;
|
NozzleChangeResult m_nozzle_change_result;
|
||||||
std::vector<int> m_filament_map;
|
std::vector<int> m_filament_map;
|
||||||
|
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||||
bool m_has_tpu_filament{false};
|
bool m_has_tpu_filament{false};
|
||||||
bool m_is_multi_extruder{false};
|
bool m_is_multi_extruder{false};
|
||||||
bool m_use_gap_wall{false};
|
bool m_use_gap_wall{false};
|
||||||
@@ -555,6 +567,7 @@ private:
|
|||||||
|
|
||||||
bool is_tpu_filament(int filament_id) const;
|
bool is_tpu_filament(int filament_id) const;
|
||||||
bool is_petg_filament(int filament_id) const;
|
bool is_petg_filament(int filament_id) const;
|
||||||
|
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include "CustomGCode.hpp"
|
#include "CustomGCode.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
|
#include "ClipperUtils.hpp"
|
||||||
|
#include "Line.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -99,9 +101,87 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
|||||||
m_max_jerk_z = LIMITS(machine_max_jerk_z);
|
m_max_jerk_z = LIMITS(machine_max_jerk_z);
|
||||||
m_max_jerk_e = LIMITS(machine_max_jerk_e);
|
m_max_jerk_e = LIMITS(machine_max_jerk_e);
|
||||||
m_resolution = print_config.resolution.value;
|
m_resolution = print_config.resolution.value;
|
||||||
|
|
||||||
#undef LIMITS
|
#undef LIMITS
|
||||||
#undef LIMITS_UINT
|
#undef LIMITS_UINT
|
||||||
|
// Orca: capture the printable area(s) so a spiral lift can be skipped when its
|
||||||
|
// circle would leave the boundary and collide with the print limits. Full polygons
|
||||||
|
// are stored (not a bounding box) so the check stays correct for non-rectangular
|
||||||
|
// beds, and per-extruder areas are kept so printers with different boundaries per
|
||||||
|
// extruder use the right limit for whichever extruder is active.
|
||||||
|
auto to_scaled_polygon = [](const Pointfs &pts) {
|
||||||
|
Polygon poly;
|
||||||
|
poly.points.reserve(pts.size());
|
||||||
|
for (const Vec2d &p : pts)
|
||||||
|
poly.points.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
|
||||||
|
poly.make_counter_clockwise();
|
||||||
|
return poly;
|
||||||
|
};
|
||||||
|
|
||||||
|
m_bed_printable_area.points.clear();
|
||||||
|
m_extruder_printable_areas.clear();
|
||||||
|
|
||||||
|
if (print_config.printable_area.values.size() >= 3)
|
||||||
|
m_bed_printable_area = to_scaled_polygon(print_config.printable_area.values);
|
||||||
|
|
||||||
|
const std::vector<Pointfs> &extruder_areas = print_config.extruder_printable_area.values;
|
||||||
|
if (!extruder_areas.empty()) {
|
||||||
|
m_extruder_printable_areas.resize(extruder_areas.size());
|
||||||
|
for (size_t i = 0; i < extruder_areas.size(); ++i) {
|
||||||
|
if (extruder_areas[i].size() < 3) {
|
||||||
|
// No dedicated area for this extruder: it can reach the whole bed.
|
||||||
|
m_extruder_printable_areas[i] = m_bed_printable_area;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Polygon extruder_poly = to_scaled_polygon(extruder_areas[i]);
|
||||||
|
if (m_bed_printable_area.points.size() < 3) {
|
||||||
|
m_extruder_printable_areas[i] = std::move(extruder_poly);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The reachable area is the extruder area clipped to the bed. Bed shapes are
|
||||||
|
// convex in practice, so keep the largest resulting contour.
|
||||||
|
Polygons clipped = intersection(extruder_poly, m_bed_printable_area);
|
||||||
|
const Polygon *largest = nullptr;
|
||||||
|
double best_area = 0.;
|
||||||
|
for (const Polygon &p : clipped) {
|
||||||
|
double a = std::abs(p.area());
|
||||||
|
if (a > best_area) { best_area = a; largest = &p; }
|
||||||
|
}
|
||||||
|
m_extruder_printable_areas[i] = largest ? *largest : std::move(extruder_poly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Polygon *GCodeWriter::active_printable_area() const
|
||||||
|
{
|
||||||
|
if (const Extruder *e = this->filament()) {
|
||||||
|
size_t id = e->extruder_id();
|
||||||
|
if (id < m_extruder_printable_areas.size() && m_extruder_printable_areas[id].points.size() >= 3)
|
||||||
|
return &m_extruder_printable_areas[id];
|
||||||
|
}
|
||||||
|
if (m_bed_printable_area.points.size() >= 3)
|
||||||
|
return &m_bed_printable_area;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCodeWriter::spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const
|
||||||
|
{
|
||||||
|
const Polygon *area = this->active_printable_area();
|
||||||
|
if (area == nullptr)
|
||||||
|
return true; // Boundary unknown: don't restrict (preserve previous behavior).
|
||||||
|
|
||||||
|
const Point c = Point::new_scale(center.x(), center.y());
|
||||||
|
const double r_scaled = scale_(radius);
|
||||||
|
const double r2 = r_scaled * r_scaled;
|
||||||
|
|
||||||
|
// The spiral traces a full circle of `radius` around `center`, so the center must lie
|
||||||
|
// inside the printable area and every edge must be at least `radius` away from it.
|
||||||
|
if (!area->contains(c))
|
||||||
|
return false;
|
||||||
|
const Points &pts = area->points;
|
||||||
|
for (size_t i = 0, n = pts.size(); i < n; ++i)
|
||||||
|
if (Line::distance_to_squared(c, pts[i], pts[(i + 1) % n]) < r2)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
|
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
|
||||||
@@ -731,14 +811,19 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BBS: spiral lift only safe with known position
|
// BBS: spiral lift only safe with known position
|
||||||
// TODO: check the arc will move within bed area
|
|
||||||
if (type == LiftType::SpiralLift && this->is_current_position_clear()) {
|
if (type == LiftType::SpiralLift && this->is_current_position_clear()) {
|
||||||
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
||||||
// static spiral alignment when no move in x,y plane.
|
// static spiral alignment when no move in x,y plane.
|
||||||
// spiral centra is a radius distance to the right (y=0)
|
// spiral centra is a radius distance to the right (y=0)
|
||||||
Vec2d ij_offset = { radius, 0 };
|
Vec2d ij_offset = { radius, 0 };
|
||||||
if (target_lift > 0) {
|
// Orca: keep the spiral inside the active extruder's printable area, otherwise
|
||||||
|
// fall back to a normal lift to avoid colliding with the print boundary. m_pos
|
||||||
|
// includes the plate offset, so remove it to match the printable area coordinates.
|
||||||
|
const Vec2d spiral_center = { m_pos.x() - m_x_offset + ij_offset.x(), m_pos.y() - m_y_offset + ij_offset.y() };
|
||||||
|
if (target_lift > 0 && this->spiral_lift_fits_printable_area(spiral_center, radius)) {
|
||||||
lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z");
|
lift_move = this->_spiral_travel_to_z(m_pos(2) + target_lift, ij_offset, "spiral lift Z");
|
||||||
|
} else if (target_lift > 0) {
|
||||||
|
lift_move = _travel_to_z(m_pos(2) + target_lift, "normal lift Z");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//BBS: if position is unknown use normal lift
|
//BBS: if position is unknown use normal lift
|
||||||
@@ -793,7 +878,15 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
|||||||
double radius = delta(2) / (2 * PI * atan(this->filament()->travel_slope()));
|
double radius = delta(2) / (2 * PI * atan(this->filament()->travel_slope()));
|
||||||
Vec2d ij_offset = radius * delta_no_z.normalized();
|
Vec2d ij_offset = radius * delta_no_z.normalized();
|
||||||
ij_offset = { -ij_offset(1), ij_offset(0) };
|
ij_offset = { -ij_offset(1), ij_offset(0) };
|
||||||
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
|
// Orca: only perform the spiral lift if its full circle stays inside the
|
||||||
|
// printable area of the active extruder, otherwise fall back to a normal
|
||||||
|
// lift to avoid colliding with the print boundary. `source` is already in
|
||||||
|
// bed coordinates (plate offset removed), matching the printable area.
|
||||||
|
const Vec2d spiral_center = { source.x() + ij_offset.x(), source.y() + ij_offset.y() };
|
||||||
|
if (this->spiral_lift_fits_printable_area(spiral_center, radius))
|
||||||
|
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
|
||||||
|
else
|
||||||
|
slop_move = _travel_to_z(target.z(), "normal lift Z");
|
||||||
}
|
}
|
||||||
//BBS: SlopeLift
|
//BBS: SlopeLift
|
||||||
else if (m_to_lift_type == LiftType::SlopeLift &&
|
else if (m_to_lift_type == LiftType::SlopeLift &&
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include "Extruder.hpp"
|
#include "Extruder.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
|
#include "Polygon.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "GCode/CoolingBuffer.hpp"
|
#include "GCode/CoolingBuffer.hpp"
|
||||||
|
|
||||||
@@ -132,6 +133,8 @@ public:
|
|||||||
const bool is_bbl_printers() const {return m_is_bbl_printers;}
|
const bool is_bbl_printers() const {return m_is_bbl_printers;}
|
||||||
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
|
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
|
||||||
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
|
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
|
||||||
|
void invalidate_acceleration() { m_last_acceleration = 0; m_last_travel_acceleration = 0; }
|
||||||
|
void invalidate_jerk() { m_last_jerk = 0; }
|
||||||
|
|
||||||
// Returns whether this flavor supports separate print and travel acceleration.
|
// Returns whether this flavor supports separate print and travel acceleration.
|
||||||
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
||||||
@@ -181,6 +184,14 @@ public:
|
|||||||
|
|
||||||
// Orca: slicing resolution in mm
|
// Orca: slicing resolution in mm
|
||||||
double m_resolution = 0.01;
|
double m_resolution = 0.01;
|
||||||
|
// Orca: printable area polygons (scaled, bed coordinates) used to keep spiral lifts
|
||||||
|
// from colliding with the print boundary. m_extruder_printable_areas holds the
|
||||||
|
// per-extruder reachable area (intersected with the bed) when a printer defines
|
||||||
|
// different boundaries per extruder; m_bed_printable_area is the global fallback.
|
||||||
|
// Storing full polygons (rather than a bounding box) keeps the check correct for
|
||||||
|
// non-rectangular beds such as delta/circular printers.
|
||||||
|
Polygon m_bed_printable_area;
|
||||||
|
std::vector<Polygon> m_extruder_printable_areas;
|
||||||
|
|
||||||
std::string m_gcode_label_objects_start;
|
std::string m_gcode_label_objects_start;
|
||||||
std::string m_gcode_label_objects_end;
|
std::string m_gcode_label_objects_end;
|
||||||
@@ -197,6 +208,10 @@ public:
|
|||||||
|
|
||||||
std::string _travel_to_z(double z, const std::string &comment);
|
std::string _travel_to_z(double z, const std::string &comment);
|
||||||
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
|
std::string _spiral_travel_to_z(double z, const Vec2d &ij_offset, const std::string &comment);
|
||||||
|
// Orca: printable area of the active extruder (per-extruder when configured, otherwise the bed). Null when unknown.
|
||||||
|
const Polygon *active_printable_area() const;
|
||||||
|
// Orca: true if a full spiral-lift circle (center in bed coordinates, mm) fits inside the active printable area.
|
||||||
|
bool spiral_lift_fits_printable_area(const Vec2d ¢er, double radius) const;
|
||||||
std::string _retract(double length, double restart_extra, const std::string &comment);
|
std::string _retract(double length, double restart_extra, const std::string &comment);
|
||||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||||
|
|
||||||
|
|||||||
@@ -1177,6 +1177,18 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d
|
|||||||
|
|
||||||
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||||
|
|
||||||
|
double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config)
|
||||||
|
{
|
||||||
|
// A filament id of 0 underflows, and get_at() then falls back to the first nozzle.
|
||||||
|
const double nozzle_diameter = print_config.nozzle_diameter.get_at(region_config.outer_wall_filament_id - 1);
|
||||||
|
ConfigOptionFloatOrPercent width = region_config.outer_wall_line_width;
|
||||||
|
if (width.value == 0)
|
||||||
|
width = object_config.line_width;
|
||||||
|
if (!width.percent && width.value <= 0.)
|
||||||
|
return Flow::auto_extrusion_width(frExternalPerimeter, float(nozzle_diameter));
|
||||||
|
return width.get_abs_value(nozzle_diameter);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
|
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
|
||||||
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||||
const std::vector<ExPolygons> &input_expolygons,
|
const std::vector<ExPolygons> &input_expolygons,
|
||||||
@@ -1347,8 +1359,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
||||||
color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) {
|
color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) {
|
||||||
//BBS: the extrusion line width is outer wall rather than inner wall
|
//BBS: the extrusion line width is outer wall rather than inner wall
|
||||||
const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0);
|
double outer_wall_line_width = resolve_outer_wall_line_width(config, print_object.config(), print_object.print()->config());
|
||||||
double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
|
||||||
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
||||||
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
||||||
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace Slic3r {
|
|||||||
class ExPolygon;
|
class ExPolygon;
|
||||||
class ModelVolume;
|
class ModelVolume;
|
||||||
class PrintObject;
|
class PrintObject;
|
||||||
|
class PrintConfig;
|
||||||
|
class PrintObjectConfig;
|
||||||
|
class PrintRegionConfig;
|
||||||
class FacetsAnnotation;
|
class FacetsAnnotation;
|
||||||
|
|
||||||
using ExPolygons = std::vector<ExPolygon>;
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
@@ -52,6 +55,9 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
|||||||
// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
|
// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
|
||||||
std::vector<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
|
std::vector<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
|
||||||
|
|
||||||
|
// Effective outer-wall line width for a region, resolved against its own nozzle with PrintRegion::flow's fallback.
|
||||||
|
double resolve_outer_wall_line_width(const PrintRegionConfig ®ion_config, const PrintObjectConfig &object_config, const PrintConfig &print_config);
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
namespace boost::polygon {
|
namespace boost::polygon {
|
||||||
|
|||||||
@@ -1376,7 +1376,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
|||||||
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
|
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
|
||||||
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc",
|
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_nc",
|
||||||
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
||||||
"filament_change_length_nc", "filament_prime_volume_nc",
|
"filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc",
|
||||||
"long_retractions_when_ec", "retraction_distances_when_ec",
|
"long_retractions_when_ec", "retraction_distances_when_ec",
|
||||||
"plugin_config_overrides",
|
"plugin_config_overrides",
|
||||||
//ams chamber
|
//ams chamber
|
||||||
@@ -3504,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
|||||||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||||
{
|
{
|
||||||
//BBS: add bed_exclude_area
|
//BBS: add bed_exclude_area
|
||||||
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
|
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
|
||||||
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
||||||
// thus the vector is presented to the user as a single input.
|
// thus the vector is presented to the user as a single input.
|
||||||
diff.emplace_back(opt_key);
|
diff.emplace_back(opt_key);
|
||||||
|
|||||||
+86
-19
@@ -3605,7 +3605,8 @@ std::vector<std::set<int>> Print::get_physical_unprintable_filaments(const std::
|
|||||||
return physical_unprintables;
|
return physical_unprintables;
|
||||||
|
|
||||||
auto get_unprintable_extruder_id = [&](unsigned int filament_idx) -> int {
|
auto get_unprintable_extruder_id = [&](unsigned int filament_idx) -> int {
|
||||||
int status = m_config.filament_printable.values[filament_idx];
|
// filament_printable may be shorter than the filament count; get_at() clamps.
|
||||||
|
int status = m_config.filament_printable.get_at(filament_idx);
|
||||||
for (int i = 0; i < extruder_num; ++i) {
|
for (int i = 0; i < extruder_num; ++i) {
|
||||||
if (!(status >> i & 1)) {
|
if (!(status >> i & 1)) {
|
||||||
return i;
|
return i;
|
||||||
@@ -4017,6 +4018,8 @@ void Print::_make_wipe_tower()
|
|||||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||||
wipe_tower.set_filament_map(this->get_filament_maps());
|
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||||
|
// Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new()
|
||||||
|
wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps());
|
||||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||||
@@ -4052,14 +4055,32 @@ void Print::_make_wipe_tower()
|
|||||||
multi_extruder_flush.emplace_back(wipe_volumes);
|
multi_extruder_flush.emplace_back(wipe_volumes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use NozzleStatusRecorder for per-carousel-slot tracking (BBS pattern).
|
||||||
|
// The original Orca code tracked per-extruder (2 slots), which collapsed all
|
||||||
|
// carousel filaments into one slot and caused massive redundant AMS flushing.
|
||||||
|
auto group_result = get_layered_nozzle_group_result();
|
||||||
|
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
||||||
|
// Fallback (group_result == null) per-physical-nozzle tracking, matching the original
|
||||||
|
// pre-port behavior: remembers the last filament loaded in each physical nozzle slot.
|
||||||
|
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, (unsigned int) -1);
|
||||||
|
|
||||||
std::vector<int>filament_maps = get_filament_maps();
|
std::vector<int>filament_maps = get_filament_maps();
|
||||||
|
int layer_idx = -1;
|
||||||
|
|
||||||
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, -1);
|
|
||||||
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||||
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||||
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
if (group_result) {
|
||||||
|
auto nozzle = group_result->get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||||
|
if (nozzle)
|
||||||
|
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
||||||
|
} else {
|
||||||
|
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||||
|
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
||||||
|
++layer_idx;
|
||||||
|
|
||||||
if (!layer_tools.has_wipe_tower) continue;
|
if (!layer_tools.has_wipe_tower) continue;
|
||||||
bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front();
|
bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front();
|
||||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, current_filament_id);
|
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, current_filament_id);
|
||||||
@@ -4070,30 +4091,76 @@ void Print::_make_wipe_tower()
|
|||||||
if (filament_id == current_filament_id)
|
if (filament_id == current_filament_id)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int nozzle_id = filament_maps[filament_id] - 1;
|
|
||||||
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
|
||||||
|
|
||||||
float volume_to_purge = 0;
|
float volume_to_purge = 0;
|
||||||
if (pre_filament_id != (unsigned int)(-1) && pre_filament_id != filament_id) {
|
|
||||||
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||||
// Fast purge mode uses flush_multiplier_fast; Default is inert.
|
if (group_result) {
|
||||||
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast) ? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
if (nozzle_info) {
|
||||||
volume_to_purge *= flush_multiplier;
|
int extruder_id = nozzle_info->extruder_id;
|
||||||
volume_to_purge = pre_filament_id == -1 ? 0 :
|
int nozzle_id = nozzle_info->group_id;
|
||||||
layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_filament_id, filament_id, volume_to_purge);
|
int prev_nozzle_filament = nozzle_recorder.get_filament_in_nozzle(nozzle_id);
|
||||||
|
|
||||||
|
if (!nozzle_recorder.is_nozzle_empty(nozzle_id) &&
|
||||||
|
static_cast<int>(filament_id) != prev_nozzle_filament) {
|
||||||
|
volume_to_purge = multi_extruder_flush[extruder_id][prev_nozzle_filament][filament_id];
|
||||||
|
// Fast purge mode uses flush_multiplier_fast; Default is inert.
|
||||||
|
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||||
|
? m_config.flush_multiplier_fast.get_at(extruder_id)
|
||||||
|
: m_config.flush_multiplier.get_at(extruder_id);
|
||||||
|
volume_to_purge *= flush_multiplier;
|
||||||
|
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||||
|
*this, current_filament_id, filament_id, volume_to_purge);
|
||||||
|
}
|
||||||
|
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: original Orca per-physical-nozzle path (non-carousel printers).
|
||||||
|
// Flush source is the last filament that occupied THIS nozzle, guarded so the
|
||||||
|
// first use of a nozzle incurs no flush.
|
||||||
|
int nozzle_id = filament_maps[filament_id] - 1;
|
||||||
|
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
||||||
|
if (pre_filament_id != (unsigned int) -1 && pre_filament_id != filament_id) {
|
||||||
|
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||||
|
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||||
|
? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||||
|
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||||
|
volume_to_purge *= flush_multiplier;
|
||||||
|
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||||
|
*this, current_filament_id, filament_id, volume_to_purge);
|
||||||
|
}
|
||||||
|
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||||
float grab_purge_volume = m_config.grab_length.get_at(nozzle_id) * 2.4; //(diameter/2)^2*PI=2.4
|
int grab_extruder_id = filament_maps[filament_id] - 1;
|
||||||
|
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||||
|
|
||||||
// Saving mode reduces the prime volume to 15 mm3; Default is inert.
|
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||||
float prime_volume = (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) ? 15.f : (float) m_config.prime_volume;
|
// filament_prime_volume_nc, filament change (same nozzle slot) uses filament_prime_volume.
|
||||||
|
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
||||||
|
? m_config.filament_prime_volume.values[filament_id]
|
||||||
|
: (float) m_config.prime_volume;
|
||||||
|
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
||||||
|
? m_config.filament_prime_volume_nc.values[filament_id]
|
||||||
|
: (float) m_config.prime_volume;
|
||||||
|
|
||||||
|
float prime_volume = wipe_volume_ec;
|
||||||
|
if (group_result) {
|
||||||
|
bool is_nozzle_change = group_result->are_filaments_same_extruder(current_filament_id, filament_id, layer_idx) &&
|
||||||
|
!group_result->are_filaments_same_nozzle(current_filament_id, filament_id, layer_idx);
|
||||||
|
if (is_nozzle_change) {
|
||||||
|
prime_volume = wipe_volume_nc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
||||||
|
prime_volume = 15.f;
|
||||||
|
}
|
||||||
|
|
||||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||||
prime_volume, volume_to_purge);
|
prime_volume, volume_to_purge);
|
||||||
current_filament_id = filament_id;
|
current_filament_id = filament_id;
|
||||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
|
||||||
}
|
}
|
||||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||||
|
|
||||||
|
|||||||
@@ -7384,7 +7384,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->tooltip = L("The flush multiplier used in fast purge mode.");
|
def->tooltip = L("The flush multiplier used in fast purge mode.");
|
||||||
def->set_default_value(new ConfigOptionFloats{1.2});
|
def->set_default_value(new ConfigOptionFloats{1.2});
|
||||||
|
|
||||||
// BBS
|
// Orca: used by the generic (Type2) wipe tower; also the fallback for filament_prime_volume on Type1.
|
||||||
def = this->add("prime_volume", coFloat);
|
def = this->add("prime_volume", coFloat);
|
||||||
def->label = L("Prime volume");
|
def->label = L("Prime volume");
|
||||||
def->tooltip = L("This is the volume of material to prime the extruder with on the tower.");
|
def->tooltip = L("This is the volume of material to prime the extruder with on the tower.");
|
||||||
@@ -8030,6 +8030,16 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
// Used by the Type1 wipe tower: filament_prime_volume on a filament change,
|
||||||
|
// filament_prime_volume_nc on a hotend/nozzle change. Type2 uses prime_volume instead.
|
||||||
|
def = this->add("filament_prime_volume", coFloats);
|
||||||
|
def->label = L("Filament change");
|
||||||
|
def->tooltip = L("The volume of material required to prime the extruder on the tower, excluding a hotend change.");
|
||||||
|
def->sidetext = L("mm³");
|
||||||
|
def->min = 1.0;
|
||||||
|
def->mode = comSimple;
|
||||||
|
def->set_default_value(new ConfigOptionFloats{45.});
|
||||||
|
|
||||||
def = this->add("filament_prime_volume_nc", coFloats);
|
def = this->add("filament_prime_volume_nc", coFloats);
|
||||||
def->label = L("Hotend change");
|
def->label = L("Hotend change");
|
||||||
def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower.");
|
def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower.");
|
||||||
@@ -9050,7 +9060,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||||||
"retraction_distance_when_cut",
|
"retraction_distance_when_cut",
|
||||||
"internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time",
|
"internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time",
|
||||||
"smooth_coefficient", "overhang_totally_speed", "silent_mode",
|
"smooth_coefficient", "overhang_totally_speed", "silent_mode",
|
||||||
"overhang_speed_classic", "filament_prime_volume",
|
"overhang_speed_classic",
|
||||||
"anisotropic_surfaces", // superseded by top_surface_fill_order / bottom_surface_fill_order
|
"anisotropic_surfaces", // superseded by top_surface_fill_order / bottom_surface_fill_order
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1841,6 +1841,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
// BBS: wipe tower is only used for priming
|
// BBS: wipe tower is only used for priming
|
||||||
((ConfigOptionFloat, prime_volume))
|
((ConfigOptionFloat, prime_volume))
|
||||||
// Nozzle-change (nc) prime volume + pre-heat delta
|
// Nozzle-change (nc) prime volume + pre-heat delta
|
||||||
|
((ConfigOptionFloats, filament_prime_volume))
|
||||||
((ConfigOptionFloats, filament_prime_volume_nc))
|
((ConfigOptionFloats, filament_prime_volume_nc))
|
||||||
((ConfigOptionFloatsNullable, filament_preheat_temperature_delta))
|
((ConfigOptionFloatsNullable, filament_preheat_temperature_delta))
|
||||||
((ConfigOptionFloats, flush_multiplier))
|
((ConfigOptionFloats, flush_multiplier))
|
||||||
|
|||||||
@@ -771,6 +771,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcode << m_writer.reset_e();
|
||||||
gcode << m_writer.set_pressure_advance(m_params.start);
|
gcode << m_writer.set_pressure_advance(m_params.start);
|
||||||
gcode << "; end pressure advance pattern for layer\n";
|
gcode << "; end pressure advance pattern for layer\n";
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ struct Calib_Params
|
|||||||
{
|
{
|
||||||
Calib_Params() : mode(CalibMode::Calib_None){};
|
Calib_Params() : mode(CalibMode::Calib_None){};
|
||||||
int extruder_id = 0;
|
int extruder_id = 0;
|
||||||
double start, end, step;
|
double start = 0.0, end = 1.0, step = 0.1;
|
||||||
bool print_numbers;
|
bool print_numbers = false;
|
||||||
double freqStartX, freqEndX, freqStartY, freqEndY;
|
double freqStartX = 0.0, freqEndX = 1.0, freqStartY = 0.0, freqEndY = 1.0;
|
||||||
int test_model;
|
int test_model = 0;
|
||||||
std::string shaper_type;
|
std::string shaper_type;
|
||||||
std::vector<double> accelerations;
|
std::vector<double> accelerations;
|
||||||
std::vector<double> speeds;
|
std::vector<double> speeds;
|
||||||
|
|||||||
@@ -558,6 +558,7 @@ namespace Slic3r
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning();
|
||||||
if (m_agent)
|
if (m_agent)
|
||||||
{
|
{
|
||||||
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
|
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
|
||||||
|
|||||||
+16
-10
@@ -1043,14 +1043,14 @@ void TextCtrl::propagate_value()
|
|||||||
|
|
||||||
void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) {
|
void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) {
|
||||||
m_disable_change_event = !change_event;
|
m_disable_change_event = !change_event;
|
||||||
|
|
||||||
if (m_opt.nullable) {
|
if (m_opt.nullable) {
|
||||||
const bool m_is_na_val = value.empty() || (boost::any_cast<wxString>(value) == _(L("N/A")));
|
const bool m_is_na_val = value.empty() || (boost::any_cast<wxString>(value) == _(L("N/A")));
|
||||||
if (!m_is_na_val)
|
if (!m_is_na_val)
|
||||||
m_last_meaningful_value = value;
|
m_last_meaningful_value = value;
|
||||||
text_ctrl()->SetValue(boost::any_cast<wxString>(value)); // BBS
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
text_ctrl()->SetValue(value.empty() ? "" : boost::any_cast<wxString>(value)); // BBS // BBS: null value
|
text_ctrl()->SetValue(value.empty() ? wxString() : boost::any_cast<wxString>(value));
|
||||||
m_disable_change_event = false;
|
m_disable_change_event = false;
|
||||||
|
|
||||||
if (!change_event) {
|
if (!change_event) {
|
||||||
@@ -1187,18 +1187,24 @@ void CheckBox::set_value(const boost::any& value, bool change_event)
|
|||||||
m_disable_change_event = !change_event;
|
m_disable_change_event = !change_event;
|
||||||
if (m_opt.nullable) {
|
if (m_opt.nullable) {
|
||||||
const bool is_value_unsigned_char = value.type() == typeid(unsigned char);
|
const bool is_value_unsigned_char = value.type() == typeid(unsigned char);
|
||||||
|
bool bool_value = false;
|
||||||
|
|
||||||
m_is_na_val = value.empty() || (is_value_unsigned_char &&
|
m_is_na_val = value.empty() || (is_value_unsigned_char &&
|
||||||
boost::any_cast<unsigned char>(value) == ConfigOptionBoolsNullable::nil_value());
|
boost::any_cast<unsigned char>(value) == ConfigOptionBoolsNullable::nil_value());
|
||||||
if (!m_is_na_val)
|
|
||||||
m_last_meaningful_value = is_value_unsigned_char ? value : static_cast<unsigned char>(boost::any_cast<bool>(value));
|
|
||||||
|
|
||||||
const auto bool_value = is_value_unsigned_char ?
|
if (!m_is_na_val) {
|
||||||
boost::any_cast<unsigned char>(value) != 0 :
|
bool_value = is_value_unsigned_char ?
|
||||||
boost::any_cast<bool>(value);
|
boost::any_cast<unsigned char>(value) != 0 :
|
||||||
dynamic_cast<::CheckBox*>(window)->SetValue(m_is_na_val ? false : bool_value); // BBS
|
boost::any_cast<bool>(value);
|
||||||
|
m_last_meaningful_value = is_value_unsigned_char ? value : static_cast<unsigned char>(bool_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic_cast<::CheckBox*>(window)->SetValue(bool_value);
|
||||||
}
|
}
|
||||||
else if (!value.empty()) // BBS: null value
|
else if (!value.empty()){ // BBS: null value
|
||||||
dynamic_cast<::CheckBox*>(window)->SetValue(boost::any_cast<bool>(value)); // BBS
|
dynamic_cast<::CheckBox*>(window)->SetValue(boost::any_cast<bool>(value)); // BBS
|
||||||
|
}
|
||||||
|
|
||||||
dynamic_cast<::CheckBox*>(window)->SetHalfChecked(value.empty());
|
dynamic_cast<::CheckBox*>(window)->SetHalfChecked(value.empty());
|
||||||
m_disable_change_event = false;
|
m_disable_change_event = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-12
@@ -6127,18 +6127,23 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
|||||||
}
|
}
|
||||||
else if (msg == "unsigned_studio") {
|
else if (msg == "unsigned_studio") {
|
||||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio";
|
BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio";
|
||||||
MessageDialog
|
// Plugin re-emits this on every subscribe retry; latch it so it shows
|
||||||
msg_dlg(nullptr,
|
// once per connection episode.
|
||||||
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
if (!m_show_error_msgdlg && !m_unsigned_plugin_warning_shown) {
|
||||||
"Please go to your printer's settings and:\n"
|
m_unsigned_plugin_warning_shown = true;
|
||||||
"1. Turn on LAN mode\n"
|
MessageDialog
|
||||||
"2. Enable Developer mode\n\n"
|
msg_dlg(nullptr,
|
||||||
"Developer mode allows the printer to work exclusively through local network access, "
|
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
||||||
"enabling full functionality with OrcaSlicer."),
|
"Please go to your printer's settings and:\n"
|
||||||
_L("Network Plug-in Restriction"), wxAPPLY | wxOK);
|
"1. Turn on LAN mode\n"
|
||||||
m_show_error_msgdlg = true;
|
"2. Enable Developer mode\n\n"
|
||||||
msg_dlg.ShowModal();
|
"Developer mode allows the printer to work exclusively through local network access, "
|
||||||
m_show_error_msgdlg = false;
|
"enabling full functionality with OrcaSlicer."),
|
||||||
|
_L("Network Plug-in Restriction"), wxAPPLY | wxOK);
|
||||||
|
m_show_error_msgdlg = true;
|
||||||
|
msg_dlg.ShowModal();
|
||||||
|
m_show_error_msgdlg = false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ private:
|
|||||||
public:
|
public:
|
||||||
//try again when subscription fails
|
//try again when subscription fails
|
||||||
void on_start_subscribe_again(std::string dev_id);
|
void on_start_subscribe_again(std::string dev_id);
|
||||||
|
void reset_unsigned_plugin_warning() { m_unsigned_plugin_warning_shown = false; }
|
||||||
std::string get_local_models_path();
|
std::string get_local_models_path();
|
||||||
bool OnInit() override;
|
bool OnInit() override;
|
||||||
int OnExit() override;
|
int OnExit() override;
|
||||||
|
|||||||
@@ -215,8 +215,8 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve
|
|||||||
const EOptionType option_type = move_type_to_option(curr_type);
|
const EOptionType option_type = move_type_to_option(curr_type);
|
||||||
if (option_type == EOptionType::COUNT || option_type == EOptionType::Travels || option_type == EOptionType::Wipes) {
|
if (option_type == EOptionType::COUNT || option_type == EOptionType::Travels || option_type == EOptionType::Wipes) {
|
||||||
if (ret.vertices.empty() || prev.type != curr.type || prev.extrusion_role != curr.extrusion_role
|
if (ret.vertices.empty() || prev.type != curr.type || prev.extrusion_role != curr.extrusion_role
|
||||||
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
// ORCA: Split the path when a preview value changes.
|
||||||
|| prev.mm3_per_mm != curr.mm3_per_mm) {
|
|| prev.mm3_per_mm != curr.mm3_per_mm || prev.acceleration != curr.acceleration || prev.jerk != curr.jerk) {
|
||||||
// to allow libvgcode to properly detect the start/end of a path we need to add a 'phantom' vertex
|
// to allow libvgcode to properly detect the start/end of a path we need to add a 'phantom' vertex
|
||||||
// equal to the current one with the exception of the position, which should match the previous move position,
|
// equal to the current one with the exception of the position, which should match the previous move position,
|
||||||
// and the times, which are set to zero
|
// and the times, which are set to zero
|
||||||
|
|||||||
@@ -2338,7 +2338,7 @@ void NotificationManager::SharedProfilesNotification::render_text(ImGuiWrapper&
|
|||||||
{
|
{
|
||||||
float hyper_y = starting_y + m_endlines.size() * shift_y + m_line_height * .5f;
|
float hyper_y = starting_y + m_endlines.size() * shift_y + m_line_height * .5f;
|
||||||
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height * .5f;
|
float dont_show_y = hyper_y + ImGui::CalcTextSize((m_hypertext + " ").c_str()).y + m_line_height * .5f;
|
||||||
std::string dont_show_text = _u8L("Don't show again") + std::to_string(m_endlines.size());
|
std::string dont_show_text = _u8L("Don't show again");
|
||||||
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
|
ImVec2 part_size = ImGui::CalcTextSize(dont_show_text.c_str());
|
||||||
|
|
||||||
if (!m_multiline && m_lines_count > 2) {
|
if (!m_multiline && m_lines_count > 2) {
|
||||||
|
|||||||
@@ -2662,7 +2662,8 @@ bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* boundi
|
|||||||
|
|
||||||
if (instance_box.min.z() < SINKING_Z_THRESHOLD) {
|
if (instance_box.min.z() < SINKING_Z_THRESHOLD) {
|
||||||
// Orca: For sinking object, we use a more expensive algorithm so part below build plate won't be considered
|
// Orca: For sinking object, we use a more expensive algorithm so part below build plate won't be considered
|
||||||
if (plate_box.intersects(instance_box)) {
|
// m_plater is null in CLI mode.
|
||||||
|
if (m_plater && plate_box.intersects(instance_box)) {
|
||||||
// TODO: FIXME: this does not take exclusion area into account
|
// TODO: FIXME: this does not take exclusion area into account
|
||||||
const BuildVolume build_volume(get_shape(), m_plater->build_volume().printable_height(), m_extruder_areas, m_extruder_heights);
|
const BuildVolume build_volume(get_shape(), m_plater->build_volume().printable_height(), m_extruder_areas, m_extruder_heights);
|
||||||
const auto state = instance->calc_print_volume_state(build_volume);
|
const auto state = instance->calc_print_volume_state(build_volume);
|
||||||
|
|||||||
@@ -11988,12 +11988,53 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
|
|||||||
auto nozzle_volumes_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
auto nozzle_volumes_values = preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||||
assert(obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2);
|
assert(obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2);
|
||||||
if (obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2) {
|
if (obj->GetExtderSystem()->GetTotalExtderCount() == 2 && nozzle_volumes_values.size() == 2) {
|
||||||
// Map device flow->volume via the table, not `flowtype - 1` (which mis-maps U_FLOW to nvtHybrid).
|
// [Vortek] H2C: Use BBS-style NozzleGroupInfo comparison instead of direct nozzle type match.
|
||||||
NozzleVolumeType right_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(0));
|
// This correctly handles Hybrid presets (which expand into per-type counts) and detects
|
||||||
NozzleVolumeType left_nozzle_type = DevNozzle::ToNozzleVolumeType(obj->GetExtderSystem()->GetNozzleFlowType(1));
|
// never-synced state (nozzle_count==0) so the first sync dialog appears.
|
||||||
NozzleVolumeType preset_left_type = NozzleVolumeType(nozzle_volumes_values[0]);
|
// After device sync, extruder_nozzle_stats matches printer → dialog suppressed.
|
||||||
NozzleVolumeType preset_right_type = NozzleVolumeType(nozzle_volumes_values[1]);
|
// Reference to BBS: BambuStudio/src/slic3r/GUI/Plater.cpp is_extruder_stat_synced()
|
||||||
is_same_as_printer = (left_nozzle_type == preset_left_type && right_nozzle_type == preset_right_type);
|
using namespace MultiNozzleUtils;
|
||||||
|
auto nozzle_diameter_values = preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values;
|
||||||
|
|
||||||
|
// Build preset nozzle groups from extruder_nozzle_stats config
|
||||||
|
std::vector<std::vector<NozzleGroupInfo>> preset_nozzle_infos(nozzle_diameter_values.size());
|
||||||
|
for (size_t extruder_id = 0; extruder_id < nozzle_diameter_values.size(); ++extruder_id) {
|
||||||
|
NozzleVolumeType preset_volume_type = NozzleVolumeType(nozzle_volumes_values[extruder_id]);
|
||||||
|
std::string preset_diameter = format_diameter_to_str(nozzle_diameter_values[extruder_id]);
|
||||||
|
|
||||||
|
if (preset_volume_type == nvtHybrid) {
|
||||||
|
// Hybrid: expand into separate groups for each nozzle type from stats
|
||||||
|
int std_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtStandard);
|
||||||
|
int hf_count = getExtruderNozzleCount(preset_bundle, extruder_id, nvtHighFlow);
|
||||||
|
if (std_count > 0)
|
||||||
|
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, nvtStandard, extruder_id, std_count);
|
||||||
|
if (hf_count > 0)
|
||||||
|
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, nvtHighFlow, extruder_id, hf_count);
|
||||||
|
// If both are 0 → never synced → empty group → will mismatch
|
||||||
|
} else {
|
||||||
|
int count = getExtruderNozzleCount(preset_bundle, extruder_id, preset_volume_type);
|
||||||
|
preset_nozzle_infos[extruder_id].emplace_back(preset_diameter, preset_volume_type, extruder_id, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare with printer nozzle groups
|
||||||
|
auto printer_groups = obj->GetNozzleSystem()->GetNozzleGroups();
|
||||||
|
for (const auto& preset_groups : preset_nozzle_infos) {
|
||||||
|
for (const auto& preset_group : preset_groups) {
|
||||||
|
if (preset_group.nozzle_count == 0) {
|
||||||
|
// Never synced: if printer has nozzles of this type → needs sync
|
||||||
|
if (std::find_if(printer_groups.begin(), printer_groups.end(),
|
||||||
|
[&preset_group](const NozzleGroupInfo& elem) { return preset_group.is_same_type(elem); })
|
||||||
|
!= printer_groups.end()) {
|
||||||
|
is_same_as_printer = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (std::find(printer_groups.begin(), printer_groups.end(), preset_group) == printer_groups.end()) {
|
||||||
|
is_same_as_printer = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::map<int, int>> ams_count_info;
|
std::vector<std::map<int, int>> ams_count_info;
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ std::string plugin_defaults_user_script()
|
|||||||
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Injected into every page at document start (before the plugin's own scripts).
|
// Injected into the top-level page at document start (before the plugin's own
|
||||||
// Defines window.orca as the only host surface the page may use. It references
|
// scripts). Defines window.orca as the only host surface the page may use. It
|
||||||
// window.wx lazily (at call time) so it never races the backend's deferred
|
// references window.wx lazily (at call time) so it never races the backend's
|
||||||
// registration of the "wx" message handler. Guarded against double-injection so
|
// deferred registration of the "wx" message handler. Guarded against
|
||||||
// it is harmless if also prepended.
|
// double-injection so it is harmless if also prepended.
|
||||||
constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
||||||
(function () {
|
(function () {
|
||||||
|
if (window.top !== window.self) return;
|
||||||
if (window.orca) return;
|
if (window.orca) return;
|
||||||
var handlers = [];
|
var handlers = [];
|
||||||
function send(kind, data) {
|
function send(kind, data) {
|
||||||
|
|||||||
@@ -300,7 +300,12 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
|||||||
|
|
||||||
add_action("open_folder", "Show in folder", has_local);
|
add_action("open_folder", "Show in folder", has_local);
|
||||||
|
|
||||||
add_action("reinstall_plugin", "Reinstall");
|
if (is_cloud) {
|
||||||
|
add_action("reinstall_plugin", "Reinstall");
|
||||||
|
} else {
|
||||||
|
add_action("reload_plugin", "Reload");
|
||||||
|
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||||
|
}
|
||||||
|
|
||||||
return available_actions;
|
return available_actions;
|
||||||
}
|
}
|
||||||
@@ -739,11 +744,13 @@ void PluginsDialog::handle_plugin_menu_action(const std::string& plugin_key, con
|
|||||||
unsubscribe_cloud_plugin(row_data);
|
unsubscribe_cloud_plugin(row_data);
|
||||||
} else if (action == "delete_mine_plugin") {
|
} else if (action == "delete_mine_plugin") {
|
||||||
delete_mine_local_and_cloud_plugin(plugin_key);
|
delete_mine_local_and_cloud_plugin(plugin_key);
|
||||||
|
} else if (action == "reload_plugin") {
|
||||||
|
reload_local_plugin(plugin_key, /*clear_cache=*/false);
|
||||||
|
} else if (action == "clear_cache_reload_plugin") {
|
||||||
|
reload_local_plugin(plugin_key, /*clear_cache=*/true);
|
||||||
} else if (action == "reinstall_plugin") {
|
} else if (action == "reinstall_plugin") {
|
||||||
if (row_data.is_cloud_plugin())
|
if (row_data.is_cloud_plugin())
|
||||||
reinstall_cloud_plugin(row_data);
|
reinstall_cloud_plugin(row_data);
|
||||||
else
|
|
||||||
reinstall_local_plugin(plugin_key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1123,7 +1130,7 @@ void PluginsDialog::unsubscribe_cloud_plugin(const PluginDescriptor& plugin)
|
|||||||
_L("Unsubscribing plugin"), _L("Deleting local files and unsubscribing plugin..."));
|
_L("Unsubscribing plugin"), _L("Deleting local files and unsubscribing plugin..."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
|
void PluginsDialog::reload_local_plugin(const std::string& plugin_key, bool clear_cache)
|
||||||
{
|
{
|
||||||
if (plugin_key.empty())
|
if (plugin_key.empty())
|
||||||
return;
|
return;
|
||||||
@@ -1132,11 +1139,34 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
|
|||||||
std::pair<bool, std::string> reload_result{false, ""};
|
std::pair<bool, std::string> reload_result{false, ""};
|
||||||
try {
|
try {
|
||||||
reload_result = run_with_dialog_wait(
|
reload_result = run_with_dialog_wait(
|
||||||
[plugin_key, was_loaded]() -> std::pair<bool, std::string> {
|
[plugin_key, was_loaded, clear_cache]() -> std::pair<bool, std::string> {
|
||||||
PluginManager& manager = PluginManager::instance();
|
PluginManager& manager = PluginManager::instance();
|
||||||
|
|
||||||
|
boost::filesystem::path cache_dir;
|
||||||
|
if (clear_cache) {
|
||||||
|
PluginDescriptor descriptor;
|
||||||
|
if (!manager.try_get_plugin_descriptor(plugin_key, descriptor))
|
||||||
|
return {false, "Plugin not found."};
|
||||||
|
|
||||||
|
boost::filesystem::path resolved_root;
|
||||||
|
std::string resolve_error;
|
||||||
|
if (!resolve_allowed_plugin_root(descriptor, {get_orca_plugins_dir()},
|
||||||
|
"Refusing to clear a plugin cache outside the local plugin directory.",
|
||||||
|
resolved_root, resolve_error))
|
||||||
|
return {false, resolve_error};
|
||||||
|
cache_dir = resolved_root / "__whl_extracted__";
|
||||||
|
}
|
||||||
|
|
||||||
if (!manager.unload_plugin(plugin_key))
|
if (!manager.unload_plugin(plugin_key))
|
||||||
return {false, "Failed to unload plugin."};
|
return {false, "Failed to unload plugin."};
|
||||||
|
|
||||||
|
if (clear_cache) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::filesystem::remove_all(cache_dir, ec);
|
||||||
|
if (ec)
|
||||||
|
return {false, "Failed to clear plugin cache: " + ec.message()};
|
||||||
|
}
|
||||||
|
|
||||||
manager.load_plugin(plugin_key, false);
|
manager.load_plugin(plugin_key, false);
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) || !manager.is_plugin_loaded(plugin_key))
|
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) || !manager.is_plugin_loaded(plugin_key))
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ private:
|
|||||||
void open_plugin_folder(const Slic3r::PluginDescriptor& plugin);
|
void open_plugin_folder(const Slic3r::PluginDescriptor& plugin);
|
||||||
void delete_local_plugin(const Slic3r::PluginDescriptor& plugin);
|
void delete_local_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||||
void unsubscribe_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
void unsubscribe_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||||
void reinstall_local_plugin(const std::string& plugin_key);
|
void reload_local_plugin(const std::string& plugin_key, bool clear_cache);
|
||||||
void reinstall_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
void reinstall_cloud_plugin(const Slic3r::PluginDescriptor& plugin);
|
||||||
void delete_mine_local_and_cloud_plugin(const std::string& plugin_key);
|
void delete_mine_local_and_cloud_plugin(const std::string& plugin_key);
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,49 @@ void PrePrintChecker::add_with_link(PrintDialogStatus state, wxString msg, wxStr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orca: acknowledgement-checkbox variant of add(); see add_with_link() for the shared classification.
|
||||||
|
void PrePrintChecker::add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback)
|
||||||
|
{
|
||||||
|
prePrintInfo info;
|
||||||
|
|
||||||
|
if (is_error(state)) {
|
||||||
|
info.level = prePrintInfoLevel::Error;
|
||||||
|
} else if (is_warning(state)) {
|
||||||
|
info.level = prePrintInfoLevel::Warning;
|
||||||
|
} else {
|
||||||
|
info.level = prePrintInfoLevel::Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_error_printer(state)) {
|
||||||
|
info.type = prePrintInfoType::Printer;
|
||||||
|
} else if (is_error_filament(state)) {
|
||||||
|
info.type = prePrintInfoType::Filament;
|
||||||
|
} else if (is_warning_printer(state)) {
|
||||||
|
info.type = prePrintInfoType::Printer;
|
||||||
|
} else if (is_warning_filament(state)) {
|
||||||
|
info.type = prePrintInfoType::Filament;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.msg = msg;
|
||||||
|
info.checkbox_label = checkbox_label;
|
||||||
|
info.checkbox_checked = checked;
|
||||||
|
info.checkbox_callback = checkbox_callback;
|
||||||
|
|
||||||
|
switch (info.type) {
|
||||||
|
case prePrintInfoType::Filament:
|
||||||
|
if (std::find(filamentList.begin(), filamentList.end(), info) == filamentList.end()) {
|
||||||
|
filamentList.push_back(info);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case prePrintInfoType::Printer:
|
||||||
|
if (std::find(printerList.begin(), printerList.end(), info) == printerList.end()) {
|
||||||
|
printerList.push_back(info);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//void PrePrintMsgBoard::add(const wxString &msg, const wxString &tips, bool is_error)
|
//void PrePrintMsgBoard::add(const wxString &msg, const wxString &tips, bool is_error)
|
||||||
//{
|
//{
|
||||||
@@ -310,6 +353,18 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector<prePrintInfo>& infos)
|
|||||||
label->Show();
|
label->Show();
|
||||||
m_sizer->Add(label, 0, wxBOTTOM, FromDIP(4));
|
m_sizer->Add(label, 0, wxBOTTOM, FromDIP(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orca: acknowledgement checkbox rendered beneath the message (e.g. a nozzle diameter that
|
||||||
|
// differs from the one the printer remembers, which the user may legitimately override).
|
||||||
|
if (!info.checkbox_label.empty())
|
||||||
|
{
|
||||||
|
wxCheckBox* checkbox = new wxCheckBox(this, wxID_ANY, info.checkbox_label);
|
||||||
|
checkbox->SetForegroundColour(_GetLabelColour(info));
|
||||||
|
checkbox->SetValue(info.checkbox_checked);
|
||||||
|
auto cb = info.checkbox_callback;
|
||||||
|
checkbox->Bind(wxEVT_CHECKBOX, [cb](wxCommandEvent& e) { if (cb) cb(e.IsChecked()); });
|
||||||
|
m_sizer->Add(checkbox, 0, wxBOTTOM, FromDIP(4));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->Show();
|
this->Show();
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ struct prePrintInfo
|
|||||||
wxString wiki_url;
|
wxString wiki_url;
|
||||||
wxString link_label; // optional: clickable text appended after msg
|
wxString link_label; // optional: clickable text appended after msg
|
||||||
std::function<void()> link_callback; // optional: internal action for link_label click
|
std::function<void()> link_callback; // optional: internal action for link_label click
|
||||||
|
wxString checkbox_label; // optional: an acknowledgement checkbox under msg
|
||||||
|
bool checkbox_checked{false};
|
||||||
|
std::function<void(bool)> checkbox_callback; // called with the new state on toggle
|
||||||
int index{0};
|
int index{0};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -33,8 +36,9 @@ public:
|
|||||||
return level == other.level && type == other.type &&
|
return level == other.level && type == other.type &&
|
||||||
msg == other.msg && tips == other.tips &&
|
msg == other.msg && tips == other.tips &&
|
||||||
wiki_url == other.wiki_url && link_label == other.link_label &&
|
wiki_url == other.wiki_url && link_label == other.link_label &&
|
||||||
|
checkbox_label == other.checkbox_label && checkbox_checked == other.checkbox_checked &&
|
||||||
index == other.index;
|
index == other.index;
|
||||||
// link_callback excluded: std::function is not comparable
|
// link_callback / checkbox_callback excluded: std::function is not comparable
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +63,6 @@ enum PrintDialogStatus : unsigned int {
|
|||||||
PrintStatusInPrinting,
|
PrintStatusInPrinting,
|
||||||
PrintStatusNozzleMatchInvalid,
|
PrintStatusNozzleMatchInvalid,
|
||||||
PrintStatusNozzleDataInvalid,
|
PrintStatusNozzleDataInvalid,
|
||||||
PrintStatusNozzleDiameterMismatch,
|
|
||||||
PrintStatusNozzleTypeMismatch,
|
PrintStatusNozzleTypeMismatch,
|
||||||
PrintStatusNozzleNoMatchedHotends,
|
PrintStatusNozzleNoMatchedHotends,
|
||||||
PrintStatusNozzleRackMaximumInstalled,
|
PrintStatusNozzleRackMaximumInstalled,
|
||||||
@@ -106,6 +109,9 @@ enum PrintDialogStatus : unsigned int {
|
|||||||
PrintStatusFilaSwitcherSlicingNotMatch,
|
PrintStatusFilaSwitcherSlicingNotMatch,
|
||||||
PrintStatusRackNozzleNumUnmeetWarning,
|
PrintStatusRackNozzleNumUnmeetWarning,
|
||||||
PrintStatusHasUnreliableNozzleWarning,
|
PrintStatusHasUnreliableNozzleWarning,
|
||||||
|
// Orca: a nozzle diameter that differs from the one the printer remembers is a warning,
|
||||||
|
// not an error, so non-standard nozzles can still be printed with.
|
||||||
|
PrintStatusNozzleDiameterMismatch,
|
||||||
PrintStatusPrinterWarningEnd,
|
PrintStatusPrinterWarningEnd,
|
||||||
|
|
||||||
// Warnings for filament
|
// Warnings for filament
|
||||||
@@ -159,6 +165,9 @@ public:
|
|||||||
void add(PrintDialogStatus state, wxString msg, wxString tip, const wxString& wiki_url);
|
void add(PrintDialogStatus state, wxString msg, wxString tip, const wxString& wiki_url);
|
||||||
// Orca: minimal callback-link render path instead of the full style-bitmask machinery.
|
// Orca: minimal callback-link render path instead of the full style-bitmask machinery.
|
||||||
void add_with_link(PrintDialogStatus state, wxString msg, wxString link_label, std::function<void()> link_callback);
|
void add_with_link(PrintDialogStatus state, wxString msg, wxString link_label, std::function<void()> link_callback);
|
||||||
|
// Orca: render msg with an acknowledgement checkbox beneath it, for an overridable warning that
|
||||||
|
// the user must tick before proceeding (checkbox_callback reports the new state).
|
||||||
|
void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function<void(bool)> checkbox_callback);
|
||||||
static ::std::string get_print_status_info(PrintDialogStatus status);
|
static ::std::string get_print_status_info(PrintDialogStatus status);
|
||||||
|
|
||||||
wxString get_pre_state_msg(PrintDialogStatus status);
|
wxString get_pre_state_msg(PrintDialogStatus status);
|
||||||
|
|||||||
@@ -246,7 +246,10 @@ void PrinterWebView::SendAPIKey()
|
|||||||
// RemoveAllUserScripts causes WebView to forget about our script message handler,
|
// RemoveAllUserScripts causes WebView to forget about our script message handler,
|
||||||
// so re-add it here.
|
// so re-add it here.
|
||||||
m_browser->RemoveScriptMessageHandler("wx");
|
m_browser->RemoveScriptMessageHandler("wx");
|
||||||
m_browser->AddScriptMessageHandler("wx");
|
if (m_browser->AddScriptMessageHandler("wx"))
|
||||||
|
WebView::MarkScriptMessageHandlerAdded(m_browser);
|
||||||
|
else
|
||||||
|
wxLogError("Could not add script message handler");
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
// Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared.
|
// Re-inject the vue-resize/WebKitGTK workaround that RemoveAllUserScripts just cleared.
|
||||||
|
|||||||
@@ -2269,8 +2269,11 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
|||||||
Enable_Refresh_Button(true);
|
Enable_Refresh_Button(true);
|
||||||
Enable_Send_Button(false);
|
Enable_Send_Button(false);
|
||||||
} else if (status == PrintStatusNozzleDiameterMismatch) {
|
} else if (status == PrintStatusNozzleDiameterMismatch) {
|
||||||
|
// Orca: overridable — a non-standard nozzle is a valid reason to differ. Send is gated on
|
||||||
|
// the acknowledgement checkbox added to the message board below (add_with_checkbox), which
|
||||||
|
// is enabled only while the user's acknowledgement still matches the current mismatch.
|
||||||
Enable_Refresh_Button(true);
|
Enable_Refresh_Button(true);
|
||||||
Enable_Send_Button(false);
|
Enable_Send_Button(!m_nozzle_diameter_ack_msg.empty() && m_nozzle_diameter_ack_msg == m_nozzle_diameter_mismatch_msg);
|
||||||
} else if (status == PrintStatusNozzleTypeMismatch) {
|
} else if (status == PrintStatusNozzleTypeMismatch) {
|
||||||
Enable_Refresh_Button(true);
|
Enable_Refresh_Button(true);
|
||||||
Enable_Send_Button(false);
|
Enable_Send_Button(false);
|
||||||
@@ -2470,7 +2473,19 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
|||||||
|
|
||||||
/*enter perpare mode*/
|
/*enter perpare mode*/
|
||||||
prepare_mode(false);
|
prepare_mode(false);
|
||||||
m_pre_print_checker.add(status, msg, tips, wiki_url);
|
if (status == PrintDialogStatus::PrintStatusNozzleDiameterMismatch) {
|
||||||
|
// Short label on purpose: the 420px message panel caps its width and a wxCheckBox label
|
||||||
|
// does not wrap; the full explanation is in the warning text above it.
|
||||||
|
m_pre_print_checker.add_with_checkbox(status, msg,
|
||||||
|
_L("I have checked the installed nozzle and want to print anyway."),
|
||||||
|
!m_nozzle_diameter_ack_msg.empty() && m_nozzle_diameter_ack_msg == m_nozzle_diameter_mismatch_msg,
|
||||||
|
[this](bool checked) {
|
||||||
|
m_nozzle_diameter_ack_msg = checked ? m_nozzle_diameter_mismatch_msg : wxString();
|
||||||
|
Enable_Send_Button(checked);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
m_pre_print_checker.add(status, msg, tips, wiki_url);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4565,8 +4580,14 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj
|
|||||||
}
|
}
|
||||||
|
|
||||||
msg_params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, please go to 'Device -> Printer parts' to change your nozzle setting."));
|
msg_params.emplace_back(_L("Tips: If you changed your nozzle of your printer lately, please go to 'Device -> Printer parts' to change your nozzle setting."));
|
||||||
|
|
||||||
|
// Orca: non-blocking. A diameter that differs from the one the printer
|
||||||
|
// remembers is legitimate with a non-standard nozzle, so the print is held back
|
||||||
|
// only by the acknowledgement checkbox shown in the message board, not by a
|
||||||
|
// disabled Send outright. Keep checking the remaining extruders.
|
||||||
|
m_nozzle_diameter_mismatch_msg = msg_params.front();
|
||||||
show_status(PrintDialogStatus::PrintStatusNozzleDiameterMismatch, msg_params);
|
show_status(PrintDialogStatus::PrintStatusNozzleDiameterMismatch, msg_params);
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4608,6 +4629,9 @@ static wxString _get_ext_loc_str(const std::unordered_set<int>& extruders, int t
|
|||||||
void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
||||||
{
|
{
|
||||||
m_pre_print_checker.clear();
|
m_pre_print_checker.clear();
|
||||||
|
// Orca: re-raised by CheckErrorExtruderNozzleWithSlicing() below if the mismatch is still there,
|
||||||
|
// so an early return from this pass cannot leave a stale warning behind.
|
||||||
|
m_nozzle_diameter_mismatch_msg.clear();
|
||||||
|
|
||||||
/*agent check and printer valid check*/
|
/*agent check and printer valid check*/
|
||||||
NetworkAgent* agent = Slic3r::GUI::wxGetApp().getAgent();
|
NetworkAgent* agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||||
|
|||||||
@@ -550,6 +550,13 @@ public:
|
|||||||
// Compare the slicing file's nozzle requirements (validity, flow, diameter) against the
|
// Compare the slicing file's nozzle requirements (validity, flow, diameter) against the
|
||||||
// printer; the rack extruder is checked against its whole inventory (mounted + rack).
|
// printer; the rack extruder is checked against its whole inventory (mounted + rack).
|
||||||
bool CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_);//return true if no errors
|
bool CheckErrorExtruderNozzleWithSlicing(MachineObject* obj_);//return true if no errors
|
||||||
|
// Orca: a nozzle diameter differing from the one the printer remembers is a non-blocking
|
||||||
|
// warning shown in the message board with an acknowledgement checkbox; Send stays disabled
|
||||||
|
// until the user ticks it. m_nozzle_diameter_mismatch_msg is the current mismatch text (empty
|
||||||
|
// when there is none), m_nozzle_diameter_ack_msg the text the user acknowledged; Send is
|
||||||
|
// allowed only while the two match, so a changed or cleared mismatch re-requires acknowledgement.
|
||||||
|
wxString m_nozzle_diameter_mismatch_msg;
|
||||||
|
wxString m_nozzle_diameter_ack_msg;
|
||||||
// Warn (without blocking) when a mapped filament would be printed from both extruders on a
|
// Warn (without blocking) when a mapped filament would be printed from both extruders on a
|
||||||
// dual-extruder printer, so per-nozzle manual K-value can't follow it across the whole print.
|
// dual-extruder printer, so per-nozzle manual K-value can't follow it across the whole print.
|
||||||
bool CheckWarningFilamentCrossExtruder(MachineObject* obj_);
|
bool CheckWarningFilamentCrossExtruder(MachineObject* obj_);
|
||||||
|
|||||||
+25
-2
@@ -34,6 +34,7 @@
|
|||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||||
|
#include "slic3r/plugin/PluginConfig.hpp"
|
||||||
#include "Plater.hpp"
|
#include "Plater.hpp"
|
||||||
#include "MainFrame.hpp"
|
#include "MainFrame.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
@@ -1795,9 +1796,19 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
|||||||
|
|
||||||
// Keep this preset's "plugins" manifest in sync when a plugin picker changes, so full_config() and
|
// Keep this preset's "plugins" manifest in sync when a plugin picker changes, so full_config() and
|
||||||
// save_to_json() always find resolved "name;uuid;capability" references and rebuild it nowhere else.
|
// save_to_json() always find resolved "name;uuid;capability" references and rebuild it nowhere else.
|
||||||
|
// Also drop any plugin_config_overrides entries for a capability the change just stopped
|
||||||
|
// referencing (e.g. a plugin removed from slicing_pipeline_plugin), so a saved preset never
|
||||||
|
// carries configuration for a capability it no longer names. The Configure button is a separate
|
||||||
|
// field holding its own cached copy of that value, so it needs to be told explicitly, or it
|
||||||
|
// keeps showing the stale count until something else happens to refresh it.
|
||||||
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
||||||
opt_def && opt_def->is_plugin_backed())
|
opt_def && opt_def->is_plugin_backed()) {
|
||||||
m_config->update_plugin_manifest();
|
m_config->update_plugin_manifest();
|
||||||
|
if (prune_stale_plugin_overrides(*m_config)) {
|
||||||
|
if (Field* overrides_field = get_field(PLUGIN_OVERRIDES_OPTION_KEY))
|
||||||
|
overrides_field->set_value(boost::any(m_config->opt_string(PLUGIN_OVERRIDES_OPTION_KEY)), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
|
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
|
||||||
if (auto printer_tab = dynamic_cast<TabPrinter*>(this))
|
if (auto printer_tab = dynamic_cast<TabPrinter*>(this))
|
||||||
@@ -3288,7 +3299,19 @@ static std::vector<std::string> intersect(std::vector<std::string> const& l, std
|
|||||||
static std::vector<std::string> concat(std::vector<std::string> const& l, std::vector<std::string> const& r)
|
static std::vector<std::string> concat(std::vector<std::string> const& l, std::vector<std::string> const& r)
|
||||||
{
|
{
|
||||||
std::vector<std::string> t;
|
std::vector<std::string> t;
|
||||||
std::set_union(l.begin(), l.end(), r.begin(), r.end(), std::back_inserter(t));
|
bool l_is_sorted = std::is_sorted(l.begin(), l.end());
|
||||||
|
bool r_is_sorted = std::is_sorted(r.begin(), r.end());
|
||||||
|
|
||||||
|
if (l_is_sorted && r_is_sorted) {
|
||||||
|
std::set_union(l.begin(), l.end(), r.begin(), r.end(), std::back_inserter(t));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> l_sorted = l;
|
||||||
|
std::vector<std::string> r_sorted = r;
|
||||||
|
std::sort(l_sorted.begin(), l_sorted.end());
|
||||||
|
std::sort(r_sorted.begin(), r_sorted.end());
|
||||||
|
std::set_union(l_sorted.begin(), l_sorted.end(), r_sorted.begin(), r_sorted.end(), std::back_inserter(t));
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ TroubleshootDialog::TroubleshootDialog()
|
|||||||
Fit();
|
Fit();
|
||||||
});
|
});
|
||||||
|
|
||||||
auto link_wiki = new HyperLink(this, _L("Wiki Guide"));
|
auto link_wiki = new HyperLink(this, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/troubleshoot_center");
|
||||||
|
|
||||||
// RIGHT SIZER //////////////////////
|
// RIGHT SIZER //////////////////////
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,15 @@ public:
|
|||||||
g_webviews.erase(iter);
|
g_webviews.erase(iter);
|
||||||
}
|
}
|
||||||
wxWebView *m_webView;
|
wxWebView *m_webView;
|
||||||
|
// Guards against registering the "wx" handler twice (a duplicate throws on WKWebView).
|
||||||
|
bool m_script_handler_added = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static WebViewRef *webview_ref(wxWebView *webView)
|
||||||
|
{
|
||||||
|
return webView ? static_cast<WebViewRef *>(webView->GetRefData()) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||||
{
|
{
|
||||||
#if wxUSE_WEBVIEW_EDGE
|
#if wxUSE_WEBVIEW_EDGE
|
||||||
@@ -304,10 +311,17 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
|||||||
Slic3r::GUI::WKWebView_setTransparentBackground(wkWebView);
|
Slic3r::GUI::WKWebView_setTransparentBackground(wkWebView);
|
||||||
#endif
|
#endif
|
||||||
auto addScriptMessageHandler = [] (wxWebView *webView) {
|
auto addScriptMessageHandler = [] (wxWebView *webView) {
|
||||||
|
// Skip if SendAPIKey() already registered "wx"; a duplicate add throws an
|
||||||
|
// uncatchable NSException on WKWebView, killing the app at startup.
|
||||||
|
WebViewRef *ref = webview_ref(webView);
|
||||||
|
if (ref && ref->m_script_handler_added)
|
||||||
|
return;
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": begin to add script message handler for wx.";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": begin to add script message handler for wx.";
|
||||||
Slic3r::GUI::wxGetApp().set_adding_script_handler(true);
|
Slic3r::GUI::wxGetApp().set_adding_script_handler(true);
|
||||||
if (!webView->AddScriptMessageHandler("wx"))
|
if (!webView->AddScriptMessageHandler("wx"))
|
||||||
wxLogError("Could not add script message handler");
|
wxLogError("Could not add script message handler");
|
||||||
|
else if (ref)
|
||||||
|
ref->m_script_handler_added = true;
|
||||||
Slic3r::GUI::wxGetApp().set_adding_script_handler(false);
|
Slic3r::GUI::wxGetApp().set_adding_script_handler(false);
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished add script message handler for wx.";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished add script message handler for wx.";
|
||||||
};
|
};
|
||||||
@@ -336,6 +350,12 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
|||||||
g_webviews.push_back(webView);
|
g_webviews.push_back(webView);
|
||||||
return webView;
|
return webView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebView::MarkScriptMessageHandlerAdded(wxWebView * webView)
|
||||||
|
{
|
||||||
|
if (WebViewRef *ref = webview_ref(webView))
|
||||||
|
ref->m_script_handler_added = true;
|
||||||
|
}
|
||||||
#if wxUSE_WEBVIEW_EDGE
|
#if wxUSE_WEBVIEW_EDGE
|
||||||
bool WebView::CheckWebViewRuntime()
|
bool WebView::CheckWebViewRuntime()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ public:
|
|||||||
|
|
||||||
static bool RunScript(wxWebView * webView, wxString const & msg);
|
static bool RunScript(wxWebView * webView, wxString const & msg);
|
||||||
|
|
||||||
|
// Marks "wx" as registered so CreateWebView's deferred add skips the duplicate.
|
||||||
|
static void MarkScriptMessageHandlerAdded(wxWebView * webView);
|
||||||
|
|
||||||
static void RecreateAll();
|
static void RecreateAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ std::string WebViewHostDialog::document_start_injector(const std::string& markup
|
|||||||
const std::string literal = nlohmann::json(markup).dump();
|
const std::string literal = nlohmann::json(markup).dump();
|
||||||
std::string s;
|
std::string s;
|
||||||
s += "(function(){";
|
s += "(function(){";
|
||||||
|
// wxWebView's AddUserScript runs in child frames too (including cross-origin
|
||||||
|
// frames on WebView2). Host theme state belongs only to the top-level page.
|
||||||
|
s += "if(window.top!==window.self)return;";
|
||||||
s += prelude;
|
s += prelude;
|
||||||
s += "var css=" + literal + ";";
|
s += "var css=" + literal + ";";
|
||||||
s += "function inject(){";
|
s += "function inject(){";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <boost/nowide/fstream.hpp>
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Config.hpp>
|
||||||
#include <libslic3r/PresetBundle.hpp>
|
#include <libslic3r/PresetBundle.hpp>
|
||||||
#include <libslic3r/PrintConfig.hpp>
|
#include <libslic3r/PrintConfig.hpp>
|
||||||
#include <slic3r/GUI/GUI.hpp>
|
#include <slic3r/GUI/GUI.hpp>
|
||||||
@@ -164,6 +165,20 @@ bool CapabilityConfigDocument::erase(const PluginCapabilityId& id)
|
|||||||
return erased;
|
return erased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CapabilityConfigDocument::prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced)
|
||||||
|
{
|
||||||
|
bool changed = false;
|
||||||
|
for (auto it = m_entries.begin(); it != m_entries.end();) {
|
||||||
|
if (referenced.count({it->first.type, it->first.name}) != 0) {
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
it = m_entries.erase(it);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
bool CapabilityConfigDocument::empty() const
|
bool CapabilityConfigDocument::empty() const
|
||||||
{
|
{
|
||||||
return m_entries.empty() && m_opaque_entries.empty();
|
return m_entries.empty() && m_opaque_entries.empty();
|
||||||
@@ -342,6 +357,50 @@ std::string serialize_plugin_overrides(const CapabilityConfigDocument& document)
|
|||||||
return document.empty() ? std::string() : document.serialize_entries().dump();
|
return document.empty() ? std::string() : document.serialize_entries().dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool prune_stale_plugin_overrides(DynamicConfig& config)
|
||||||
|
{
|
||||||
|
const auto* overrides_opt = dynamic_cast<const ConfigOptionString*>(config.option(PLUGIN_OVERRIDES_OPTION_KEY));
|
||||||
|
if (overrides_opt == nullptr || overrides_opt->value.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CapabilityConfigDocument overrides;
|
||||||
|
std::string error;
|
||||||
|
if (!parse_plugin_overrides(overrides_opt->value, overrides, error)) {
|
||||||
|
// Malformed text is not ours to fix up here: leave it untouched rather than risk
|
||||||
|
// discarding data the user might still be able to recover.
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "prune_stale_plugin_overrides: " << error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capability names currently referenced by a plugin-backed option's value(s) — e.g.
|
||||||
|
// slicing_pipeline_plugin's ConfigOptionStrings entries name SlicingPipeline capabilities
|
||||||
|
// directly, the same raw values save_plugin_collection() resolves into the "plugins" manifest.
|
||||||
|
std::set<std::pair<PluginCapabilityType, std::string>> referenced;
|
||||||
|
const ConfigDef* def = config.def();
|
||||||
|
for (const std::string& opt_key : config.keys()) {
|
||||||
|
const ConfigOptionDef* opt_def = def != nullptr ? def->get(opt_key) : nullptr;
|
||||||
|
if (opt_def == nullptr || !opt_def->is_plugin_backed())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const ConfigOption* opt = config.option(opt_key);
|
||||||
|
const PluginCapabilityType type = plugin_capability_type_from_string(opt_def->plugin_type);
|
||||||
|
if (const auto* string_opt = dynamic_cast<const ConfigOptionString*>(opt)) {
|
||||||
|
if (!string_opt->value.empty())
|
||||||
|
referenced.emplace(type, string_opt->value);
|
||||||
|
} else if (const auto* vector_opt = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
|
||||||
|
for (const std::string& value : vector_opt->vserialize())
|
||||||
|
if (!value.empty())
|
||||||
|
referenced.emplace(type, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overrides.prune_unreferenced(referenced))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
config.set_key_value(PLUGIN_OVERRIDES_OPTION_KEY, new ConfigOptionString(serialize_plugin_overrides(overrides)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
EffectiveCapabilityConfig PresetPluginConfigService::get_effective_config(const CapabilityConfigDocument& overrides,
|
EffectiveCapabilityConfig PresetPluginConfigService::get_effective_config(const CapabilityConfigDocument& overrides,
|
||||||
const PluginCapabilityId& id) const
|
const PluginCapabilityId& id) const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define PLUGIN_CONFIG_DIR "config.json"
|
#define PLUGIN_CONFIG_DIR "config.json"
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class Preset;
|
class Preset;
|
||||||
|
class DynamicConfig;
|
||||||
struct CapabilityConfigEntry
|
struct CapabilityConfigEntry
|
||||||
{
|
{
|
||||||
PluginCapabilityId id;
|
PluginCapabilityId id;
|
||||||
@@ -35,6 +38,9 @@ public:
|
|||||||
bool contains(const PluginCapabilityId& id) const;
|
bool contains(const PluginCapabilityId& id) const;
|
||||||
bool upsert(CapabilityConfigEntry entry);
|
bool upsert(CapabilityConfigEntry entry);
|
||||||
bool erase(const PluginCapabilityId& id);
|
bool erase(const PluginCapabilityId& id);
|
||||||
|
// Drops every entry whose (type, name) is not in `referenced`, e.g. capabilities a preset's
|
||||||
|
// plugin-backed options no longer name. Returns true if anything was removed.
|
||||||
|
bool prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced);
|
||||||
bool empty() const;
|
bool empty() const;
|
||||||
nlohmann::json serialize_entries() const;
|
nlohmann::json serialize_entries() const;
|
||||||
nlohmann::json root_json() const;
|
nlohmann::json root_json() const;
|
||||||
@@ -50,6 +56,14 @@ std::string plugin_overrides_of(const Preset& preset);
|
|||||||
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
|
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
|
||||||
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
|
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
|
||||||
|
|
||||||
|
// Drops plugin_config_overrides entries for capabilities no longer named by any plugin-backed
|
||||||
|
// option's current value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a
|
||||||
|
// different capability), and writes the result back if anything changed. Called wherever a
|
||||||
|
// plugin-backed option's value changes, so a saved preset never carries configuration for a
|
||||||
|
// capability it no longer references. Returns true if `config` was modified, so a caller holding a
|
||||||
|
// GUI field over PLUGIN_OVERRIDES_OPTION_KEY knows it must refresh that field's displayed value.
|
||||||
|
bool prune_stale_plugin_overrides(DynamicConfig& config);
|
||||||
|
|
||||||
struct EffectiveCapabilityConfig
|
struct EffectiveCapabilityConfig
|
||||||
{
|
{
|
||||||
PluginCapabilityId id;
|
PluginCapabilityId id;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ struct PluginDescriptor
|
|||||||
std::string description; // Plugin description
|
std::string description; // Plugin description
|
||||||
std::string author; // Plugin author from manifest, if available
|
std::string author; // Plugin author from manifest, if available
|
||||||
std::string version; // Selected plugin version
|
std::string version; // Selected plugin version
|
||||||
std::string latest_version; // Latest available cloud version fallback when changelog is unavailable.
|
std::string latest_version; // Authoritative latest available cloud version.
|
||||||
std::string installed_version; // Locally installed package version. Preserved across cloud merges, which overwrite `version` with the latest cloud version. Empty when not installed.
|
std::string installed_version; // Locally installed package version. Preserved across cloud merges, which overwrite `version` with the latest cloud version. Empty when not installed.
|
||||||
std::vector<std::string> display_types; // Display-only "compatibility" labels (cloud: raw service labels; local: from real capabilities). Never used for dispatch.
|
std::vector<std::string> display_types; // Display-only "compatibility" labels (cloud: raw service labels; local: from real capabilities). Never used for dispatch.
|
||||||
std::string plugin_root; // Installed plugin directory, even when entry_path is invalid or ambiguous.
|
std::string plugin_root; // Installed plugin directory, even when entry_path is invalid or ambiguous.
|
||||||
@@ -117,10 +117,6 @@ struct PluginDescriptor
|
|||||||
bool is_unauthorized() const { return get_update_status() == PluginUpdateStatus::Unauthorized; }
|
bool is_unauthorized() const { return get_update_status() == PluginUpdateStatus::Unauthorized; }
|
||||||
std::string latest_available_version() const
|
std::string latest_available_version() const
|
||||||
{
|
{
|
||||||
for (const PluginChangelog& entry : changelog) {
|
|
||||||
if (!entry.version.empty())
|
|
||||||
return entry.version;
|
|
||||||
}
|
|
||||||
if (!latest_version.empty())
|
if (!latest_version.empty())
|
||||||
return latest_version;
|
return latest_version;
|
||||||
return version;
|
return version;
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Compare Analyzer — G-code Slicing Comparison Tools
|
||||||
|
|
||||||
|
Tools for deep comparison and analysis of `.3mf` slicing project files, designed for
|
||||||
|
verifying multi-nozzle (H2C carousel) and multi-extruder slicing correctness.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### `compare_slices.py` — Slice Comparison Analyzer
|
||||||
|
|
||||||
|
Deep comparison of two `.3mf` files (OrcaSlicer, BambuStudio, or any compatible slicer).
|
||||||
|
Generates a comprehensive Markdown report covering:
|
||||||
|
|
||||||
|
- **Filament usage** — per-filament weight/length with color mapping
|
||||||
|
- **Nozzle/extruder mapping** — Vortek carousel slot assignments
|
||||||
|
- **Tool change sequences** — T-code ordering and count
|
||||||
|
- **Prime tower analysis** — tower entries, G-code line count
|
||||||
|
- **Temperature timeline** — pre-heat lead times, target temperatures per tool change
|
||||||
|
- **Retract parameters** — M620.11 analysis during nozzle switches
|
||||||
|
- **Filament change G-code blocks** — line-by-line diff of change_filament_gcode
|
||||||
|
- **Control command diff** — timeline of M/G-code differences
|
||||||
|
- **Critical discrepancy detection** — automatic flagging of weight/time anomalies
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compare two slice files
|
||||||
|
python3 compare_slices.py file1.3mf file2.3mf
|
||||||
|
|
||||||
|
# With custom labels
|
||||||
|
python3 compare_slices.py file1.3mf file2.3mf --labels "Upstream" "Fixed"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
Markdown report saved to `mp_reports/compare_report_YYYYMMDD_HHMMSS.md`
|
||||||
|
|
||||||
|
#### Example: Detecting H2C purge regression
|
||||||
|
```
|
||||||
|
⚠️ CRITICAL DISCREPANCY: Huge difference in part weight:
|
||||||
|
OrcaSlicer 60.90 g vs BambuStudio 17.47 g (difference 43.43 g or 71.3%).
|
||||||
|
The reason is incorrect nozzle mapping, causing huge AMS flushing.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `show_temp_plot.py` — Temperature Timeline Plotter
|
||||||
|
|
||||||
|
Generates interactive HTML temperature plots for analyzing thermal profiles during
|
||||||
|
multi-nozzle prints. Visualizes heater temperature commands (M104/M109) per tool change,
|
||||||
|
showing pre-heat timing and temperature convergence.
|
||||||
|
|
||||||
|
#### Architecture
|
||||||
|
- H2C dual-extruder layout with Vortek carousel nozzles
|
||||||
|
- Physical heaters mapped dynamically:
|
||||||
|
- Heater 0: Extruder 2 (right nozzle slot, T0/T2/T3/T4)
|
||||||
|
- Heater 1: Extruder 1 (left nozzle slot, T1)
|
||||||
|
- Active heater mapping derived from G-code temperature signals
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Single file analysis
|
||||||
|
python3 show_temp_plot.py file.3mf
|
||||||
|
|
||||||
|
# Side-by-side comparison of two files
|
||||||
|
python3 show_temp_plot.py file1.3mf file2.3mf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Output
|
||||||
|
Interactive HTML report saved to Desktop as `temp_plot_v3.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **Python 3.8+**
|
||||||
|
- **No external dependencies** — uses only Python standard library
|
||||||
|
(`json`, `zipfile`, `xml.etree.ElementTree`, `difflib`, `webbrowser`)
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
1. **Regression testing** — compare slices before/after code changes to verify
|
||||||
|
no unintended differences in purge volumes, tool ordering, or temperature timing
|
||||||
|
2. **BBS compatibility verification** — compare OrcaSlicer output against BambuStudio
|
||||||
|
reference slices to ensure behavioral parity
|
||||||
|
3. **H2C carousel validation** — verify per-slot nozzle tracking produces correct
|
||||||
|
purge volumes (not collapsed per-extruder)
|
||||||
|
4. **Temperature protocol analysis** — verify pre-heat lead times and cooling
|
||||||
|
temperatures during nozzle changes match expected profiles
|
||||||
Executable
+1282
File diff suppressed because it is too large
Load Diff
Executable
+1545
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,18 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
#include "libslic3r/Fill/Fill.hpp"
|
#include "libslic3r/Fill/Fill.hpp"
|
||||||
#include "libslic3r/Flow.hpp"
|
#include "libslic3r/Flow.hpp"
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
|
#include "libslic3r/Layer.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/SVG.hpp"
|
#include "libslic3r/SVG.hpp"
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
@@ -476,3 +482,219 @@ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacin
|
|||||||
|
|
||||||
return uncovered.empty(); // solid surface is fully filled
|
return uncovered.empty(); // solid surface is fully filled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Length-weighted dominant direction of the layer's role_wanted extrusions, whole degrees
|
||||||
|
// [0, 180), or -1 if it has none. Needs a line pattern such as monotonic or rectilinear.
|
||||||
|
template<typename RolePred> static int dominant_fill_angle(const Layer &layer, RolePred role_wanted)
|
||||||
|
{
|
||||||
|
std::map<int, double> weight_per_degree;
|
||||||
|
|
||||||
|
auto account = [&weight_per_degree, &role_wanted](const ExtrusionPath &path) {
|
||||||
|
if (!role_wanted(path.role()))
|
||||||
|
return;
|
||||||
|
const Points3 &pts = path.polyline.points;
|
||||||
|
for (size_t i = 1; i < pts.size(); ++i) {
|
||||||
|
const double dx = double(pts[i].x() - pts[i - 1].x());
|
||||||
|
const double dy = double(pts[i].y() - pts[i - 1].y());
|
||||||
|
const double len = std::hypot(dx, dy);
|
||||||
|
if (len <= 0.)
|
||||||
|
continue;
|
||||||
|
int deg = int(std::lround(Geometry::rad2deg(std::atan2(dy, dx)))) % 180;
|
||||||
|
if (deg < 0)
|
||||||
|
deg += 180;
|
||||||
|
weight_per_degree[deg] += len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const LayerRegion *region : layer.regions())
|
||||||
|
for (const ExtrusionEntity *entity : region->fills.flatten().entities) {
|
||||||
|
if (auto *path = dynamic_cast<const ExtrusionPath *>(entity))
|
||||||
|
account(*path);
|
||||||
|
else if (auto *multi = dynamic_cast<const ExtrusionMultiPath *>(entity))
|
||||||
|
for (const ExtrusionPath &p : multi->paths)
|
||||||
|
account(p);
|
||||||
|
else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(entity))
|
||||||
|
for (const ExtrusionPath &p : loop->paths)
|
||||||
|
account(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weight_per_degree.empty())
|
||||||
|
return -1;
|
||||||
|
return std::max_element(weight_per_degree.begin(), weight_per_degree.end(),
|
||||||
|
[](const auto &a, const auto &b) { return a.second < b.second; })->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename RolePred> static std::vector<int> angles_per_layer(const Print &print, RolePred role_wanted)
|
||||||
|
{
|
||||||
|
std::vector<int> angles;
|
||||||
|
for (const Layer *layer : print.objects().front()->layers())
|
||||||
|
angles.push_back(dominant_fill_angle(*layer, role_wanted));
|
||||||
|
return angles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool solid_role(ExtrusionRole role) { return is_solid_infill(role) && role != erIroning; }
|
||||||
|
static bool sparse_role(ExtrusionRole role) { return role == erInternalInfill; }
|
||||||
|
static bool ironing_role(ExtrusionRole role) { return role == erIroning; }
|
||||||
|
|
||||||
|
TEST_CASE("Infill rotation template is unaffected by a raft", "[Fill][Regression]")
|
||||||
|
{
|
||||||
|
// More angles than raft layers, so a raft cannot alias back to the same angle.
|
||||||
|
const std::string template_string = GENERATE("+45", "0,25,50,75,100,125,150");
|
||||||
|
const int raft_layers = GENERATE(1, 3);
|
||||||
|
CAPTURE(template_string, raft_layers);
|
||||||
|
|
||||||
|
auto angles_for = [&template_string](int rafts) {
|
||||||
|
Print print;
|
||||||
|
// 100% density makes every layer solid, so the template shows on all 100, not just shells.
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"solid_infill_rotate_template", template_string},
|
||||||
|
{"sparse_infill_density", "100%"},
|
||||||
|
{"internal_solid_infill_pattern", "monotonic"},
|
||||||
|
{"layer_height", 0.2},
|
||||||
|
{"raft_layers", rafts}});
|
||||||
|
return angles_per_layer(print, solid_role);
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<int> without_raft = angles_for(0);
|
||||||
|
const std::vector<int> with_raft = angles_for(raft_layers);
|
||||||
|
|
||||||
|
REQUIRE(without_raft.size() == 100);
|
||||||
|
REQUIRE(with_raft.size() == without_raft.size());
|
||||||
|
REQUIRE(std::count(without_raft.begin(), without_raft.end(), -1) == 0);
|
||||||
|
CHECK(with_raft == without_raft);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Sparse infill rotation template turns the infill layer by layer", "[Fill]")
|
||||||
|
{
|
||||||
|
const std::vector<int> expected_cycle = {0, 25, 50, 75, 100, 125, 150};
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
// No shells, so every layer is sparse infill rather than solid.
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(10)}, print,
|
||||||
|
{{"sparse_infill_rotate_template", "0,25,50,75,100,125,150"},
|
||||||
|
{"sparse_infill_density", "40%"},
|
||||||
|
{"sparse_infill_pattern", "rectilinear"},
|
||||||
|
{"top_shell_layers", 0},
|
||||||
|
{"bottom_shell_layers", 0},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
|
||||||
|
const std::vector<int> angles = angles_per_layer(print, sparse_role);
|
||||||
|
REQUIRE(angles.size() == 50);
|
||||||
|
REQUIRE(std::count(angles.begin(), angles.end(), -1) == 0);
|
||||||
|
|
||||||
|
std::vector<int> expected;
|
||||||
|
for (size_t i = 0; i < angles.size(); ++i)
|
||||||
|
expected.push_back(expected_cycle[i % expected_cycle.size()]);
|
||||||
|
CHECK(angles == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Infill rotation template layer count modifier holds each angle for N layers", "[Fill]")
|
||||||
|
{
|
||||||
|
Print print;
|
||||||
|
// "+45#2" turns 45 degrees every 2 layers, so equal angles come in pairs.
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(10)}, print,
|
||||||
|
{{"solid_infill_rotate_template", "+45#2"},
|
||||||
|
{"sparse_infill_density", "100%"},
|
||||||
|
{"internal_solid_infill_pattern", "monotonic"},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
|
||||||
|
const std::vector<int> angles = angles_per_layer(print, solid_role);
|
||||||
|
REQUIRE(angles.size() == 50);
|
||||||
|
REQUIRE(std::count(angles.begin(), angles.end(), -1) == 0);
|
||||||
|
|
||||||
|
std::vector<int> run_lengths;
|
||||||
|
for (size_t i = 0; i < angles.size();) {
|
||||||
|
size_t j = i;
|
||||||
|
while (j < angles.size() && angles[j] == angles[i])
|
||||||
|
++j;
|
||||||
|
run_lengths.push_back(int(j - i));
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
// The first and last runs can be clipped by the start and end of the object.
|
||||||
|
REQUIRE(run_lengths.size() > 3);
|
||||||
|
const std::vector<int> interior(run_lengths.begin() + 1, run_lengths.end() - 1);
|
||||||
|
CHECK(std::count(interior.begin(), interior.end(), 2) == int(interior.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Z anti-aliasing keeps the infill rotation template's step", "[Fill]")
|
||||||
|
{
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(10)}, print,
|
||||||
|
{{"solid_infill_rotate_template", "+45"},
|
||||||
|
{"sparse_infill_density", "100%"},
|
||||||
|
{"internal_solid_infill_pattern", "monotonic"},
|
||||||
|
{"zaa_enabled", 1},
|
||||||
|
{"zaa_min_z", 0.05},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
|
||||||
|
// Z contouring varies the layer heights, so the layer count is not 10mm / 0.2mm here.
|
||||||
|
const std::vector<int> angles = angles_per_layer(print, solid_role);
|
||||||
|
REQUIRE(angles.size() > 10);
|
||||||
|
REQUIRE(std::count(angles.begin(), angles.end(), -1) == 0);
|
||||||
|
|
||||||
|
// Z contouring may change when the template advances, but each step must still be 45 degrees.
|
||||||
|
int steps = 0;
|
||||||
|
for (size_t i = 1; i < angles.size(); ++i) {
|
||||||
|
const int delta = ((angles[i] - angles[i - 1]) % 180 + 180) % 180;
|
||||||
|
CAPTURE(i, angles[i - 1], angles[i]);
|
||||||
|
// Split rather than "delta == 0 || delta == 45" so Catch2 can show the operands.
|
||||||
|
REQUIRE(delta % 45 == 0);
|
||||||
|
REQUIRE(delta <= 45);
|
||||||
|
steps += delta == 45;
|
||||||
|
}
|
||||||
|
CHECK(steps > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Ironing follows the solid infill rotation template", "[Fill]")
|
||||||
|
{
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(10)}, print,
|
||||||
|
{{"solid_infill_rotate_template", "+45"},
|
||||||
|
{"internal_solid_infill_pattern", "monotonic"},
|
||||||
|
{"top_surface_pattern", "monotonic"},
|
||||||
|
// Every solid surface, so the comparison covers every layer.
|
||||||
|
{"ironing_type", "solid"},
|
||||||
|
{"sparse_infill_density", "100%"},
|
||||||
|
{"ironing_angle", 0},
|
||||||
|
{"ironing_angle_fixed", 0},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
|
||||||
|
const std::vector<int> ironing = angles_per_layer(print, ironing_role);
|
||||||
|
const std::vector<int> solid = angles_per_layer(print, solid_role);
|
||||||
|
REQUIRE(ironing.size() == solid.size());
|
||||||
|
|
||||||
|
// With no fixed angle and no offset, ironing runs along the template's angle for that layer.
|
||||||
|
int compared = 0;
|
||||||
|
for (size_t i = 0; i < ironing.size(); ++i)
|
||||||
|
if (ironing[i] != -1 && solid[i] != -1) {
|
||||||
|
CAPTURE(i, ironing[i], solid[i]);
|
||||||
|
CHECK(ironing[i] == solid[i]);
|
||||||
|
++compared;
|
||||||
|
}
|
||||||
|
// Most of the object, not one lucky layer.
|
||||||
|
REQUIRE(compared > int(ironing.size()) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Solid infill direction offsets every layer when no template is set", "[Fill]")
|
||||||
|
{
|
||||||
|
auto angles_for = [](int direction) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(10)}, print,
|
||||||
|
{{"solid_infill_direction", direction},
|
||||||
|
{"sparse_infill_density", "100%"},
|
||||||
|
{"internal_solid_infill_pattern", "monotonic"},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return angles_per_layer(print, solid_role);
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<int> at_0 = angles_for(0);
|
||||||
|
const std::vector<int> at_30 = angles_for(30);
|
||||||
|
REQUIRE(at_0.size() == at_30.size());
|
||||||
|
REQUIRE(std::count(at_0.begin(), at_0.end(), -1) == 0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < at_0.size(); ++i) {
|
||||||
|
const int delta = ((at_30[i] - at_0[i]) % 180 + 180) % 180;
|
||||||
|
CAPTURE(i, at_0[i], at_30[i]);
|
||||||
|
CHECK(delta == 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -820,3 +820,30 @@ SCENARIO("Shipped dual-nozzle change_filament_gcode resolves during a real slice
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Custom G-code motion limits are restored before generated moves", "[GCodeWriter]")
|
||||||
|
{
|
||||||
|
const std::string gcode = Slic3r::Test::slice({ cube(20) }, {
|
||||||
|
{ "gcode_flavor", "marlin" },
|
||||||
|
{ "gcode_comments", "1" },
|
||||||
|
{ "machine_start_gcode", "" },
|
||||||
|
{ "layer_change_gcode", "M204 S5000\nm205 x5 y5\n" },
|
||||||
|
{ "layer_height", "0.2" },
|
||||||
|
{ "initial_layer_print_height", "0.2" },
|
||||||
|
{ "initial_layer_line_width", "0" },
|
||||||
|
{ "z_hop", "0" },
|
||||||
|
{ "default_acceleration", "6000" },
|
||||||
|
{ "initial_layer_acceleration", "6000" },
|
||||||
|
{ "outer_wall_acceleration", "6000" },
|
||||||
|
{ "inner_wall_acceleration", "0" },
|
||||||
|
{ "default_jerk", "8" },
|
||||||
|
{ "initial_layer_jerk", "8" },
|
||||||
|
{ "outer_wall_jerk", "8" },
|
||||||
|
{ "inner_wall_jerk", "0" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const size_t custom_gcode_pos = gcode.find("m205 x5 y5");
|
||||||
|
REQUIRE(custom_gcode_pos != std::string::npos);
|
||||||
|
REQUIRE(gcode.find("M204 S6000 ; adjust acceleration", custom_gcode_pos) != std::string::npos);
|
||||||
|
REQUIRE(gcode.find("M205 X8 Y8 ; adjust jerk", custom_gcode_pos) != std::string::npos);
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_preset_diff.cpp
|
test_preset_diff.cpp
|
||||||
test_elephant_foot_compensation.cpp
|
test_elephant_foot_compensation.cpp
|
||||||
test_geometry.cpp
|
test_geometry.cpp
|
||||||
|
test_multimaterial_segmentation.cpp
|
||||||
test_placeholder_parser.cpp
|
test_placeholder_parser.cpp
|
||||||
test_polygon.cpp
|
test_polygon.cpp
|
||||||
test_mutable_polygon.cpp
|
test_mutable_polygon.cpp
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "libslic3r/calib.hpp"
|
#include "libslic3r/calib.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
@@ -38,3 +42,69 @@ TEST_CASE("Zero calibration line width resolves to a positive default", "[Calib]
|
|||||||
REQUIRE(pattern.line_width() > 0.);
|
REQUIRE(pattern.line_width() > 0.);
|
||||||
REQUIRE(pattern.line_width_first_layer() > 0.);
|
REQUIRE(pattern.line_width_first_layer() > 0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct EndState { double final_e; double max_e; };
|
||||||
|
|
||||||
|
EndState simulate_absolute_e(const std::string &gcode)
|
||||||
|
{
|
||||||
|
double final_e = 0.;
|
||||||
|
double max_e = 0.;
|
||||||
|
|
||||||
|
std::istringstream lines(gcode);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(lines, line)) {
|
||||||
|
std::istringstream words(line);
|
||||||
|
std::string op;
|
||||||
|
if (!(words >> op))
|
||||||
|
continue;
|
||||||
|
if (op != "G1" && op != "G0" && op != "G92")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string word;
|
||||||
|
while (words >> word) {
|
||||||
|
if (word.size() >= 2 && word[0] == 'E') {
|
||||||
|
final_e = std::stod(word.substr(1));
|
||||||
|
max_e = std::max(max_e, final_e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {final_e, max_e};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("PA pattern resets the extruder after the final layer in absolute E mode", "[Calib][Regression]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.set_deserialize_strict({
|
||||||
|
{"use_relative_e_distances", "0"},
|
||||||
|
{"line_width", "0.45"},
|
||||||
|
{"initial_layer_line_width", "0.45"},
|
||||||
|
});
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
|
||||||
|
|
||||||
|
Calib_Params params;
|
||||||
|
params.mode = CalibMode::Calib_PA_Pattern;
|
||||||
|
params.start = 0.;
|
||||||
|
params.end = 0.08;
|
||||||
|
params.step = 0.002;
|
||||||
|
|
||||||
|
CalibPressureAdvancePattern pattern(params, config, /* is_bbl_machine */ false, *model.objects.front(), Vec3d(0, 0, 0));
|
||||||
|
const CustomGCode::Info info = pattern.generate_custom_gcodes(config, /* is_bbl_machine */ false, *model.objects.front(),
|
||||||
|
Vec3d(0, 0, 0));
|
||||||
|
|
||||||
|
std::string gcode;
|
||||||
|
for (const CustomGCode::Item &item : info.gcodes)
|
||||||
|
gcode += item.extra;
|
||||||
|
|
||||||
|
const EndState state = simulate_absolute_e(gcode);
|
||||||
|
|
||||||
|
REQUIRE(state.max_e > 1.);
|
||||||
|
REQUIRE_THAT(state.final_e, Catch::Matchers::WithinAbs(0., 1e-9));
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
// MultiMaterialSegmentation.hpp declares boost::polygon traits for ColoredLine, so its
|
||||||
|
// geometry/boost dependencies must be included first.
|
||||||
|
#include <boost/polygon/polygon.hpp>
|
||||||
|
#include "libslic3r/Line.hpp"
|
||||||
|
#include "libslic3r/Flow.hpp"
|
||||||
|
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||||
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("Multi-material segmentation resolves the outer-wall line width", "[MultiMaterialSegmentation][Regression]")
|
||||||
|
{
|
||||||
|
struct Case
|
||||||
|
{
|
||||||
|
std::string description;
|
||||||
|
double outer_value;
|
||||||
|
bool outer_percent;
|
||||||
|
double line_value;
|
||||||
|
bool line_percent;
|
||||||
|
std::vector<double> nozzle_diameters;
|
||||||
|
int outer_wall_filament_id;
|
||||||
|
double expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto c = GENERATE(values<Case>({
|
||||||
|
{"absolute outer-wall width is used as-is", 0.6, false, 0.42, false, {0.4}, 1, 0.6},
|
||||||
|
{"percent outer-wall width uses the nozzle", 120, true, 0.42, false, {0.5}, 1, 0.6},
|
||||||
|
{"zero outer-wall width uses the line width", 0, false, 0.5, false, {0.4}, 1, 0.5},
|
||||||
|
{"zero outer-wall width uses a percent line", 0, false, 100, true, {0.5}, 1, 0.5},
|
||||||
|
{"zero width falls back to auto", 0, false, 0, false, {0.4}, 1, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)},
|
||||||
|
{"the auto fallback scales with the nozzle", 0, false, 0, false, {0.6}, 1, Flow::auto_extrusion_width(frExternalPerimeter, 0.6)},
|
||||||
|
{"a percent width uses the outer wall's nozzle", 120, true, 0.42, false, {0.4, 0.8}, 2, 0.96},
|
||||||
|
{"the auto width uses the outer wall's nozzle", 0, false, 0, false, {0.4, 0.8}, 2, Flow::auto_extrusion_width(frExternalPerimeter, 0.8)},
|
||||||
|
{"an absolute width ignores the nozzle", 0.6, false, 0.42, false, {0.4, 0.8}, 2, 0.6},
|
||||||
|
{"a zero percent width uses the line width", 0, true, 0.5, false, {0.4}, 1, 0.5},
|
||||||
|
{"an unset filament id uses the first nozzle", 0, false, 0, false, {0.4, 0.8}, 0, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)},
|
||||||
|
{"an out-of-range filament id uses nozzle 1", 0, false, 0, false, {0.4, 0.8}, 5, Flow::auto_extrusion_width(frExternalPerimeter, 0.4)},
|
||||||
|
}));
|
||||||
|
|
||||||
|
DYNAMIC_SECTION(c.description)
|
||||||
|
{
|
||||||
|
PrintConfig print_config;
|
||||||
|
print_config.nozzle_diameter.values = c.nozzle_diameters;
|
||||||
|
|
||||||
|
PrintObjectConfig object_config;
|
||||||
|
object_config.line_width = ConfigOptionFloatOrPercent(c.line_value, c.line_percent);
|
||||||
|
|
||||||
|
PrintRegionConfig region_config;
|
||||||
|
region_config.outer_wall_line_width = ConfigOptionFloatOrPercent(c.outer_value, c.outer_percent);
|
||||||
|
region_config.outer_wall_filament_id.value = c.outer_wall_filament_id;
|
||||||
|
|
||||||
|
REQUIRE_THAT(resolve_outer_wall_line_width(region_config, object_config, print_config),
|
||||||
|
Catch::Matchers::WithinAbs(c.expected, 1e-9));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,29 @@ print('ok')
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("plugin latest version uses the authoritative catalog field", "[PluginDescriptor]")
|
||||||
|
{
|
||||||
|
PluginDescriptor descriptor;
|
||||||
|
descriptor.version = "1.3.0";
|
||||||
|
descriptor.latest_version = "1.3.0";
|
||||||
|
PluginChangelog changelog;
|
||||||
|
changelog.version = "1.2.0";
|
||||||
|
descriptor.changelog.push_back(changelog);
|
||||||
|
|
||||||
|
CHECK(descriptor.latest_available_version() == "1.3.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("plugin latest version falls back to the descriptor version", "[PluginDescriptor]")
|
||||||
|
{
|
||||||
|
PluginDescriptor descriptor;
|
||||||
|
descriptor.version = "1.1.0";
|
||||||
|
PluginChangelog changelog;
|
||||||
|
changelog.version = "1.0.0";
|
||||||
|
descriptor.changelog.push_back(changelog);
|
||||||
|
|
||||||
|
CHECK(descriptor.latest_available_version() == "1.1.0");
|
||||||
|
}
|
||||||
|
|
||||||
// Regression: update_cloud_metadata() replaces a matched entry's descriptor wholesale with the
|
// Regression: update_cloud_metadata() replaces a matched entry's descriptor wholesale with the
|
||||||
// cloud catalog record (`entry = cloud_entry`). Configuration used to ride on the descriptor, so
|
// cloud catalog record (`entry = cloud_entry`). Configuration used to ride on the descriptor, so
|
||||||
// that overwrite silently wiped it and plugins fell back to their built-in defaults (found via
|
// that overwrite silently wiped it and plugins fell back to their built-in defaults (found via
|
||||||
|
|||||||
Reference in New Issue
Block a user