mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
Merge branch 'main' into main
This commit is contained in:
@@ -17,7 +17,7 @@ cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
|
||||
## Testing
|
||||
|
||||
Catch2 framework. Tests in `tests/` directory.
|
||||
Catch2 framework. Tests in `tests/`; see [tests/AGENTS.md](tests/AGENTS.md) for where a new test belongs and the conventions to follow.
|
||||
|
||||
```bash
|
||||
cd build && ctest --output-on-failure # all tests
|
||||
|
||||
+1
-3
@@ -835,7 +835,7 @@ endif ()
|
||||
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/i18n")
|
||||
set(BBL_L18N_DIR "${CMAKE_CURRENT_SOURCE_DIR}/localization/i18n")
|
||||
add_custom_target(gettext_make_pot
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_CTX:1,2c --keyword=_CTX_utf8:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
-f "${BBL_L18N_DIR}/list.txt"
|
||||
-o "${BBL_L18N_DIR}/OrcaSlicer.pot"
|
||||
COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${BBL_L18N_DIR}
|
||||
@@ -853,7 +853,6 @@ foreach(po_file ${BBL_L10N_PO_FILES})
|
||||
add_custom_command(
|
||||
TARGET gettext_merge_po_with_pot PRE_BUILD
|
||||
COMMAND msgmerge -N -o ${po_file} ${po_file} "${BBL_L18N_DIR}/OrcaSlicer.pot"
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(gettext_po_to_mo
|
||||
@@ -869,7 +868,6 @@ foreach(po_file ${BBL_L10N_PO_FILES})
|
||||
TARGET gettext_po_to_mo PRE_BUILD
|
||||
COMMAND msgfmt ARGS --check-format -o ${mo_file} ${po_file}
|
||||
#COMMAND msgfmt ARGS --check-compatibility -o ${mo_file} ${po_file}
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
|
||||
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
|
||||
<h3>
|
||||
|
||||
# Official links and community
|
||||
|
||||
@@ -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)**
|
||||
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
|
||||
|
||||
## 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.*
|
||||
<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`
|
||||
</details>
|
||||
|
||||
Windows Package Manager
|
||||
### Windows Package Manager
|
||||
|
||||
```shell
|
||||
winget install --id=SoftFever.OrcaSlicer -e
|
||||
@@ -117,7 +127,7 @@ winget install --id=SoftFever.OrcaSlicer -e
|
||||
|
||||
## Mac
|
||||
|
||||
1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU.
|
||||
1. Download the universal DMG, which runs on both Apple Silicon and Intel Macs.
|
||||
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:*
|
||||
|
||||
@@ -142,6 +152,14 @@ winget install --id=SoftFever.OrcaSlicer -e
|
||||

|
||||
</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
|
||||
|
||||
### Flathub (Recommended)
|
||||
@@ -190,8 +208,8 @@ resolution: 0.1
|
||||
|
||||
# Supports
|
||||
|
||||
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
|
||||
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
|
||||
**OrcaSlicer** is an open-source project, and we're deeply grateful to all our sponsors and backers.
|
||||
Their generous support helps fund filaments and other essential 3D printing materials for the project.
|
||||
Thank you! :)
|
||||
|
||||
## Sponsors
|
||||
@@ -215,7 +233,7 @@ Thank you! :)
|
||||
|
||||
**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://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>
|
||||
|
||||
@@ -128,6 +128,10 @@ cmake_minimum_required(VERSION 3.13)
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
# Re-set after cmake_minimum_required above cleared it; use BoostConfig, not the removed FindBoost.
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
endif()
|
||||
|
||||
if(OpenVDB_FIND_QUIETLY)
|
||||
set (_quiet "QUIET")
|
||||
|
||||
+389
-109
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-17 15:13-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,16 +18,136 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
|
||||
msgid "right"
|
||||
msgid "Main Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left"
|
||||
msgid "Main extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "main extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left extruder"
|
||||
msgid "Main Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "main nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "main hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "left hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "right hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "main"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary"
|
||||
msgstr ""
|
||||
|
||||
msgid "left"
|
||||
msgstr ""
|
||||
|
||||
msgid "right"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
msgid "extruder"
|
||||
@@ -225,6 +345,9 @@ msgstr ""
|
||||
msgid "Please wait"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading"
|
||||
msgstr ""
|
||||
|
||||
msgid "Running..."
|
||||
msgstr ""
|
||||
|
||||
@@ -237,6 +360,12 @@ msgstr ""
|
||||
msgid "Abnormal Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jump to the upgrade page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
@@ -246,12 +375,6 @@ msgstr ""
|
||||
msgid "Hotend status abnormal, unavailable at present. Please upgrade the firmware and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jump to the upgrade page"
|
||||
msgstr ""
|
||||
|
||||
msgid "SN"
|
||||
msgstr ""
|
||||
|
||||
@@ -429,6 +552,7 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
@@ -754,6 +878,7 @@ msgstr ""
|
||||
msgid "Select all connectors"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Cut tool"
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
@@ -1609,12 +1734,14 @@ msgstr ""
|
||||
msgid "Select point"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart selection"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
@@ -1715,6 +1842,7 @@ msgstr ""
|
||||
msgid "Leaving Measure gizmo"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Assembly tool"
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
@@ -2225,6 +2353,9 @@ msgstr ""
|
||||
msgid "Import File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose files"
|
||||
msgstr ""
|
||||
|
||||
@@ -2343,12 +2474,14 @@ msgstr ""
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Del"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the selected object"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Backspace"
|
||||
msgstr ""
|
||||
|
||||
@@ -2536,6 +2669,9 @@ msgstr ""
|
||||
msgid "Restore to Meter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble the selected objects into an object with multiple parts"
|
||||
msgstr ""
|
||||
|
||||
@@ -3280,6 +3416,7 @@ msgctxt "air_duct"
|
||||
msgid "Left(Aux)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Hotend Heat Breaker Fan"
|
||||
msgid "Hotend"
|
||||
msgstr ""
|
||||
|
||||
@@ -3926,10 +4063,8 @@ msgstr ""
|
||||
msgid "Please select from the following filaments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select filament that installed to the left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select filament that installed to the right nozzle"
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Select filament that installed to the %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left AMS"
|
||||
@@ -3944,37 +4079,23 @@ msgstr ""
|
||||
msgid "Right AMS"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament && Hotends"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Printing with the current nozzle may produce an extra %0.2f g of waste."
|
||||
msgstr ""
|
||||
|
||||
msgid "External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Tips: 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."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-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 "Cannot select: 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."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Note: the slot is empty or undefined. If you want to use this slot, you can install %s and change slot information on the 'Device' page."
|
||||
msgid "Cannot select: the slot is empty or undefined. If you want to use this slot, you can install %s and change slot information on the 'Device' page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Note: Only filament-loaded slots can be selected."
|
||||
msgid "Cannot select: No filament loaded in current slot."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable AMS"
|
||||
@@ -4013,9 +4134,11 @@ msgstr ""
|
||||
msgid "Print with filament in AMS"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -4659,6 +4782,9 @@ msgstr ""
|
||||
msgid "Calibration error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network unavailable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resume Printing"
|
||||
msgstr ""
|
||||
|
||||
@@ -4692,6 +4818,9 @@ msgstr ""
|
||||
msgid "No Reminder Next Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recheck"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ignore. Don't Remind Next Time"
|
||||
msgstr ""
|
||||
|
||||
@@ -4713,6 +4842,15 @@ msgstr ""
|
||||
msgid "Proceed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disable Purification for This Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Remind Me"
|
||||
msgstr ""
|
||||
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
@@ -4722,6 +4860,9 @@ msgstr ""
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
@@ -5076,12 +5217,6 @@ msgstr ""
|
||||
msgid "Why this grouping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please place filaments on the printer based on grouping result."
|
||||
msgstr ""
|
||||
|
||||
@@ -5234,6 +5369,7 @@ msgstr ""
|
||||
msgid "Quality / Speed"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Mesh action"
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
@@ -5276,12 +5412,6 @@ msgstr ""
|
||||
msgid "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "right nozzle"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "The position or size of some models exceeds the %s's printable range."
|
||||
msgstr ""
|
||||
@@ -5848,6 +5978,9 @@ msgstr ""
|
||||
msgid "Redo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut selection to clipboard"
|
||||
msgstr ""
|
||||
|
||||
@@ -5947,6 +6080,10 @@ msgstr ""
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Menu"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
@@ -7004,6 +7141,18 @@ msgid ""
|
||||
"Enable or install some to use."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is stringing-prone filament in the current print job. Enabling nozzle clumping detection now may degrade print quality. Are you sure you want to enable it?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable Nozzle Clumping Detection"
|
||||
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?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Enable Print Status Snapshot"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable detection of build plate position"
|
||||
msgstr ""
|
||||
|
||||
@@ -7016,6 +7165,21 @@ msgstr ""
|
||||
msgid "Identifies the type and position of the build plate on the heatbed. Pausing printing if a mismatch is detected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purifies the chamber air as the print finishes, based on the selected mode."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purifies the chamber air through internal circulation as each print finishes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically match the corresponding switch strategy for leak-prone filaments (disable blob detection) and regular filaments (enable blob detection)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Detect whether the nozzle is wrapped by filament or other foreign matter."
|
||||
msgstr ""
|
||||
|
||||
msgid "After disabling, nozzle wrapping cannot be detected, which may lead to print failure or nozzle damage."
|
||||
msgstr ""
|
||||
|
||||
msgid "AI Detections"
|
||||
msgstr ""
|
||||
|
||||
@@ -7058,7 +7222,7 @@ msgstr ""
|
||||
msgid "Store Sent Files on External Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save the printing files initiated from Bambu Studio, Bambu Handy and MakerWorld on External Storage"
|
||||
msgid "Save the printing files sent from the slicer and other apps on External Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Allow Prompt Sound"
|
||||
@@ -7070,6 +7234,36 @@ msgstr ""
|
||||
msgid "Check if the nozzle is clumping by filament or other foreign objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purify Air at Print End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal Circulation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Alignment Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pauses printing when build plate misalignment is detected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Foreign Object Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checks for any objects on the build plate at the start of a print to avoid collisions."
|
||||
msgstr ""
|
||||
|
||||
msgid "Printed Part Displacement Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Monitors the printed part during printing and alerts immediately if it shifts or collapses."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checks if the nozzle is clumping by filament or other foreign objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open Door Detection"
|
||||
msgstr ""
|
||||
|
||||
@@ -7079,6 +7273,12 @@ msgstr ""
|
||||
msgid "Pause printing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print Status Snapshot"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically capture and upload print photos, showing defects during printing and the final result for remote viewing."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle Type"
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
@@ -7228,10 +7428,10 @@ msgstr ""
|
||||
msgid "Configuration incompatible"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament switcher detected. All AMS filaments are now available for both extruders. The slicer will auto-assign for optimal printing. "
|
||||
msgid "Filament switcher detected. All AMS filaments are now available for both extruders. The slicer will auto-assign for optimal printing."
|
||||
msgstr ""
|
||||
|
||||
msgid "A filament switcher is detected but not calibrated and thus currently unavailable. Please calibrate it on the printer and synchronize before use. "
|
||||
msgid "A filament switcher is detected but not calibrated and thus currently unavailable. Please calibrate it on the printer and synchronize before use."
|
||||
msgstr ""
|
||||
|
||||
msgid "Tips"
|
||||
@@ -7260,6 +7460,9 @@ msgstr ""
|
||||
msgid "Click to edit preset"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project Filaments"
|
||||
msgstr ""
|
||||
|
||||
@@ -7626,6 +7829,15 @@ msgstr ""
|
||||
msgid "Please resolve the slicing errors and publish again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Network plug-in switched successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load network plug-in. Please restart the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "The network plug-in was not detected. Network related features are unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -8020,18 +8232,6 @@ msgstr ""
|
||||
msgid "Choose Download Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Latest)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network plug-in switched successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load network plug-in. Please restart the application."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"You've selected network plug-in version %s.\n"
|
||||
@@ -8445,6 +8645,15 @@ msgstr ""
|
||||
msgid "Displays current viewport FPS in the top-right corner."
|
||||
msgstr ""
|
||||
|
||||
msgid "G-code Preview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dim lower layers"
|
||||
msgstr ""
|
||||
|
||||
msgid "When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -8727,6 +8936,7 @@ msgstr ""
|
||||
msgid "The selected preset is null!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Layer range"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
@@ -8898,9 +9108,6 @@ msgstr ""
|
||||
msgid "Send print job"
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not satisfied with the grouping of filaments? Regroup and slice ->"
|
||||
msgstr ""
|
||||
|
||||
@@ -8916,6 +9123,9 @@ msgstr ""
|
||||
msgid "To ensure print quality, the drying temperature will be lowered during printing."
|
||||
msgstr ""
|
||||
|
||||
msgid "Select timelapse storage location"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto Bed Leveling"
|
||||
msgstr ""
|
||||
|
||||
@@ -8940,6 +9150,12 @@ msgid ""
|
||||
"*Automatic mode: Check for calibration before printing. Skip if unnecessary."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shared PA Profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzles and filaments of the same type share the same PA profile."
|
||||
msgstr ""
|
||||
|
||||
msgid "Send complete"
|
||||
msgstr ""
|
||||
|
||||
@@ -8983,6 +9199,10 @@ msgstr ""
|
||||
msgid "The current nozzle mapping may produce an extra %0.2f g of waste."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Recommended filament arrangement saves %s->"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."
|
||||
msgstr ""
|
||||
@@ -9000,6 +9220,9 @@ msgstr ""
|
||||
msgid "The current printer does not support timelapse in Traditional Mode when printing By-Object."
|
||||
msgstr ""
|
||||
|
||||
msgid "I have checked the installed nozzle and want to print anyway."
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors"
|
||||
msgstr ""
|
||||
|
||||
@@ -9030,6 +9253,37 @@ msgstr ""
|
||||
msgid "This process determines the dynamic flow values to improve overall print quality."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "%s space less than 20MB. Timelapse may not save properly. You can turn it off or"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean up files"
|
||||
msgstr ""
|
||||
|
||||
msgid "Low internal storage. This timelapse will overwrite the oldest video files."
|
||||
msgstr ""
|
||||
|
||||
msgid "Low external storage. This timelapse will overwrite the oldest video files."
|
||||
msgstr ""
|
||||
|
||||
msgid "Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."
|
||||
msgstr ""
|
||||
|
||||
msgid "Storage Space Not Enough"
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm & Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel Timelapse & Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preparing print job"
|
||||
msgstr ""
|
||||
|
||||
@@ -9076,7 +9330,7 @@ msgstr ""
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting."
|
||||
msgid "Tips: If you changed your nozzle of your printer lately, please go to 'Device -> Printer parts' to change your nozzle setting."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
@@ -9094,10 +9348,22 @@ msgstr ""
|
||||
msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). Please verify the nozzle or material settings and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your current firmware version cannot start this print job. Please update to the latest version and try again."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). It may cause nozzle wear, leading to material leakage and unstable flow. Please exercise caution when using it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some filaments may switch between extruders during printing. Manual K-value calibration cannot be applied throughout the entire print, which may affect print quality. Enabling Flow Dynamics Calibration is recommended."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is stringing-prone filament in this file. For best print quality, we recommend switching nozzle clumping detection to Auto mode."
|
||||
msgstr ""
|
||||
|
||||
msgid "If 'Dynamic Flow Calibration' is set to Auto/On, the system will use the manual calibration value or the default value and skip the flow calibration process. You can perform a manual flow calibration for TPU filament on the 'Calibration' page."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "[ %s ] requires printing in a high-temperature environment. Please close the door."
|
||||
msgstr ""
|
||||
@@ -9201,12 +9467,19 @@ msgstr ""
|
||||
msgid "This printer does not support printing all plates."
|
||||
msgstr ""
|
||||
|
||||
msgid "The current firmware supports a maximum of 16 materials. You can either reduce the number of materials to 16 or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support."
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid "The current firmware supports a maximum of %s materials. You can either reduce the number of materials to %s or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support."
|
||||
msgstr ""
|
||||
|
||||
msgid "The type of external filament is unknown or does not match with the filament type in the slicing file. Please make sure you have installed the correct filament in the external spool."
|
||||
msgstr ""
|
||||
|
||||
msgid "TPU 90A/TPU 85A are too soft. It is recommended to perform manual flow calibration on the 'Calibration' page. If 'Dynamic Flow Calibration' is set to auto/on, the system will use the previous calibration value and skip the flow calibration process."
|
||||
msgstr ""
|
||||
|
||||
msgid "The filament in the AMS may be insufficient for this print. Please refill or replace it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Current firmware does not support file transfer to internal storage."
|
||||
msgstr ""
|
||||
|
||||
@@ -10319,15 +10592,9 @@ msgstr ""
|
||||
msgid "Reset all filament mapping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Recommended filament)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Advanced Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -10460,12 +10727,6 @@ msgstr ""
|
||||
msgid "Re-calculate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multiplier"
|
||||
msgstr ""
|
||||
|
||||
@@ -10586,24 +10847,28 @@ msgstr ""
|
||||
msgid "Select objects by rectangle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in positive Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Down"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative X direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -10688,9 +10953,17 @@ msgstr ""
|
||||
msgid "Toggle printable for object/part"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between Prepare/Preview"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Space"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open actions speed dial"
|
||||
msgstr ""
|
||||
|
||||
@@ -10748,9 +11021,17 @@ msgstr ""
|
||||
msgid "Move slider 5x faster"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to start position"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to last position"
|
||||
msgstr ""
|
||||
|
||||
@@ -10764,7 +11045,10 @@ msgstr ""
|
||||
msgid "Network plug-in update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click OK to update the Network plug-in the next time Orca Slicer launches."
|
||||
msgid "Click OK to update the Network plug-in now. If a file is in use, the update will be applied the next time Orca Slicer launches."
|
||||
msgstr ""
|
||||
|
||||
msgid "A new Network plug-in is available. Do you want to install it?"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
@@ -11017,13 +11301,6 @@ msgstr ""
|
||||
msgid "Internal Bridge"
|
||||
msgstr ""
|
||||
|
||||
#, possible-boost-format
|
||||
msgid "Failed to calculate line width of %1%. Cannot get value of “%2%” "
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"
|
||||
msgstr ""
|
||||
|
||||
msgid "undefined error"
|
||||
msgstr ""
|
||||
|
||||
@@ -11978,9 +12255,6 @@ msgid ""
|
||||
"In addition, if Slow down for curled perimeters is disabled or Classic overhang mode is enabled, it will be the print speed of overhang walls that are supported by less than 13%, whether they are part of a bridge or an overhang."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed of internal bridges. If the value is expressed as a percentage, it will be calculated based on the bridge_speed. Default value is 150%."
|
||||
msgstr ""
|
||||
|
||||
@@ -12299,7 +12573,7 @@ msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane.Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top.The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection."
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection."
|
||||
msgstr ""
|
||||
|
||||
msgid "Top expansion wall margin"
|
||||
@@ -14887,6 +15161,9 @@ msgstr ""
|
||||
msgid "Traditional"
|
||||
msgstr ""
|
||||
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
msgid "Farthest point timelapse"
|
||||
msgstr ""
|
||||
|
||||
@@ -15790,6 +16067,12 @@ msgstr ""
|
||||
msgid "Whether this printer supports fast purge mode with optimized temperature and multiplier."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament change"
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder on the tower, excluding a hotend change."
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder for a hotend change on the tower."
|
||||
msgstr ""
|
||||
|
||||
@@ -17985,7 +18268,7 @@ msgstr ""
|
||||
msgid "Pack"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cleans and rebuilds system profiles cache on next launch"
|
||||
msgid "Cleans and rebuilds system profiles cache on next launch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean system profiles cache"
|
||||
@@ -17997,16 +18280,16 @@ msgstr ""
|
||||
msgid "Loaded profiles overview"
|
||||
msgstr ""
|
||||
|
||||
msgid "This section shows information for loaded profiles"
|
||||
msgid "This section shows information for loaded profiles."
|
||||
msgstr ""
|
||||
|
||||
msgid "Exports detailed overview of loaded profiles in json format"
|
||||
msgid "Exports detailed overview of loaded profiles in json format."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configurations folder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Opens configurations folder"
|
||||
msgid "Opens configurations folder."
|
||||
msgstr ""
|
||||
|
||||
msgid "Log level"
|
||||
@@ -18021,15 +18304,12 @@ msgstr ""
|
||||
msgid "Profiles"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select NO to close dialog and review project"
|
||||
msgid "Select NO to close dialog and review project."
|
||||
msgstr ""
|
||||
|
||||
msgid "No project file on current session. Only logs will be included to package"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select NO to close dialog and review project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please make sure any instances of OrcaSlicer are not running"
|
||||
msgstr ""
|
||||
|
||||
@@ -18676,7 +18956,7 @@ msgstr ""
|
||||
msgid "Error code not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "The printer is busy, Please check the device page for the file and try to start printing again."
|
||||
msgid "The printer is busy, please check the device page for the file and try to start printing again."
|
||||
msgstr ""
|
||||
|
||||
msgid "The file is lost, please check and try again."
|
||||
@@ -18807,6 +19087,9 @@ msgstr ""
|
||||
msgid "Skipping objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament"
|
||||
msgstr ""
|
||||
|
||||
msgid "Null Color"
|
||||
msgstr ""
|
||||
|
||||
@@ -18838,15 +19121,15 @@ msgstr ""
|
||||
msgid "Show details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Version to install:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download and Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip for Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "A new version of the Bambu Network Plug-in is available."
|
||||
msgstr ""
|
||||
|
||||
@@ -18857,16 +19140,16 @@ msgstr ""
|
||||
msgid "Update to version:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remind Later"
|
||||
msgid "(Latest)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgid "(installed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "The Bambu Network Plug-in has been installed successfully."
|
||||
@@ -18878,9 +19161,6 @@ msgstr ""
|
||||
msgid "Restart Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "NO RAMMING AT ALL"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-17 15:13-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||
"Language-Team: \n"
|
||||
@@ -14,16 +14,136 @@ msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n==1) ? 0 : 1;\n"
|
||||
"X-Generator: Poedit 3.9\n"
|
||||
|
||||
msgid "right"
|
||||
msgid "Main Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left"
|
||||
msgid "Main extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "main extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "left extruder"
|
||||
msgid "Main Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "main nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "main hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "left hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "right hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "main"
|
||||
msgstr ""
|
||||
|
||||
msgid "auxiliary"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auxiliary"
|
||||
msgstr ""
|
||||
|
||||
msgid "left"
|
||||
msgstr ""
|
||||
|
||||
msgid "right"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
msgid "extruder"
|
||||
@@ -221,6 +341,9 @@ msgstr ""
|
||||
msgid "Please wait"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading"
|
||||
msgstr ""
|
||||
|
||||
msgid "Running..."
|
||||
msgstr ""
|
||||
|
||||
@@ -233,6 +356,12 @@ msgstr ""
|
||||
msgid "Abnormal Hotend"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jump to the upgrade page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
@@ -242,12 +371,6 @@ msgstr ""
|
||||
msgid "Hotend status abnormal, unavailable at present. Please upgrade the firmware and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jump to the upgrade page"
|
||||
msgstr ""
|
||||
|
||||
msgid "SN"
|
||||
msgstr ""
|
||||
|
||||
@@ -425,6 +548,7 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
@@ -750,6 +874,7 @@ msgstr ""
|
||||
msgid "Select all connectors"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Cut tool"
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
@@ -1605,12 +1730,14 @@ msgstr ""
|
||||
msgid "Select point"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart selection"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
@@ -1711,6 +1838,7 @@ msgstr ""
|
||||
msgid "Leaving Measure gizmo"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Assembly tool"
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
@@ -2221,6 +2349,9 @@ msgstr ""
|
||||
msgid "Import File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose files"
|
||||
msgstr ""
|
||||
|
||||
@@ -2339,12 +2470,14 @@ msgstr ""
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Del"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the selected object"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Backspace"
|
||||
msgstr ""
|
||||
|
||||
@@ -2532,6 +2665,9 @@ msgstr ""
|
||||
msgid "Restore to Meter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble the selected objects into an object with multiple parts"
|
||||
msgstr ""
|
||||
|
||||
@@ -3276,6 +3412,7 @@ msgctxt "air_duct"
|
||||
msgid "Left(Aux)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Hotend Heat Breaker Fan"
|
||||
msgid "Hotend"
|
||||
msgstr ""
|
||||
|
||||
@@ -3922,10 +4059,8 @@ msgstr ""
|
||||
msgid "Please select from the following filaments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select filament that installed to the left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select filament that installed to the right nozzle"
|
||||
#, c-format, boost-format
|
||||
msgid "Select filament that installed to the %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left AMS"
|
||||
@@ -3940,37 +4075,23 @@ msgstr ""
|
||||
msgid "Right AMS"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament && Hotends"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "Printing with the current nozzle may produce an extra %0.2f g of waste."
|
||||
msgstr ""
|
||||
|
||||
msgid "External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."
|
||||
#, c-format, boost-format
|
||||
msgid "Tips: 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."
|
||||
msgstr ""
|
||||
|
||||
#, 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 "Cannot select: 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."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "Note: the slot is empty or undefined. If you want to use this slot, you can install %s and change slot information on the 'Device' page."
|
||||
msgid "Cannot select: the slot is empty or undefined. If you want to use this slot, you can install %s and change slot information on the 'Device' page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Note: Only filament-loaded slots can be selected."
|
||||
msgid "Cannot select: No filament loaded in current slot."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable AMS"
|
||||
@@ -4009,9 +4130,11 @@ msgstr ""
|
||||
msgid "Print with filament in AMS"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -4655,6 +4778,9 @@ msgstr ""
|
||||
msgid "Calibration error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network unavailable"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resume Printing"
|
||||
msgstr ""
|
||||
|
||||
@@ -4688,6 +4814,9 @@ msgstr ""
|
||||
msgid "No Reminder Next Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recheck"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ignore. Don't Remind Next Time"
|
||||
msgstr ""
|
||||
|
||||
@@ -4709,6 +4838,15 @@ msgstr ""
|
||||
msgid "Proceed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Abort"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disable Purification for This Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Remind Me"
|
||||
msgstr ""
|
||||
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
@@ -4718,6 +4856,9 @@ msgstr ""
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
@@ -5072,12 +5213,6 @@ msgstr ""
|
||||
msgid "Why this grouping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please place filaments on the printer based on grouping result."
|
||||
msgstr ""
|
||||
|
||||
@@ -5230,6 +5365,7 @@ msgstr ""
|
||||
msgid "Quality / Speed"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Mesh action"
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
@@ -5272,12 +5408,6 @@ msgstr ""
|
||||
msgid "Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "left nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "right nozzle"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "The position or size of some models exceeds the %s's printable range."
|
||||
msgstr ""
|
||||
@@ -5844,6 +5974,9 @@ msgstr ""
|
||||
msgid "Redo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut selection to clipboard"
|
||||
msgstr ""
|
||||
|
||||
@@ -5943,6 +6076,10 @@ msgstr ""
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Menu"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
@@ -7000,6 +7137,18 @@ msgid ""
|
||||
"Enable or install some to use."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is stringing-prone filament in the current print job. Enabling nozzle clumping detection now may degrade print quality. Are you sure you want to enable it?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable Nozzle Clumping Detection"
|
||||
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?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Enable Print Status Snapshot"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable detection of build plate position"
|
||||
msgstr ""
|
||||
|
||||
@@ -7012,6 +7161,21 @@ msgstr ""
|
||||
msgid "Identifies the type and position of the build plate on the heatbed. Pausing printing if a mismatch is detected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purifies the chamber air as the print finishes, based on the selected mode."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purifies the chamber air through internal circulation as each print finishes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically match the corresponding switch strategy for leak-prone filaments (disable blob detection) and regular filaments (enable blob detection)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Detect whether the nozzle is wrapped by filament or other foreign matter."
|
||||
msgstr ""
|
||||
|
||||
msgid "After disabling, nozzle wrapping cannot be detected, which may lead to print failure or nozzle damage."
|
||||
msgstr ""
|
||||
|
||||
msgid "AI Detections"
|
||||
msgstr ""
|
||||
|
||||
@@ -7054,7 +7218,7 @@ msgstr ""
|
||||
msgid "Store Sent Files on External Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save the printing files initiated from Bambu Studio, Bambu Handy and MakerWorld on External Storage"
|
||||
msgid "Save the printing files sent from the slicer and other apps on External Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Allow Prompt Sound"
|
||||
@@ -7066,6 +7230,36 @@ msgstr ""
|
||||
msgid "Check if the nozzle is clumping by filament or other foreign objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "Purify Air at Print End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal Circulation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Alignment Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pauses printing when build plate misalignment is detected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Foreign Object Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checks for any objects on the build plate at the start of a print to avoid collisions."
|
||||
msgstr ""
|
||||
|
||||
msgid "Printed Part Displacement Detection"
|
||||
msgstr ""
|
||||
|
||||
msgid "Monitors the printed part during printing and alerts immediately if it shifts or collapses."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checks if the nozzle is clumping by filament or other foreign objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open Door Detection"
|
||||
msgstr ""
|
||||
|
||||
@@ -7075,6 +7269,12 @@ msgstr ""
|
||||
msgid "Pause printing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print Status Snapshot"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically capture and upload print photos, showing defects during printing and the final result for remote viewing."
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle Type"
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
@@ -7224,10 +7424,10 @@ msgstr ""
|
||||
msgid "Configuration incompatible"
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament switcher detected. All AMS filaments are now available for both extruders. The slicer will auto-assign for optimal printing. "
|
||||
msgid "Filament switcher detected. All AMS filaments are now available for both extruders. The slicer will auto-assign for optimal printing."
|
||||
msgstr ""
|
||||
|
||||
msgid "A filament switcher is detected but not calibrated and thus currently unavailable. Please calibrate it on the printer and synchronize before use. "
|
||||
msgid "A filament switcher is detected but not calibrated and thus currently unavailable. Please calibrate it on the printer and synchronize before use."
|
||||
msgstr ""
|
||||
|
||||
msgid "Tips"
|
||||
@@ -7256,6 +7456,9 @@ msgstr ""
|
||||
msgid "Click to edit preset"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Project Filaments"
|
||||
msgstr ""
|
||||
|
||||
@@ -7622,6 +7825,15 @@ msgstr ""
|
||||
msgid "Please resolve the slicing errors and publish again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Network plug-in switched successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load network plug-in. Please restart the application."
|
||||
msgstr ""
|
||||
|
||||
msgid "The network plug-in was not detected. Network related features are unavailable."
|
||||
msgstr ""
|
||||
|
||||
@@ -8016,18 +8228,6 @@ msgstr ""
|
||||
msgid "Choose Download Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Latest)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network plug-in switched successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to load network plug-in. Please restart the application."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"You've selected network plug-in version %s.\n"
|
||||
@@ -8441,6 +8641,15 @@ msgstr ""
|
||||
msgid "Displays current viewport FPS in the top-right corner."
|
||||
msgstr ""
|
||||
|
||||
msgid "G-code Preview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dim lower layers"
|
||||
msgstr ""
|
||||
|
||||
msgid "When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -8723,6 +8932,7 @@ msgstr ""
|
||||
msgid "The selected preset is null!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Layer range"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
@@ -8894,9 +9104,6 @@ msgstr ""
|
||||
msgid "Send print job"
|
||||
msgstr ""
|
||||
|
||||
msgid "On"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not satisfied with the grouping of filaments? Regroup and slice ->"
|
||||
msgstr ""
|
||||
|
||||
@@ -8912,6 +9119,9 @@ msgstr ""
|
||||
msgid "To ensure print quality, the drying temperature will be lowered during printing."
|
||||
msgstr ""
|
||||
|
||||
msgid "Select timelapse storage location"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto Bed Leveling"
|
||||
msgstr ""
|
||||
|
||||
@@ -8936,6 +9146,12 @@ msgid ""
|
||||
"*Automatic mode: Check for calibration before printing. Skip if unnecessary."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shared PA Profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nozzles and filaments of the same type share the same PA profile."
|
||||
msgstr ""
|
||||
|
||||
msgid "Send complete"
|
||||
msgstr ""
|
||||
|
||||
@@ -8979,6 +9195,10 @@ msgstr ""
|
||||
msgid "The current nozzle mapping may produce an extra %0.2f g of waste."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "Recommended filament arrangement saves %s->"
|
||||
msgstr ""
|
||||
|
||||
#, 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."
|
||||
msgstr ""
|
||||
@@ -8996,6 +9216,9 @@ msgstr ""
|
||||
msgid "The current printer does not support timelapse in Traditional Mode when printing By-Object."
|
||||
msgstr ""
|
||||
|
||||
msgid "I have checked the installed nozzle and want to print anyway."
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors"
|
||||
msgstr ""
|
||||
|
||||
@@ -9026,6 +9249,37 @@ msgstr ""
|
||||
msgid "This process determines the dynamic flow values to improve overall print quality."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "%s space less than 20MB. Timelapse may not save properly. You can turn it off or"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean up files"
|
||||
msgstr ""
|
||||
|
||||
msgid "Low internal storage. This timelapse will overwrite the oldest video files."
|
||||
msgstr ""
|
||||
|
||||
msgid "Low external storage. This timelapse will overwrite the oldest video files."
|
||||
msgstr ""
|
||||
|
||||
msgid "Insufficient external storage for time-lapse photography. Connect to computer to delete files, or use a larger memory card."
|
||||
msgstr ""
|
||||
|
||||
msgid "Storage Space Not Enough"
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm & Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel Timelapse & Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preparing print job"
|
||||
msgstr ""
|
||||
|
||||
@@ -9072,7 +9326,7 @@ msgstr ""
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Tips: If you changed your nozzle of your printer lately, Please go to 'Device -> Printer parts' to change your nozzle setting."
|
||||
msgid "Tips: If you changed your nozzle of your printer lately, please go to 'Device -> Printer parts' to change your nozzle setting."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
@@ -9090,10 +9344,22 @@ msgstr ""
|
||||
msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). Please verify the nozzle or material settings and try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your current firmware version cannot start this print job. Please update to the latest version and try again."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "The hardness of current material (%s) exceeds the hardness of %s(%s). It may cause nozzle wear, leading to material leakage and unstable flow. Please exercise caution when using it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some filaments may switch between extruders during printing. Manual K-value calibration cannot be applied throughout the entire print, which may affect print quality. Enabling Flow Dynamics Calibration is recommended."
|
||||
msgstr ""
|
||||
|
||||
msgid "There is stringing-prone filament in this file. For best print quality, we recommend switching nozzle clumping detection to Auto mode."
|
||||
msgstr ""
|
||||
|
||||
msgid "If 'Dynamic Flow Calibration' is set to Auto/On, the system will use the manual calibration value or the default value and skip the flow calibration process. You can perform a manual flow calibration for TPU filament on the 'Calibration' page."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "[ %s ] requires printing in a high-temperature environment. Please close the door."
|
||||
msgstr ""
|
||||
@@ -9197,12 +9463,19 @@ msgstr ""
|
||||
msgid "This printer does not support printing all plates."
|
||||
msgstr ""
|
||||
|
||||
msgid "The current firmware supports a maximum of 16 materials. You can either reduce the number of materials to 16 or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support."
|
||||
#, c-format, boost-format
|
||||
msgid "The current firmware supports a maximum of %s materials. You can either reduce the number of materials to %s or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support."
|
||||
msgstr ""
|
||||
|
||||
msgid "The type of external filament is unknown or does not match with the filament type in the slicing file. Please make sure you have installed the correct filament in the external spool."
|
||||
msgstr ""
|
||||
|
||||
msgid "TPU 90A/TPU 85A are too soft. It is recommended to perform manual flow calibration on the 'Calibration' page. If 'Dynamic Flow Calibration' is set to auto/on, the system will use the previous calibration value and skip the flow calibration process."
|
||||
msgstr ""
|
||||
|
||||
msgid "The filament in the AMS may be insufficient for this print. Please refill or replace it."
|
||||
msgstr ""
|
||||
|
||||
msgid "Current firmware does not support file transfer to internal storage."
|
||||
msgstr ""
|
||||
|
||||
@@ -10315,15 +10588,9 @@ msgstr ""
|
||||
msgid "Reset all filament mapping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Recommended filament)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right Extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Advanced Options"
|
||||
msgstr ""
|
||||
|
||||
@@ -10456,12 +10723,6 @@ msgstr ""
|
||||
msgid "Re-calculate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Left extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Right extruder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Multiplier"
|
||||
msgstr ""
|
||||
|
||||
@@ -10582,24 +10843,28 @@ msgstr ""
|
||||
msgid "Select objects by rectangle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in positive Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Down"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative X direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -10684,9 +10949,17 @@ msgstr ""
|
||||
msgid "Toggle printable for object/part"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between Prepare/Preview"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Space"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open actions speed dial"
|
||||
msgstr ""
|
||||
|
||||
@@ -10744,9 +11017,17 @@ msgstr ""
|
||||
msgid "Move slider 5x faster"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to start position"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to last position"
|
||||
msgstr ""
|
||||
|
||||
@@ -10760,7 +11041,10 @@ msgstr ""
|
||||
msgid "Network plug-in update"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click OK to update the Network plug-in the next time Orca Slicer launches."
|
||||
msgid "Click OK to update the Network plug-in now. If a file is in use, the update will be applied the next time Orca Slicer launches."
|
||||
msgstr ""
|
||||
|
||||
msgid "A new Network plug-in is available. Do you want to install it?"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
@@ -11013,13 +11297,6 @@ msgstr ""
|
||||
msgid "Internal Bridge"
|
||||
msgstr ""
|
||||
|
||||
#, boost-format
|
||||
msgid "Failed to calculate line width of %1%. Cannot get value of “%2%” "
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"
|
||||
msgstr ""
|
||||
|
||||
msgid "undefined error"
|
||||
msgstr ""
|
||||
|
||||
@@ -11974,9 +12251,6 @@ msgid ""
|
||||
"In addition, if Slow down for curled perimeters is disabled or Classic overhang mode is enabled, it will be the print speed of overhang walls that are supported by less than 13%, whether they are part of a bridge or an overhang."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed of internal bridges. If the value is expressed as a percentage, it will be calculated based on the bridge_speed. Default value is 150%."
|
||||
msgstr ""
|
||||
|
||||
@@ -12295,7 +12569,7 @@ msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane.Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top.The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection."
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection."
|
||||
msgstr ""
|
||||
|
||||
msgid "Top expansion wall margin"
|
||||
@@ -14883,6 +15157,9 @@ msgstr ""
|
||||
msgid "Traditional"
|
||||
msgstr ""
|
||||
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
msgid "Farthest point timelapse"
|
||||
msgstr ""
|
||||
|
||||
@@ -15786,6 +16063,12 @@ msgstr ""
|
||||
msgid "Whether this printer supports fast purge mode with optimized temperature and multiplier."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament change"
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder on the tower, excluding a hotend change."
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder for a hotend change on the tower."
|
||||
msgstr ""
|
||||
|
||||
@@ -17981,7 +18264,7 @@ msgstr ""
|
||||
msgid "Pack"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cleans and rebuilds system profiles cache on next launch"
|
||||
msgid "Cleans and rebuilds system profiles cache on next launch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Clean system profiles cache"
|
||||
@@ -17993,16 +18276,16 @@ msgstr ""
|
||||
msgid "Loaded profiles overview"
|
||||
msgstr ""
|
||||
|
||||
msgid "This section shows information for loaded profiles"
|
||||
msgid "This section shows information for loaded profiles."
|
||||
msgstr ""
|
||||
|
||||
msgid "Exports detailed overview of loaded profiles in json format"
|
||||
msgid "Exports detailed overview of loaded profiles in json format."
|
||||
msgstr ""
|
||||
|
||||
msgid "Configurations folder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Opens configurations folder"
|
||||
msgid "Opens configurations folder."
|
||||
msgstr ""
|
||||
|
||||
msgid "Log level"
|
||||
@@ -18017,15 +18300,12 @@ msgstr ""
|
||||
msgid "Profiles"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select NO to close dialog and review project"
|
||||
msgid "Select NO to close dialog and review project."
|
||||
msgstr ""
|
||||
|
||||
msgid "No project file on current session. Only logs will be included to package"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select NO to close dialog and review project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please make sure any instances of OrcaSlicer are not running"
|
||||
msgstr ""
|
||||
|
||||
@@ -18672,7 +18952,7 @@ msgstr ""
|
||||
msgid "Error code not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "The printer is busy, Please check the device page for the file and try to start printing again."
|
||||
msgid "The printer is busy, please check the device page for the file and try to start printing again."
|
||||
msgstr ""
|
||||
|
||||
msgid "The file is lost, please check and try again."
|
||||
@@ -18803,6 +19083,9 @@ msgstr ""
|
||||
msgid "Skipping objects."
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Filament"
|
||||
msgstr ""
|
||||
|
||||
msgid "Null Color"
|
||||
msgstr ""
|
||||
|
||||
@@ -18834,15 +19117,15 @@ msgstr ""
|
||||
msgid "Show details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Version to install:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download and Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip for Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "A new version of the Bambu Network Plug-in is available."
|
||||
msgstr ""
|
||||
|
||||
@@ -18853,16 +19136,16 @@ msgstr ""
|
||||
msgid "Update to version:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remind Later"
|
||||
msgid "(Latest)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgid "(installed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "The Bambu Network Plug-in has been installed successfully."
|
||||
@@ -18874,9 +19157,6 @@ msgstr ""
|
||||
msgid "Restart Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "NO RAMMING AT ALL"
|
||||
msgstr ""
|
||||
|
||||
@@ -19639,7 +19919,7 @@ msgstr ""
|
||||
#~ msgid "Speed of bridge and completely overhang wall"
|
||||
#~ msgstr "This is the speed for bridges and 100% overhang walls."
|
||||
|
||||
#~ msgid "Higher chamber temperature can help suppress or reduce warping and potentially lead to higher interlayer bonding strength for high temperature materials like ABS, ASA, PC, PA and so on. At the same time, the air filtration of ABS and ASA will get worse. While for PLA, PETG, TPU, PVA and other low temperature materials,the actual chamber temperature should not be high to avoid cloggings, so 0 which stands for turning off is highly recommended"
|
||||
#~ msgid "Higher chamber temperature can help suppress or reduce warping and potentially lead to higher interlayer bonding strength for high temperature materials like ABS, ASA, PC, PA and so on. At the same time, the air filtration of ABS and ASA will get worse. While for PLA, PETG, TPU, PVA and other low temperature materials, the actual chamber temperature should not be high to avoid cloggings, so 0 which stands for turning off is highly recommended"
|
||||
#~ msgstr "Higher chamber temperature can help suppress or reduce warping and potentially lead to higher interlayer bonding strength for high temperature materials like ABS, ASA, PC, PA and so on. At the same time, the air filtration of ABS and ASA will get worse. While for PLA, PETG, TPU, PVA and other low temperature materials, the actual chamber temperature should not be high to avoid clogs, so 0 (turned off) is highly recommended."
|
||||
|
||||
#~ msgid "Printer local connection failed, please try again."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+3511
-2558
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Bambulab",
|
||||
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
||||
"version": "02.01.00.22",
|
||||
"version": "02.01.00.23",
|
||||
"force_update": "0",
|
||||
"description": "BBL configurations",
|
||||
"machine_model_list": [
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<span id="statusText" class="status-text"></span>
|
||||
</footer>
|
||||
</main>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,12 @@ let selectedCapabilityType = "";
|
||||
let selectedHasPresetOverride = false;
|
||||
let selectedReadOnly = false;
|
||||
|
||||
// Whether the frame already holds the selected capability's custom UI (payloads are gated by
|
||||
// IsCurrentCapability and every selection change clears the view). Saving re-sends the whole
|
||||
// capability_config payload, and rebuilding the frame from it would reload the plugin's page under
|
||||
// the user's cursor, so a loaded frame gets the new values posted in instead.
|
||||
let customFrameLoaded = false;
|
||||
|
||||
function SafeJsonParse(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
@@ -193,6 +199,7 @@ function ClearCapabilityConfigView() {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameLoaded = false;
|
||||
}
|
||||
if (text)
|
||||
text.value = "";
|
||||
@@ -248,8 +255,14 @@ function ApplyCapabilityConfig(payload) {
|
||||
|
||||
if (html) {
|
||||
if (custom) {
|
||||
const context = OrcaConfigContext(payload, "preset");
|
||||
custom.hidden = false;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
if (customFrameLoaded && custom.contentWindow) {
|
||||
custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*");
|
||||
} else {
|
||||
customFrameLoaded = true;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, context);
|
||||
}
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -260,6 +273,7 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameLoaded = false;
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = false;
|
||||
@@ -354,39 +368,6 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, drop the preset's
|
||||
// override, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
@@ -432,6 +413,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame("configCustom");
|
||||
|
||||
SendMessage("request_capabilities");
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<script type="text/javascript" src="../../data/text.js"></script>
|
||||
<script type="text/javascript" src="../js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="../js/common.js"></script>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./plugin-sort.js"></script>
|
||||
<script src="../js/fuzzy-search.js"></script>
|
||||
|
||||
@@ -90,6 +90,7 @@ function OnInit() {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame("configCustom");
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!event.target.closest(".ctx"))
|
||||
@@ -1077,7 +1078,7 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (html) {
|
||||
if (custom) {
|
||||
custom.hidden = false;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, OrcaConfigContext(payload, "global"));
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -1178,39 +1179,6 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, restore the plugin's
|
||||
// defaults, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// The host surface a plugin capability's custom configuration UI gets, shared by the Plugins dialog
|
||||
// and by a preset's plugin configuration dialog. Both sandbox the page into an opaque origin, so this
|
||||
// bridge is its only channel — and both must offer plugin authors the same one.
|
||||
|
||||
// The host theme (WebViewHostDialog::host_theme_vars_css) is injected as a ":root{--orca-*}" rule in
|
||||
// <style id="orca-host-theme-vars">, but only on the top-level page, so a sandboxed config UI never
|
||||
// sees it unless we hand it over. Relay that CSS verbatim instead of re-deriving it: C++ stays the
|
||||
// only place the contract is spelled out, and the host re-themes the style in place, so reading it
|
||||
// always yields the live theme.
|
||||
function OrcaThemeSnapshot() {
|
||||
const style = document.getElementById("orca-host-theme-vars");
|
||||
return {
|
||||
theme: document.documentElement.getAttribute("data-orca-theme") === "dark" ? "dark" : "light",
|
||||
css: style ? style.textContent : ""
|
||||
};
|
||||
}
|
||||
|
||||
// Inlined into a <script> or a JSON payload: a stored "</script>" would close the tag early, so
|
||||
// escape "<" — the literal stays valid JSON.
|
||||
function OrcaInlineJson(value) {
|
||||
return JSON.stringify(value).replace(/</g, "\\u003c");
|
||||
}
|
||||
|
||||
// What a custom UI can learn about the surface it is edited on, kept to what changes the page's own
|
||||
// behavior: "Restore defaults" writes the plugin's get_default_config() in the Plugins dialog but
|
||||
// drops the preset's override in a preset dialog, so a page needs the scope to label its own button.
|
||||
function OrcaConfigContext(payload, scope) {
|
||||
return {
|
||||
scope: scope,
|
||||
readOnly: payload ? payload.read_only === true : false,
|
||||
hasPresetOverride: payload ? payload.has_preset_override === true : false
|
||||
};
|
||||
}
|
||||
|
||||
// The whole host surface: read the config, save one, restore defaults, follow the theme, and be told
|
||||
// when any of it lands.
|
||||
function BuildCustomConfigDocument(html, config, context) {
|
||||
const theme = OrcaThemeSnapshot();
|
||||
const bridge = `<style id="orca-host-theme-vars"></style>
|
||||
<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var themeHandlers = [];
|
||||
var current = ${OrcaInlineJson(config === undefined ? {} : config)};
|
||||
var context = ${OrcaInlineJson(context || {})};
|
||||
var theme = ${OrcaInlineJson(theme)};
|
||||
|
||||
function applyTheme(next) {
|
||||
if (next && typeof next.css === "string") theme = next;
|
||||
var style = document.getElementById("orca-host-theme-vars");
|
||||
if (style) style.textContent = theme.css;
|
||||
if (document.documentElement) document.documentElement.setAttribute("data-orca-theme", theme.theme);
|
||||
themeHandlers.forEach(function (handler) {
|
||||
try { handler(theme.theme); } catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
getContext: function () { return context; },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
},
|
||||
onTheme: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
themeHandlers.push(cb);
|
||||
try { cb(theme.theme); } catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data) return;
|
||||
if (event.data.__orca === "theme") { applyTheme(event.data.theme); return; }
|
||||
if (event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
if (event.data.context) context = event.data.context;
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
applyTheme();
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
// The host re-themes an open dialog in place (WebViewHostDialog::host_theme_apply_js), which a
|
||||
// sandboxed child frame never sees. Relay it so a custom UI follows a light/dark switch live.
|
||||
function OrcaWatchThemeForFrame(frameId) {
|
||||
const relay = () => {
|
||||
const frame = document.getElementById(frameId);
|
||||
if (!frame || frame.hidden || !frame.contentWindow)
|
||||
return;
|
||||
frame.contentWindow.postMessage({ __orca: "theme", theme: OrcaThemeSnapshot() }, "*");
|
||||
};
|
||||
new MutationObserver(relay).observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-orca-theme"]
|
||||
});
|
||||
}
|
||||
+355
-145
@@ -3,18 +3,18 @@
|
||||
# dependencies = ["numpy"]
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# name = "Orca Inspector Example"
|
||||
# name = "Orca Inspector"
|
||||
# description = "An interactive panel that browses the whole orca.host read-only API and demos every orca.host.ui facility."
|
||||
# author = "OrcaSlicer"
|
||||
# version = "0.0.1"
|
||||
# author = "SoftFever"
|
||||
# version = "0.0.3"
|
||||
# ///
|
||||
"""Orca Inspector — the worked example for `orca.host` and `orca.host.ui`.
|
||||
"""Orca Inspector — a guided tour of `orca.host` and `orca.host.ui`.
|
||||
|
||||
Run it from the Plugins dialog. It opens a NON-MODAL window (OrcaSlicer stays
|
||||
usable) with a sidebar of sections, each exercising one part of the API:
|
||||
|
||||
Overview plater state, scene statistics, current printer/process/filaments
|
||||
Presets every PresetCollection, preset flags, any preset's full config
|
||||
Presets every PresetCollection, filament slots, config viewer windows
|
||||
Config the complete merged full_config as a searchable table
|
||||
Model Model -> Object -> Volume/Instance tree with lazy mesh geometry
|
||||
Assembly objects grouped by module_name, per-instance assemble transforms
|
||||
@@ -32,9 +32,10 @@ and heavy mesh geometry (zero-copy numpy arrays, world-space math, the little
|
||||
point-cloud previews) only when you ask for it. "Refresh" drops the cache and
|
||||
refetches the visible section.
|
||||
|
||||
Style rules the page follows (see the plugin docs):
|
||||
- no hardcoded colors — only the host-injected --orca-* theme variables, so
|
||||
the panel matches light and dark mode automatically;
|
||||
Style rules the pages follow (see the plugin docs):
|
||||
- no hardcoded theme: BASE_CSS aliases the host-injected --orca-* variables
|
||||
(with fallbacks so the raw HTML previews in a plain browser) and styles the
|
||||
body and native controls from them — the host injects variables only;
|
||||
- self-contained HTML (inline CSS/JS, no external resources);
|
||||
- on_message runs on the UI thread, so long work (the progress demos) is
|
||||
offloaded to a thread and results are pushed back with win.post().
|
||||
@@ -45,6 +46,7 @@ transforms need bindings added in July 2026 — on older builds the panel shows
|
||||
what is missing instead of failing.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
@@ -125,6 +127,19 @@ def split_config_list(value):
|
||||
# --------------------------------------------------------------------------- #
|
||||
# section builders
|
||||
# --------------------------------------------------------------------------- #
|
||||
def filament_slots(bundle):
|
||||
"""One entry per filament slot: the mounted preset (current_filament_presets)
|
||||
plus its color and material from the merged config."""
|
||||
cfg = bundle.full_config_value
|
||||
colors = split_config_list(cfg("filament_colour"))
|
||||
types = split_config_list(cfg("filament_type"))
|
||||
return [{"slot": i + 1,
|
||||
"color": colors[i] if i < len(colors) else "",
|
||||
"material": types[i] if i < len(types) else "",
|
||||
"preset": preset_dict(preset) if preset else None}
|
||||
for i, preset in enumerate(bundle.current_filament_presets())]
|
||||
|
||||
|
||||
def build_overview():
|
||||
plater = orca.host.plater()
|
||||
model = orca.host.model()
|
||||
@@ -132,17 +147,7 @@ def build_overview():
|
||||
objects = model.objects()
|
||||
|
||||
cfg = bundle.full_config_value
|
||||
colors = split_config_list(cfg("filament_colour"))
|
||||
types = split_config_list(cfg("filament_type"))
|
||||
filaments = []
|
||||
for i, preset in enumerate(bundle.current_filament_presets()):
|
||||
filaments.append({
|
||||
"name": preset.name if preset else "<missing preset>",
|
||||
"color": colors[i] if i < len(colors) else "",
|
||||
"type": types[i] if i < len(types) else "",
|
||||
"is_system": bool(preset.is_system) if preset else False,
|
||||
"is_dirty": bool(preset.is_dirty) if preset else False,
|
||||
})
|
||||
filaments = filament_slots(bundle)
|
||||
|
||||
printer = bundle.current_printer_preset()
|
||||
process = bundle.current_process_preset()
|
||||
@@ -189,13 +194,12 @@ def build_overview():
|
||||
}
|
||||
|
||||
|
||||
# label -> how to reach the PresetCollection on the bundle
|
||||
# PresetCollection attribute -> label, in the order the Presets tab stacks its groups.
|
||||
# The bundle also exposes sla_prints/sla_materials, omitted as OrcaSlicer has no SLA.
|
||||
COLLECTIONS = [
|
||||
("prints", "Process"),
|
||||
("printers", "Printer"),
|
||||
("filaments", "Filament"),
|
||||
("sla_prints", "SLA print"),
|
||||
("sla_materials", "SLA material"),
|
||||
("prints", "Process"),
|
||||
]
|
||||
|
||||
|
||||
@@ -206,21 +210,25 @@ def build_presets():
|
||||
coll = getattr(bundle, attr)
|
||||
names = coll.preset_names()
|
||||
selected = coll.selected_preset()
|
||||
selected_name = coll.selected_preset_name()
|
||||
shown = names[:MAX_PRESET_NAMES]
|
||||
if selected_name and selected_name not in shown:
|
||||
shown.insert(0, selected_name) # the active preset stays pickable past the cap
|
||||
collections.append({
|
||||
"key": attr,
|
||||
"label": label,
|
||||
"size": coll.size(),
|
||||
"selected_name": coll.selected_preset_name(),
|
||||
"selected_name": selected_name,
|
||||
# selected = as saved on disk; edited = selected + unsaved modifications
|
||||
"selected": {"name": selected.name, "is_dirty": selected.is_dirty,
|
||||
"config_key_count": len(selected.config_keys())},
|
||||
"edited": preset_dict(coll.edited_preset()),
|
||||
"names": names[:MAX_PRESET_NAMES],
|
||||
"names": shown,
|
||||
"truncated": max(0, len(names) - MAX_PRESET_NAMES),
|
||||
})
|
||||
return {
|
||||
"collections": collections,
|
||||
"filament_names": bundle.current_filament_preset_names(),
|
||||
"filament_slots": filament_slots(bundle),
|
||||
}
|
||||
|
||||
|
||||
@@ -231,8 +239,7 @@ def build_preset_config(collection_key, name):
|
||||
preset = getattr(bundle, collection_key).find_preset(name)
|
||||
if preset is None:
|
||||
raise ValueError(f"preset {name!r} not found")
|
||||
return {"preset": preset_dict(preset),
|
||||
"rows": config_rows(preset.config_keys(), preset.config_value)}
|
||||
return config_rows(preset.config_keys(), preset.config_value)
|
||||
|
||||
|
||||
def build_config():
|
||||
@@ -467,102 +474,149 @@ SECTION_BUILDERS = {
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# the page — sidebar app, themed exclusively via the injected --orca-* vars
|
||||
# the pages — themed via BASE_CSS, which aliases the injected --orca-* vars
|
||||
# --------------------------------------------------------------------------- #
|
||||
# The host injects theme *variables* only (never element styles), so every page owns
|
||||
# its base look. The fallbacks keep the raw HTML previewable in a plain browser.
|
||||
BASE_CSS = r"""
|
||||
:root {
|
||||
--bg: var(--orca-bg, #ffffff);
|
||||
--fg: var(--orca-fg, #1f2429);
|
||||
--muted: var(--orca-muted, #6b7580);
|
||||
--border: var(--orca-border, #d9dee3);
|
||||
--accent: var(--orca-accent, #009688);
|
||||
--accent-fg: var(--orca-accent-fg, #ffffff);
|
||||
--ui: var(--orca-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: var(--orca-bg, #2b2d30);
|
||||
--fg: var(--orca-fg, #e4e6e8);
|
||||
--muted: var(--orca-muted, #9aa0a6);
|
||||
--border: var(--orca-border, #3d4043);
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; background:var(--bg); color:var(--fg); font:13px/1.45 var(--ui); }
|
||||
button { font:inherit; padding:4px 12px; cursor:pointer; border-radius:6px;
|
||||
background:var(--accent); color:var(--accent-fg); border:1px solid var(--accent); }
|
||||
button.secondary { background:transparent; color:var(--fg); border-color:var(--border); }
|
||||
button:disabled { opacity:.55; cursor:default; }
|
||||
input, select { font:inherit; color:var(--fg); background:var(--bg);
|
||||
border:1px solid var(--border); border-radius:6px; padding:4px 8px; }
|
||||
:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
|
||||
"""
|
||||
|
||||
# Identity hues for the preset collections, shared by the main page's group styling and
|
||||
# the config viewer window. Deliberately the same in light and dark mode: they say what a
|
||||
# card is, not what theme is on.
|
||||
HUES = {"printers": "#9a6ee8", "filaments": "#ee8f41", "prints": "#4e8df6"}
|
||||
HUE_CSS = "".join(f" .hue-{key} {{ --hue:{color}; }}\n" for key, color in HUES.items())
|
||||
|
||||
# The filterable key/value config table, shared by the Config tab and the viewer window.
|
||||
CFG_TABLE_CSS = r"""
|
||||
table.cfg { width:100%; border-collapse:collapse; font-size:12px; }
|
||||
table.cfg th { text-align:left; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--muted); padding-bottom:4px; }
|
||||
table.cfg td:first-child { font-family:var(--mono); white-space:nowrap;
|
||||
vertical-align:top; }
|
||||
table.cfg td.val { font-family:var(--mono); white-space:pre-wrap; word-break:break-word; }
|
||||
td.val .more { color:var(--accent); cursor:pointer; }
|
||||
"""
|
||||
|
||||
PAGE = r"""<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
:root { --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
||||
<style>""" + BASE_CSS + r"""
|
||||
body { height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
||||
|
||||
header { flex:none; display:flex; align-items:center; gap:10px;
|
||||
padding:10px 16px; border-bottom:1px solid var(--orca-border); }
|
||||
padding:10px 16px; border-bottom:1px solid var(--border); }
|
||||
header h1 { font-size:15px; margin:0; }
|
||||
#stamp { flex:1; color:var(--orca-muted); font-size:12px; }
|
||||
button.secondary { background:transparent; color:var(--orca-fg);
|
||||
border-color:var(--orca-border); }
|
||||
#stamp { flex:1; color:var(--muted); font-size:12px; }
|
||||
button.small { padding:2px 10px; font-size:12px; }
|
||||
|
||||
main { flex:1; display:flex; min-height:0; }
|
||||
nav { flex:none; width:150px; padding:8px 0; overflow-y:auto;
|
||||
border-right:1px solid var(--orca-border); }
|
||||
border-right:1px solid var(--border); }
|
||||
nav .item { padding:8px 14px; cursor:pointer; user-select:none;
|
||||
color:var(--orca-muted); border-left:3px solid transparent; }
|
||||
nav .item:hover { color:var(--orca-fg); }
|
||||
nav .item.active { color:var(--orca-fg); font-weight:600;
|
||||
border-left-color:var(--orca-accent); }
|
||||
color:var(--muted); border-left:3px solid transparent; }
|
||||
nav .item:hover { color:var(--fg); }
|
||||
nav .item.active { color:var(--fg); font-weight:600;
|
||||
border-left-color:var(--accent); }
|
||||
nav .glyph { display:inline-block; width:1.5em; }
|
||||
#content { flex:1; overflow:auto; padding:14px 16px 28px; }
|
||||
|
||||
.tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(108px,1fr));
|
||||
gap:10px; margin-bottom:12px; }
|
||||
.tile { border:1px solid var(--orca-border); border-radius:8px; padding:10px 12px; }
|
||||
.tile { border:1px solid var(--border); border-radius:8px; padding:10px 12px; }
|
||||
.tile .v { font-size:20px; font-weight:600; font-variant-numeric:tabular-nums; }
|
||||
.tile .l { font-size:11px; color:var(--orca-muted); margin-top:2px; }
|
||||
.tile .l { font-size:11px; color:var(--muted); margin-top:2px; }
|
||||
|
||||
.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:12px; }
|
||||
.card { border:1px solid var(--orca-border); border-radius:8px;
|
||||
.card { border:1px solid var(--border); border-radius:8px;
|
||||
padding:12px 14px; min-width:0; }
|
||||
.card h3 { margin:0 0 8px; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--orca-muted); }
|
||||
text-transform:uppercase; color:var(--muted); }
|
||||
.card.wide { grid-column:1 / -1; }
|
||||
""" + HUE_CSS + r""" .card.hued { border-top:3px solid var(--hue); }
|
||||
.dot { display:inline-block; width:9px; height:9px; border-radius:50%;
|
||||
background:var(--hue); margin-right:6px; }
|
||||
.pgroup { margin-bottom:18px; }
|
||||
.pgroup-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
|
||||
.pgroup-head h2 { margin:0; font-size:12px; letter-spacing:.07em; text-transform:uppercase; }
|
||||
.pgroup-head select { flex:0 1 260px; min-width:120px; }
|
||||
.spacer { flex:1; }
|
||||
|
||||
.kv { display:grid; grid-template-columns:minmax(110px,max-content) 1fr;
|
||||
gap:3px 14px; font-size:12px; }
|
||||
.kv .k { color:var(--orca-muted); }
|
||||
.kv .k { color:var(--muted); }
|
||||
.kv .v { font-family:var(--mono); word-break:break-word; }
|
||||
|
||||
.badge { display:inline-block; border:1px solid var(--orca-border);
|
||||
color:var(--orca-muted); border-radius:999px; padding:1px 8px;
|
||||
.badge { display:inline-block; border:1px solid var(--border);
|
||||
color:var(--muted); border-radius:999px; padding:1px 8px;
|
||||
font-size:11px; margin:1px 3px 1px 0; white-space:nowrap; }
|
||||
.badge.on { background:var(--orca-accent); border-color:var(--orca-accent);
|
||||
color:var(--orca-accent-fg); }
|
||||
.badge.on { background:var(--accent); border-color:var(--accent);
|
||||
color:var(--accent-fg); }
|
||||
.swatch { display:inline-block; width:12px; height:12px; border-radius:3px;
|
||||
border:1px solid var(--orca-border); vertical-align:-2px; margin-right:5px; }
|
||||
border:1px solid var(--border); vertical-align:-2px; margin-right:5px; }
|
||||
|
||||
.toolbar { display:flex; gap:8px; align-items:center; margin-bottom:10px; }
|
||||
.toolbar input { flex:1; max-width:340px; }
|
||||
.count { color:var(--orca-muted); font-size:12px; }
|
||||
|
||||
table.cfg { width:100%; border-collapse:collapse; font-size:12px; }
|
||||
table.cfg td:first-child { font-family:var(--mono); white-space:nowrap;
|
||||
vertical-align:top; }
|
||||
table.cfg td.val { font-family:var(--mono); white-space:pre-wrap; word-break:break-word; }
|
||||
td.val .more { color:var(--orca-accent); cursor:pointer; }
|
||||
|
||||
.count { color:var(--muted); font-size:12px; }
|
||||
""" + CFG_TABLE_CSS + r"""
|
||||
.node { margin:1px 0; }
|
||||
.nhead { display:flex; align-items:center; gap:7px; padding:4px 8px;
|
||||
border-radius:6px; cursor:pointer; user-select:none; flex-wrap:wrap; }
|
||||
.nhead:hover { background:var(--orca-border); }
|
||||
.twist { width:1em; flex:none; color:var(--orca-muted); font-size:10px; }
|
||||
.nhead:hover { background:var(--border); }
|
||||
.twist { width:1em; flex:none; color:var(--muted); font-size:10px; }
|
||||
.nname { font-weight:600; }
|
||||
.nbody { display:none; margin-left:14px; padding:4px 0 4px 12px;
|
||||
border-left:1px dotted var(--orca-border); }
|
||||
border-left:1px dotted var(--border); }
|
||||
.node.open > .nhead .twist { transform:rotate(90deg); }
|
||||
.node.open > .nbody { display:block; }
|
||||
.subhead { margin:8px 0 4px; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--orca-muted); }
|
||||
text-transform:uppercase; color:var(--muted); }
|
||||
|
||||
.banner { border:1px solid var(--orca-border); border-left:3px solid var(--orca-accent);
|
||||
.banner { border:1px solid var(--border); border-left:3px solid var(--accent);
|
||||
border-radius:6px; padding:9px 12px; margin-bottom:12px; font-size:12px; }
|
||||
.muted { color:var(--orca-muted); }
|
||||
.muted { color:var(--muted); }
|
||||
.mono { font-family:var(--mono); }
|
||||
|
||||
canvas.preview { width:100%; height:160px; border:1px solid var(--orca-border);
|
||||
canvas.preview { width:100%; height:160px; border:1px solid var(--border);
|
||||
border-radius:6px; }
|
||||
.previews { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:8px; }
|
||||
.previews .cap { font-size:11px; color:var(--orca-muted); text-align:center; margin-top:2px; }
|
||||
.previews .cap { font-size:11px; color:var(--muted); text-align:center; margin-top:2px; }
|
||||
|
||||
.log { font-family:var(--mono); font-size:12px; border:1px solid var(--orca-border);
|
||||
.log { font-family:var(--mono); font-size:12px; border:1px solid var(--border);
|
||||
border-radius:6px; padding:8px 10px; max-height:190px; overflow:auto; }
|
||||
.log div { padding:1px 0; }
|
||||
fieldset { border:1px solid var(--orca-border); border-radius:6px; margin:0 0 10px; }
|
||||
legend { color:var(--orca-muted); font-size:11px; padding:0 6px; }
|
||||
fieldset { border:1px solid var(--border); border-radius:6px; margin:0 0 10px; }
|
||||
legend { color:var(--muted); font-size:11px; padding:0 6px; }
|
||||
.frow { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:6px 0; }
|
||||
label { font-size:12px; color:var(--orca-muted); }
|
||||
label { font-size:12px; color:var(--muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -588,7 +642,7 @@ const SECTIONS = [
|
||||
['assembly', '⬡', 'Assembly'],
|
||||
['uikit', '▣', 'UI Toolkit'],
|
||||
];
|
||||
const S = { current:'overview', cache:{}, busy:{}, colors:[], uiLog:[] };
|
||||
const S = { current:'overview', cache:{}, busy:{}, colors:[], uiLog:[], gen:0 };
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
const esc = s => String(s == null ? '' : s)
|
||||
@@ -598,6 +652,9 @@ const deg = r => num(r * 180 / Math.PI, 1) + '°';
|
||||
const vec3 = (v, d=2) => v ? '(' + v.map(c => num(c, d)).join(', ') + ')' : '—';
|
||||
const yn = b => b ? 'yes' : 'no';
|
||||
const badge = (label, on) => '<span class="badge' + (on ? ' on' : '') + '">' + esc(label) + '</span>';
|
||||
// Colors land in a style attribute, so only literal hex colors pass through.
|
||||
const swatch = c => /^#[0-9a-f]{3,8}$/i.test(c || '')
|
||||
? '<span class="swatch" style="background:' + c + '"></span>' : '';
|
||||
const kv = rows => '<div class="kv">' + rows.map(([k, v]) =>
|
||||
'<div class="k">' + esc(k) + '</div><div class="v">' + v + '</div>').join('') + '</div>';
|
||||
const bboxRows = (label, bb) => bb ? [
|
||||
@@ -608,7 +665,8 @@ const bboxRows = (label, bb) => bb ? [
|
||||
/* ------------------------------------------------------- nav + fetching */
|
||||
function buildNav() {
|
||||
$('nav').innerHTML = SECTIONS.map(([key, glyph, label]) =>
|
||||
'<div class="item" id="nav-' + key + '" onclick="show(\'' + key + '\')">' +
|
||||
'<div class="item" id="nav-' + key + '" role="button" tabindex="0" ' +
|
||||
'onclick="show(\'' + key + '\')">' +
|
||||
'<span class="glyph">' + glyph + '</span>' + label + '</div>').join('');
|
||||
}
|
||||
function show(key) {
|
||||
@@ -622,9 +680,10 @@ function show(key) {
|
||||
function fetchSection(key) {
|
||||
S.busy[key] = true;
|
||||
$('content').innerHTML = '<p class="muted">Loading ' + esc(key) + '…</p>';
|
||||
orca.postMessage({ command:'fetch', section:key });
|
||||
orca.postMessage({ command:'fetch', section:key, gen:S.gen });
|
||||
}
|
||||
function refresh() {
|
||||
S.gen++; // replies to fetches from before this point are dropped
|
||||
S.cache = {};
|
||||
S.busy = {};
|
||||
show(S.current);
|
||||
@@ -660,13 +719,12 @@ function renderOverview(d) {
|
||||
[t.config_keys, 'config keys'],
|
||||
].map(([v, l]) => '<div class="tile"><div class="v">' + v + '</div><div class="l">' + l + '</div></div>').join('');
|
||||
|
||||
const filaments = d.setup.filaments.map((f, i) =>
|
||||
'<div style="margin:3px 0">' +
|
||||
(f.color ? '<span class="swatch" style="background:' + esc(f.color) + '"></span>' : '') +
|
||||
'<b>' + (i + 1) + '</b> ' + esc(f.name) +
|
||||
(f.type ? ' <span class="muted">' + esc(f.type) + '</span>' : '') +
|
||||
badge(f.is_system ? 'system' : 'user', f.is_system) +
|
||||
(f.is_dirty ? badge('modified', true) : '') +
|
||||
const filaments = d.setup.filaments.map(f =>
|
||||
'<div style="margin:3px 0">' + swatch(f.color) +
|
||||
'<b>' + f.slot + '</b> ' + esc(f.preset ? f.preset.name : '<missing preset>') +
|
||||
(f.material ? ' <span class="muted">' + esc(f.material) + '</span>' : '') +
|
||||
(f.preset ? badge(f.preset.is_system ? 'system' : 'user', f.preset.is_system) +
|
||||
(f.preset.is_dirty ? badge('modified', true) : '') : '') +
|
||||
'</div>').join('') || '<span class="muted">none</span>';
|
||||
|
||||
return '<div class="tiles">' + tiles + '</div><div class="cards">' +
|
||||
@@ -709,35 +767,65 @@ function presetBadges(p) {
|
||||
(p.is_visible ? '' : badge('hidden', false));
|
||||
}
|
||||
function renderPresets(d) {
|
||||
return '<div class="banner">Each card is one <span class="mono">host.PresetCollection</span>. ' +
|
||||
'“Edited” is the selected preset plus any unsaved changes. Pick any preset ' +
|
||||
'and load its complete config via <span class="mono">find_preset().config_value()</span>.</div>' +
|
||||
'<div class="cards">' + d.collections.map(c => {
|
||||
const p = c.edited;
|
||||
const options = c.names.map(n => // explicit value= so odd whitespace in names survives
|
||||
'<option value="' + esc(n) + '"' + (n === c.selected_name ? ' selected' : '') + '>' +
|
||||
const detailCard = g => {
|
||||
const p = g.edited;
|
||||
return '<div class="cards"><div class="card hued">' +
|
||||
'<div style="margin-bottom:6px"><b>' + esc(p.name) + '</b> ' + presetBadges(p) + '</div>' +
|
||||
kv([['label()', esc(p.label)], ['alias', esc(p.alias) || '—'],
|
||||
['type', esc(p.type)], ['bundle', esc(p.bundle_id) || '—'],
|
||||
['edited_preset()', p.config_key_count + ' keys' +
|
||||
(p.is_dirty ? ' · has unsaved changes' : ' · same as saved')],
|
||||
['selected_preset()', esc(g.selected.name) + ' · ' + g.selected.config_key_count + ' keys'],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'</div></div>';
|
||||
};
|
||||
const slotCard = (s, g) => {
|
||||
const p = s.preset;
|
||||
if (!p) return '<div class="card hued"><h3>Slot ' + s.slot + '</h3>' +
|
||||
'<p class="muted">no preset mounted</p></div>';
|
||||
return '<div class="card hued"><h3>Slot ' + s.slot + '</h3>' +
|
||||
'<div style="margin-bottom:6px">' + swatch(s.color) + '<b>' + esc(p.name) + '</b> ' +
|
||||
badge(p.is_system ? 'system' : 'user', p.is_system) +
|
||||
(p.is_dirty ? badge('modified', true) : '') +
|
||||
(p.name === g.selected_name ? badge('editing', true) : '') + '</div>' +
|
||||
kv([['material', esc(s.material) || '—'],
|
||||
['alias', esc(p.alias) || '—'],
|
||||
['config keys', p.config_key_count],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'<div class="frow" style="margin-top:8px"><button class="small secondary" ' +
|
||||
'data-act="pcfg" data-coll="filaments" data-name="' + esc(p.name) + '">View config</button></div>' +
|
||||
'</div>';
|
||||
};
|
||||
return '<div class="banner">Each group is one <span class="mono">host.PresetCollection</span>; ' +
|
||||
'the Filament group shows every slot from <span class="mono">current_filament_presets()</span>, ' +
|
||||
'and “editing” marks the preset open in its settings tab. Pick any preset in a group ' +
|
||||
'header — “View config” opens its complete config (via ' +
|
||||
'<span class="mono">find_preset().config_value()</span>) in its own window.</div>' +
|
||||
d.collections.map(g => {
|
||||
const options = g.names.map(n => // explicit value= so odd whitespace in names survives
|
||||
'<option value="' + esc(n) + '"' + (n === g.selected_name ? ' selected' : '') + '>' +
|
||||
esc(n) + '</option>').join('');
|
||||
return '<div class="card"><h3>' + esc(c.label) + ' · ' + c.size + ' presets</h3>' +
|
||||
'<div style="margin-bottom:6px"><b>' + esc(p.name) + '</b> ' + presetBadges(p) + '</div>' +
|
||||
kv([['label()', esc(p.label)], ['alias', esc(p.alias) || '—'],
|
||||
['type', esc(p.type)], ['bundle', esc(p.bundle_id) || '—'],
|
||||
['edited_preset()', p.config_key_count + ' keys' +
|
||||
(p.is_dirty ? ' · has unsaved changes' : ' · same as saved')],
|
||||
['selected_preset()', esc(c.selected.name) + ' · ' + c.selected.config_key_count + ' keys'],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'<div class="frow" style="margin-top:8px">' +
|
||||
'<select id="sel-' + c.key + '" style="flex:1;min-width:0">' + options + '</select>' +
|
||||
'<button class="small" data-act="pcfg" data-coll="' + c.key + '">View config</button>' +
|
||||
'</div>' +
|
||||
(c.truncated ? '<p class="muted">list capped, ' + c.truncated + ' more not shown</p>' : '') +
|
||||
'<div id="pcfg-' + c.key + '"></div></div>';
|
||||
}).join('') + '</div>';
|
||||
const cards = g.key === 'filaments'
|
||||
? '<div class="cards">' + (d.filament_slots.map(s => slotCard(s, g)).join('') ||
|
||||
'<p class="muted">no filament slots</p>') + '</div>'
|
||||
: detailCard(g);
|
||||
return '<div class="pgroup hue-' + g.key + '">' +
|
||||
'<div class="pgroup-head"><span class="dot"></span><h2>' + esc(g.label) + '</h2>' +
|
||||
'<span class="count">' + g.size + ' presets</span><span class="spacer"></span>' +
|
||||
'<select id="sel-' + g.key + '">' + options + '</select>' +
|
||||
'<button class="small" data-act="pcfg" data-coll="' + g.key + '">View config</button></div>' +
|
||||
cards +
|
||||
(g.truncated ? '<p class="muted">select capped, ' + g.truncated + ' more not listed</p>' : '') +
|
||||
// the config itself opens in a viewer window; this slot only shows failures
|
||||
'<div id="pcfg-' + g.key + '"></div></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function configTable(rows, id) {
|
||||
const body = rows.map(r => {
|
||||
const long = r.v.length > 160;
|
||||
const shown = long ? esc(r.v.slice(0, 160)) + '<span class="more" data-act="expand"> … show all</span>'
|
||||
const shown = long ? esc(r.v.slice(0, 160)) +
|
||||
'<span class="more" data-act="expand" role="button" tabindex="0"> … show all</span>'
|
||||
: esc(r.v);
|
||||
return '<tr data-k="' + esc(r.k.toLowerCase()) + '"><td>' + esc(r.k) + '</td>' +
|
||||
'<td class="val" data-full="' + esc(r.v) + '">' + shown + '</td></tr>';
|
||||
@@ -746,37 +834,36 @@ function configTable(rows, id) {
|
||||
}
|
||||
function renderConfig(d) {
|
||||
return '<div class="toolbar"><input id="cfg-search" placeholder="Filter ' + d.count +
|
||||
' keys…" oninput="filterConfig(this.value)">' +
|
||||
' keys…" oninput="filterTable(\'cfg-table\', this.value, \'cfg-count\')">' +
|
||||
'<span class="count" id="cfg-count">' + d.count + ' keys</span></div>' +
|
||||
'<div class="banner">The merged result of printer + process + filament presets — ' +
|
||||
'exactly what <span class="mono">preset_bundle().full_config_value(key)</span> returns for ' +
|
||||
'every key in <span class="mono">full_config_keys()</span>.</div>' +
|
||||
configTable(d.rows, 'cfg-table');
|
||||
}
|
||||
function filterConfig(q) {
|
||||
function filterTable(tableId, q, countId) {
|
||||
q = q.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
document.querySelectorAll('#cfg-table tr[data-k]').forEach(tr => {
|
||||
document.querySelectorAll('#' + tableId + ' tr[data-k]').forEach(tr => {
|
||||
const hit = !q || tr.dataset.k.includes(q) ||
|
||||
tr.querySelector('.val').dataset.full.toLowerCase().includes(q);
|
||||
tr.style.display = hit ? '' : 'none';
|
||||
if (hit) visible++;
|
||||
});
|
||||
$('cfg-count').textContent = visible + ' keys';
|
||||
if (countId) $(countId).textContent = visible + ' keys';
|
||||
}
|
||||
|
||||
function extruderChip(id) {
|
||||
if (id == null || id < 1) return '<span class="badge">extruder —</span>';
|
||||
const color = S.colors[id - 1];
|
||||
return '<span class="badge">' + (color ? '<span class="swatch" style="background:' +
|
||||
esc(color) + '"></span>' : '') + 'extruder ' + id + '</span>';
|
||||
return '<span class="badge">' + swatch(S.colors[id - 1]) + 'extruder ' + id + '</span>';
|
||||
}
|
||||
const VOL_GLYPH = { ModelPart:'◆', NegativeVolume:'◇', ParameterModifier:'▨',
|
||||
SupportEnforcer:'▲', SupportBlocker:'▽' };
|
||||
|
||||
function node(head, body, open) {
|
||||
return '<div class="node' + (open ? ' open' : '') + '">' +
|
||||
'<div class="nhead" data-act="toggle"><span class="twist">▶</span>' + head + '</div>' +
|
||||
'<div class="nhead" data-act="toggle" role="button" tabindex="0" aria-expanded="' +
|
||||
!!open + '"><span class="twist">▶</span>' + head + '</div>' +
|
||||
'<div class="nbody">' + body + '</div></div>';
|
||||
}
|
||||
function paintedBadges(p) {
|
||||
@@ -969,7 +1056,7 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
PREVIEWS[canvas.id] = { pts, ax, ay };
|
||||
const scaleDpr = window.devicePixelRatio || 1;
|
||||
const w = canvas.clientWidth * scaleDpr, h = canvas.clientHeight * scaleDpr;
|
||||
if (!w || !h) return; // inside a collapsed node; repainted on toggle
|
||||
if (!w || !h) { delete canvas.dataset.painted; return; } // collapsed; repainted on toggle
|
||||
canvas.dataset.painted = '1';
|
||||
canvas.width = w; canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -983,7 +1070,7 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
(h - 2 * pad) / Math.max(maxY - minY, 1e-6));
|
||||
const ox = (w - (maxX - minX) * scale) / 2, oy = (h - (maxY - minY) * scale) / 2;
|
||||
const style = getComputedStyle(document.body);
|
||||
ctx.strokeStyle = style.getPropertyValue('--orca-accent');
|
||||
ctx.strokeStyle = style.getPropertyValue('--accent');
|
||||
ctx.strokeRect(ox, oy, (maxX - minX) * scale, (maxY - minY) * scale);
|
||||
ctx.fillStyle = style.color;
|
||||
ctx.globalAlpha = 0.55;
|
||||
@@ -992,6 +1079,21 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
ctx.fillRect(ox + (p[ax] - minX) * scale - r / 2,
|
||||
h - oy - (p[ay] - minY) * scale - r / 2, r, r);
|
||||
}
|
||||
// A live theme switch re-stamps data-orca-theme and a resize leaves the bitmap at its
|
||||
// old size — both need every known preview repainted.
|
||||
function repaintPreviews() {
|
||||
document.querySelectorAll('canvas.preview').forEach(c => {
|
||||
const p = PREVIEWS[c.id];
|
||||
if (p) drawPoints(c, p.pts, p.ax, p.ay);
|
||||
});
|
||||
}
|
||||
new MutationObserver(repaintPreviews)
|
||||
.observe(document.documentElement, { attributes:true, attributeFilter:['data-orca-theme'] });
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(repaintPreviews, 150);
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------ UI toolkit */
|
||||
function renderUikit() {
|
||||
@@ -1052,6 +1154,7 @@ $('content').addEventListener('click', e => {
|
||||
if (act === 'toggle') {
|
||||
const node = t.parentElement;
|
||||
node.classList.toggle('open');
|
||||
t.setAttribute('aria-expanded', node.classList.contains('open'));
|
||||
// Repaint any preview whose canvas was hidden (zero-size) when its data arrived.
|
||||
node.querySelectorAll('canvas.preview').forEach(c => {
|
||||
const p = PREVIEWS[c.id];
|
||||
@@ -1062,9 +1165,10 @@ $('content').addEventListener('click', e => {
|
||||
t.textContent = 'Loading…';
|
||||
orca.postMessage({ command:'mesh', object:+t.dataset.o, volume:+t.dataset.v });
|
||||
} else if (act === 'pcfg') {
|
||||
const coll = t.dataset.coll;
|
||||
const coll = t.dataset.coll; // slot buttons carry their preset in data-name
|
||||
orca.postMessage({ command:'preset_config', collection:coll,
|
||||
name:$('sel-' + coll).value });
|
||||
name:t.dataset.name !== undefined ? t.dataset.name
|
||||
: $('sel-' + coll).value });
|
||||
} else if (act === 'expand') {
|
||||
const td = t.closest('td');
|
||||
td.textContent = td.dataset.full;
|
||||
@@ -1072,11 +1176,22 @@ $('content').addEventListener('click', e => {
|
||||
uiAction(t.dataset.ui);
|
||||
}
|
||||
});
|
||||
// Keyboard parity for the click-only elements (nav items, tree toggles, "show all").
|
||||
// Real form controls are skipped so they keep their native keys.
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key !== 'Enter' && e.key !== ' ') return;
|
||||
if (/^(BUTTON|INPUT|SELECT|TEXTAREA)$/.test(e.target.tagName)) return;
|
||||
const t = e.target.closest('.item, [data-act]');
|
||||
if (!t) return;
|
||||
e.preventDefault();
|
||||
t.click();
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------- messages */
|
||||
orca.onMessage(msg => {
|
||||
if (!msg || !msg.command) return;
|
||||
if (msg.command === 'section') {
|
||||
if (msg.gen !== S.gen) return; // raced a Refresh; the refetch is in flight
|
||||
S.cache[msg.section] = msg;
|
||||
S.busy[msg.section] = false;
|
||||
stamp();
|
||||
@@ -1085,14 +1200,15 @@ orca.onMessage(msg => {
|
||||
const container = $('mesh-' + msg.object + '-' + msg.volume);
|
||||
if (!container) return;
|
||||
if (msg.ok) renderMeshDetail(container, msg.data);
|
||||
else container.innerHTML = '<span class="muted">⚠ ' + esc(msg.error) + '</span>';
|
||||
else container.innerHTML =
|
||||
'<button class="small secondary" data-act="mesh" data-o="' + msg.object +
|
||||
'" data-v="' + msg.volume + '">Retry</button> <span class="muted">⚠ ' +
|
||||
esc(msg.error) + '</span>';
|
||||
} else if (msg.command === 'preset_config') {
|
||||
// Success opened a viewer window; this slot only shows (or clears) failures.
|
||||
const container = $('pcfg-' + msg.collection);
|
||||
if (!container) return;
|
||||
container.innerHTML = msg.ok
|
||||
? '<div class="subhead">' + esc(msg.name) + ' · ' + msg.data.rows.length +
|
||||
' keys</div>' + configTable(msg.data.rows, 'pcfg-table-' + msg.collection)
|
||||
: '<span class="muted">⚠ ' + esc(msg.error) + '</span>';
|
||||
container.innerHTML = msg.ok ? '' : '<p class="muted">⚠ ' + esc(msg.error) + '</p>';
|
||||
} else if (msg.command === 'ui_result') {
|
||||
addLog(msg.ok ? '← ' + msg.action + ': ' + msg.result
|
||||
: '← ' + msg.action + ' failed: ' + msg.error);
|
||||
@@ -1110,28 +1226,33 @@ show('overview');
|
||||
# The modal page demoed from the UI Toolkit tab: create_window(style=WINDOW_MODAL)
|
||||
# keeps the handle alive for callbacks; orca.submit(payload) invokes on_submit.
|
||||
MODAL_PAGE = r"""<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"></head>
|
||||
<body style="padding:18px">
|
||||
<h3 style="margin-top:0">Modal dialog</h3>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + r"""
|
||||
body { padding:18px; }
|
||||
h3 { margin-top:0; }
|
||||
input { width:100%; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h3>Modal dialog</h3>
|
||||
<p>create_window(style=WINDOW_MODAL) keeps this page interactive until it submits or closes.</p>
|
||||
<p><input id="note" style="width:100%" value="hello from the modal"></p>
|
||||
<p><input id="note" value="hello from the modal"></p>
|
||||
<p style="text-align:right">
|
||||
<button style="background:transparent;color:var(--orca-fg);border-color:var(--orca-border)"
|
||||
onclick="orca.postMessage({live: document.getElementById('note').value})">Post while open</button>
|
||||
<button style="background:transparent;color:var(--orca-fg);border-color:var(--orca-border)"
|
||||
onclick="orca.close()">Cancel</button>
|
||||
<button class="secondary" onclick="orca.postMessage({live: document.getElementById('note').value})">Post while open</button>
|
||||
<button class="secondary" onclick="orca.close()">Cancel</button>
|
||||
<button onclick="orca.submit({note: document.getElementById('note').value})">Submit</button>
|
||||
</p>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
CHILD_PAGE = r"""<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"></head>
|
||||
<body style="padding:18px">
|
||||
<h3 style="margin-top:0">Child window</h3>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + r"""
|
||||
body { padding:18px; }
|
||||
h3 { margin-top:0; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h3>Child window</h3>
|
||||
<p>Same create_window() API — one plugin, several windows.</p>
|
||||
<p><button onclick="orca.postMessage({command:'ping'})">Ping the plugin</button></p>
|
||||
<div id="log" style="color:var(--orca-muted)"></div>
|
||||
<div id="log" style="color:var(--muted)"></div>
|
||||
<script>
|
||||
var n = 0;
|
||||
orca.onMessage(function (msg) {
|
||||
@@ -1142,6 +1263,75 @@ CHILD_PAGE = r"""<!DOCTYPE html>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
# The per-preset config viewer, opened by "View config" on the Presets tab. Its rows are
|
||||
# baked in as JSON at build time, so the window needs no bridge traffic and stays usable
|
||||
# side by side with the panel until closed.
|
||||
CONFIG_PAGE = r"""<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + CFG_TABLE_CSS + r"""
|
||||
body { padding:12px 16px 20px; border-top:3px solid var(--hue, var(--accent)); }
|
||||
.toolbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px; }
|
||||
.toolbar h3 { margin:0; font-size:13px; }
|
||||
.toolbar input { flex:1; min-width:140px; }
|
||||
.dot { display:inline-block; width:9px; height:9px; border-radius:50%; flex:none;
|
||||
background:var(--hue, var(--accent)); }
|
||||
.count { color:var(--muted); font-size:12px; white-space:nowrap; }
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="toolbar"><span class="dot"></span><h3 id="name"></h3>
|
||||
<span class="count" id="count"></span>
|
||||
<input id="q" placeholder="Filter keys…">
|
||||
<button class="secondary" onclick="orca.close()">Close</button></div>
|
||||
<div id="table"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
const DATA = __DATA__;
|
||||
const $ = id => document.getElementById(id);
|
||||
const esc = s => String(s == null ? '' : s)
|
||||
.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
if (DATA.hue) document.body.style.setProperty('--hue', DATA.hue);
|
||||
$('name').textContent = DATA.name;
|
||||
$('table').innerHTML = '<table class="cfg" id="t"><tr><th>key</th><th>value</th></tr>' +
|
||||
DATA.rows.map(r => {
|
||||
const long = r.v.length > 160;
|
||||
const shown = long ? esc(r.v.slice(0, 160)) +
|
||||
'<span class="more" role="button" tabindex="0"> … show all</span>' : esc(r.v);
|
||||
return '<tr data-k="' + esc(r.k.toLowerCase()) + '"><td>' + esc(r.k) + '</td>' +
|
||||
'<td class="val" data-full="' + esc(r.v) + '">' + shown + '</td></tr>';
|
||||
}).join('') + '</table>';
|
||||
function filter(q) {
|
||||
q = q.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
document.querySelectorAll('#t tr[data-k]').forEach(tr => {
|
||||
const hit = !q || tr.dataset.k.includes(q) ||
|
||||
tr.querySelector('.val').dataset.full.toLowerCase().includes(q);
|
||||
tr.style.display = hit ? '' : 'none';
|
||||
if (hit) visible++;
|
||||
});
|
||||
$('count').textContent = visible + ' keys';
|
||||
}
|
||||
$('q').addEventListener('input', e => filter(e.target.value));
|
||||
$('table').addEventListener('click', e => {
|
||||
const t = e.target.closest('.more');
|
||||
if (t) { const td = t.closest('td'); td.textContent = td.dataset.full; }
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && e.target.classList.contains('more')) {
|
||||
e.preventDefault();
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
filter('');
|
||||
</script>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
def config_page(collection, name, rows):
|
||||
"""CONFIG_PAGE with one preset's rows baked in. `</` is escaped so a config
|
||||
value containing e.g. `</script>` cannot break out of the script block."""
|
||||
payload = json.dumps({"name": name, "hue": HUES.get(collection, ""), "rows": rows})
|
||||
return CONFIG_PAGE.replace("__DATA__", payload.replace("</", "<\\/"))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# the plugin
|
||||
@@ -1150,6 +1340,7 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
win = None
|
||||
child = None
|
||||
modal = None
|
||||
cfgwin = None
|
||||
|
||||
def get_name(self):
|
||||
return "Orca Inspector"
|
||||
@@ -1166,6 +1357,9 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.modal is not None:
|
||||
self.modal.close()
|
||||
self.modal = None
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = None
|
||||
# Non-modal: returns immediately. The window is host-owned and lives on
|
||||
# after execute() returns; on_message keeps firing when the page posts.
|
||||
self.win = orca.host.ui.create_window(
|
||||
@@ -1184,11 +1378,11 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
msg = msg or {}
|
||||
command = msg.get("command")
|
||||
if command == "fetch":
|
||||
self.send_section(msg.get("section", ""))
|
||||
self.send_section(msg.get("section", ""), msg.get("gen"))
|
||||
elif command == "mesh":
|
||||
self.send_mesh(int(msg.get("object", -1)), int(msg.get("volume", -1)))
|
||||
self.send_mesh(msg.get("object", -1), msg.get("volume", -1))
|
||||
elif command == "preset_config":
|
||||
self.send_preset_config(msg.get("collection", ""), msg.get("name", ""))
|
||||
self.open_preset_config(msg.get("collection", ""), msg.get("name", ""))
|
||||
elif command == "ui":
|
||||
self.run_ui_action(msg)
|
||||
|
||||
@@ -1196,11 +1390,15 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.child is not None:
|
||||
self.child.close()
|
||||
self.child = None
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = None
|
||||
print("Orca Inspector closed")
|
||||
|
||||
def send_section(self, section):
|
||||
def send_section(self, section, gen=None):
|
||||
builder = SECTION_BUILDERS.get(section)
|
||||
reply = {"command": "section", "section": section}
|
||||
# gen echoes the page's refresh counter so a reply that raced a Refresh is dropped there.
|
||||
reply = {"command": "section", "section": section, "gen": gen}
|
||||
if builder is None:
|
||||
self.win.post({**reply, "ok": False, "error": f"unknown section {section!r}"})
|
||||
return
|
||||
@@ -1213,15 +1411,23 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
reply = {"command": "mesh", "object": object_index, "volume": volume_index}
|
||||
try:
|
||||
self.win.post({**reply, "ok": True,
|
||||
"data": build_mesh(object_index, volume_index)})
|
||||
"data": build_mesh(int(object_index), int(volume_index))})
|
||||
except Exception as exc:
|
||||
self.win.post({**reply, "ok": False, "error": str(exc)})
|
||||
|
||||
def send_preset_config(self, collection, name):
|
||||
def open_preset_config(self, collection, name):
|
||||
# One viewer at a time: a new pick replaces the previous window. The main page
|
||||
# only hears back about failures.
|
||||
reply = {"command": "preset_config", "collection": collection, "name": name}
|
||||
try:
|
||||
self.win.post({**reply, "ok": True,
|
||||
"data": build_preset_config(collection, name)})
|
||||
rows = build_preset_config(collection, name)
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = orca.host.ui.create_window(
|
||||
title=f"Orca Inspector — {name}",
|
||||
html=config_page(collection, name, rows),
|
||||
width=520, height=640)
|
||||
self.win.post({**reply, "ok": True})
|
||||
except Exception as exc:
|
||||
self.win.post({**reply, "ok": False, "error": str(exc)})
|
||||
|
||||
@@ -1267,15 +1473,19 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.child is not None and self.child.is_open():
|
||||
report("child already open")
|
||||
else:
|
||||
# Not `reply`: the close can also come from "child_close" later, so the
|
||||
# on_close payload is labelled neutrally.
|
||||
self.child = orca.host.ui.create_window(
|
||||
title="Orca Inspector — child", html=CHILD_PAGE,
|
||||
width=380, height=240, on_message=self.on_child_message,
|
||||
on_close=lambda: self.win.post(
|
||||
{**reply, "ok": True, "result": "child window closed"}))
|
||||
{"command": "ui_result", "action": "child",
|
||||
"ok": True, "result": "child window closed"}))
|
||||
report("child opened")
|
||||
elif action == "child_close":
|
||||
if self.child is not None:
|
||||
self.child.close()
|
||||
report("close requested") # the actual close is logged by on_close
|
||||
else:
|
||||
report("no child window")
|
||||
elif action == "child_state":
|
||||
@@ -0,0 +1,618 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# name = "Twistify"
|
||||
# description = "Twists, tapers, and wobbles every layer's slice polygons as a function of Z (demo)."
|
||||
# author = "SoftFever"
|
||||
# version = "0.03"
|
||||
# type = "slicing-pipeline"
|
||||
# ///
|
||||
"""Twistify -- twist/taper/wobble any model at slice time.
|
||||
|
||||
At Step.posSlice, every layer's sliced surfaces are transformed by a similarity
|
||||
about the object's bounding-box center as a function of Z -- edited IN PLACE
|
||||
through the host geometry classes (ExPolygon.rotate/scale/translate). Each
|
||||
surface is rotated about the center, then (if tapering) translated to the
|
||||
origin, uniformly scaled, and translated back, so the taper stays centered on
|
||||
the object instead of drifting toward the coordinate origin. An optional X
|
||||
wobble is applied last. After the per-region edits, layer.make_slices()
|
||||
re-derives the layer's merged islands so overhang/bridge/skirt/support stay
|
||||
coherent. The split slice loop runs make_perimeters() right after the hook, so
|
||||
the transform cascades into perimeters, infill, and the final G-code -- the
|
||||
preview corkscrews and the print keeps correct walls/infill/flow.
|
||||
|
||||
Because we edit geometry in place, surface types are preserved automatically
|
||||
(no per-surface type carry needed), and no numpy is required --
|
||||
rotate/scale/translate are host methods. Parameters come from self.get_config()
|
||||
(see _params below); the host seeds them from get_default_config() the first
|
||||
time the plugin loads. The first object layer is untouched (z_rel = 0), so bed
|
||||
adhesion is unaffected.
|
||||
|
||||
The plugin also ships its own configuration UI (has_config_ui/get_config_ui):
|
||||
a self-contained page the Plugins dialog renders instead of the JSON editor,
|
||||
drawing the transform it is about to apply as an isometric stack of layer
|
||||
outlines. It reaches the host only through the injected window.orca bridge --
|
||||
getConfig/saveConfig/restoreDefaults/getContext/onConfig/onTheme -- and styles
|
||||
itself from the --orca-* theme variables the host hands the frame, so it
|
||||
follows OrcaSlicer's light/dark theme.
|
||||
"""
|
||||
import math
|
||||
import json
|
||||
import orca
|
||||
|
||||
_DEFAULTS = {
|
||||
"twist_deg_per_mm": 1.0,
|
||||
"taper_per_mm": 0.0,
|
||||
"wobble_ampl_mm": 0.0,
|
||||
"wobble_period_mm": 20.0,
|
||||
"min_scale": 0.05,
|
||||
}
|
||||
|
||||
|
||||
def _params(self):
|
||||
try:
|
||||
src = json.loads(self.get_config())
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
src = {}
|
||||
out = {}
|
||||
for key, default in _DEFAULTS.items():
|
||||
try:
|
||||
out[key] = float(src[key])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
out[key] = default
|
||||
return out
|
||||
|
||||
|
||||
def _is_identity(p):
|
||||
return p["twist_deg_per_mm"] == 0.0 and p["taper_per_mm"] == 0.0 and p["wobble_ampl_mm"] == 0.0
|
||||
|
||||
|
||||
def _layer_params(z_rel, mm_to_scaled, p):
|
||||
"""(angle_rad, scale, x_offset_scaled) for one layer. Exact identity at z_rel == 0."""
|
||||
theta = math.radians(p["twist_deg_per_mm"] * z_rel)
|
||||
s = max(p["min_scale"], 1.0 + p["taper_per_mm"] * z_rel)
|
||||
ox = 0.0
|
||||
if p["wobble_ampl_mm"] != 0.0 and p["wobble_period_mm"] > 0.0:
|
||||
ox = p["wobble_ampl_mm"] * math.sin(2.0 * math.pi * z_rel / p["wobble_period_mm"]) * mm_to_scaled
|
||||
return theta, s, ox
|
||||
|
||||
|
||||
# The configuration page. Self-contained on purpose: it runs in an iframe sandboxed into an opaque
|
||||
# origin, so it has only the window.orca bridge and the --orca-* theme variables the host injects --
|
||||
# no network, no same-origin access, no shared stylesheet. get_config_ui() substitutes _DEFAULTS for
|
||||
# __ORCA_DEFAULTS__, so Python owns the values and the page adds only presentation.
|
||||
_CONFIG_UI = """
|
||||
<style>
|
||||
:root {
|
||||
--ink: var(--orca-fg, #1f2429);
|
||||
--paper: var(--orca-bg, #ffffff);
|
||||
--rule: var(--orca-border, #d9dee3);
|
||||
--quiet: var(--orca-muted, #6b7580);
|
||||
--live: var(--orca-accent, #009688);
|
||||
--live-ink: var(--orca-accent-fg, #ffffff);
|
||||
--ui: var(--orca-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
||||
--data: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--ink: var(--orca-fg, #e4e6e8);
|
||||
--paper: var(--orca-bg, #2b2d30);
|
||||
--rule: var(--orca-border, #3d4043);
|
||||
--quiet: var(--orca-muted, #9aa0a6);
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font: 13px/1.45 var(--ui);
|
||||
}
|
||||
.panel { display: flex; flex-direction: column; height: 100%; }
|
||||
|
||||
.head {
|
||||
display: flex; align-items: baseline; gap: 10px;
|
||||
padding: 12px 16px 10px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
.mark {
|
||||
font: 10px/1 var(--data); letter-spacing: .14em; text-transform: uppercase;
|
||||
color: var(--live);
|
||||
}
|
||||
.head-note { font-size: 11px; color: var(--quiet); }
|
||||
|
||||
/* The frame is as narrow as ~400px in a resized dialog and as wide as ~600px; below the
|
||||
breakpoint the drawing moves under the controls rather than squeezing both. */
|
||||
.work {
|
||||
flex: 1; min-height: 0; overflow: auto;
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) 180px; gap: 16px;
|
||||
align-content: start; padding: 14px 16px;
|
||||
}
|
||||
@media (max-width: 470px) {
|
||||
.work { grid-template-columns: minmax(0, 1fr); }
|
||||
.plot { max-width: 210px; }
|
||||
}
|
||||
|
||||
.rows { display: flex; flex-direction: column; gap: 12px; }
|
||||
.row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 8px; }
|
||||
.row-label {
|
||||
grid-column: 1;
|
||||
font: 10px/1 var(--data); letter-spacing: .12em; text-transform: uppercase;
|
||||
color: var(--quiet);
|
||||
}
|
||||
.row-figure { grid-column: 2; display: flex; align-items: baseline; gap: 4px; }
|
||||
.row-figure input {
|
||||
/* No spinners: the rail is the coarse control, this is the exact one, and the arrows would
|
||||
steal the width the value needs. */
|
||||
-webkit-appearance: textfield; appearance: textfield;
|
||||
width: 76px; padding: 3px 6px; text-align: right;
|
||||
font: 13px/1.2 var(--data); font-variant-numeric: tabular-nums;
|
||||
color: var(--ink); background: transparent;
|
||||
border: 1px solid transparent; border-radius: 3px;
|
||||
}
|
||||
.row-figure input::-webkit-outer-spin-button,
|
||||
.row-figure input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.row-figure input:hover { border-color: var(--rule); }
|
||||
.row-figure input:focus { outline: none; border-color: var(--live); }
|
||||
.unit { font: 10px/1 var(--data); color: var(--quiet); min-width: 26px; }
|
||||
|
||||
/* The rail carries a detent at the value that means "no deformation", so neutral is a position
|
||||
you can feel for rather than a number you have to read. */
|
||||
.rail { grid-column: 1 / -1; position: relative; height: 16px; }
|
||||
.rail[data-detent]::before {
|
||||
content: ""; position: absolute; left: var(--detent); top: 3px;
|
||||
width: 1px; height: 10px; background: var(--rule);
|
||||
}
|
||||
.rail input {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
position: relative; width: 100%; height: 16px; margin: 0; background: transparent;
|
||||
}
|
||||
.rail input::-webkit-slider-runnable-track { height: 2px; background: var(--rule); border-radius: 1px; }
|
||||
.rail input::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 11px; height: 11px; margin-top: -4.5px;
|
||||
border: none; border-radius: 50%; background: var(--live); cursor: grab;
|
||||
}
|
||||
.rail input:focus { outline: none; }
|
||||
.rail input:focus-visible { outline: 2px solid var(--live); outline-offset: 1px; border-radius: 3px; }
|
||||
.rail input:disabled::-webkit-slider-thumb { background: var(--quiet); cursor: default; }
|
||||
|
||||
.readout {
|
||||
margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--rule);
|
||||
font-size: 11px; color: var(--quiet); min-height: 32px;
|
||||
}
|
||||
.readout b { color: var(--ink); font-weight: 600; font-family: var(--data); font-variant-numeric: tabular-nums; }
|
||||
|
||||
.plot { margin: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.plot svg { width: 100%; height: auto; display: block; }
|
||||
.plot figcaption { font: 10px/1.4 var(--data); color: var(--quiet); }
|
||||
/* SVG colours live here rather than in stroke="" attributes: var() is not reliable in a
|
||||
presentation attribute, and this way the drawing re-themes with the rest of the page. */
|
||||
.ring, .helix, .datum { fill: none; }
|
||||
.ring { stroke: var(--live); stroke-width: 1; }
|
||||
.helix { stroke: var(--live); stroke-width: 1; stroke-opacity: .5; stroke-dasharray: 2 2; }
|
||||
.datum { stroke: var(--quiet); stroke-width: 1; stroke-opacity: .7; }
|
||||
.tick { stroke: var(--rule); stroke-width: 1; }
|
||||
.plot text { fill: var(--quiet); font: 8px var(--data); }
|
||||
|
||||
.foot {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 16px; border-top: 1px solid var(--rule);
|
||||
}
|
||||
.state { flex: 1; font-size: 11px; color: var(--quiet); }
|
||||
.state.dirty { color: var(--ink); }
|
||||
button {
|
||||
font: 12px/1 var(--ui); padding: 6px 14px; border-radius: 4px; cursor: pointer;
|
||||
border: 1px solid var(--rule); background: transparent; color: var(--ink);
|
||||
}
|
||||
button.go { border-color: var(--live); background: var(--live); color: var(--live-ink); }
|
||||
button:disabled { opacity: .45; cursor: default; }
|
||||
button:focus-visible { outline: 2px solid var(--live); outline-offset: 2px; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
||||
</style>
|
||||
|
||||
<div class="panel">
|
||||
<header class="head">
|
||||
<span class="mark">Twistify</span>
|
||||
<span class="head-note">Applied to every layer as it is sliced</span>
|
||||
</header>
|
||||
|
||||
<div class="work">
|
||||
<div>
|
||||
<div class="rows" id="rows"></div>
|
||||
<p class="readout" id="readout"></p>
|
||||
</div>
|
||||
|
||||
<figure class="plot">
|
||||
<svg id="plot" viewBox="0 0 180 208" aria-hidden="true"></svg>
|
||||
<figcaption id="plotNote"></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<footer class="foot">
|
||||
<span class="state" id="state"></span>
|
||||
<button type="button" id="restore">Restore defaults</button>
|
||||
<button type="button" id="save" class="go">Save</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var DEFAULTS = __ORCA_DEFAULTS__;
|
||||
|
||||
// Presentation only. `neutral` marks the value at which the control contributes no deformation;
|
||||
// rows without one (period, floor) are shape parameters, not amounts.
|
||||
var FIELDS = [
|
||||
{ key: "twist_deg_per_mm", label: "Twist", unit: "\\u00b0/mm", min: -20, max: 20, step: 0.1, dp: 1, neutral: 0,
|
||||
hint: "Rotation added per millimetre of height." },
|
||||
{ key: "taper_per_mm", label: "Taper", unit: "/mm", min: -0.05, max: 0.05, step: 0.001, dp: 3, neutral: 0,
|
||||
hint: "Scale added per millimetre. Negative narrows the model as it rises." },
|
||||
{ key: "wobble_ampl_mm", label: "Wobble", unit: "mm", min: 0, max: 10, step: 0.1, dp: 1, neutral: 0,
|
||||
hint: "How far each layer slides in X, from centre to peak." },
|
||||
{ key: "wobble_period_mm", label: "Wobble period", unit: "mm", min: 1, max: 120, step: 1, dp: 0,
|
||||
hint: "Height of one full wobble cycle." },
|
||||
{ key: "min_scale", label: "Scale floor", unit: "\\u00d7", min: 0.01, max: 1, step: 0.01, dp: 2,
|
||||
hint: "Taper never shrinks a layer past this, so a tall model cannot collapse." }
|
||||
];
|
||||
|
||||
// The preview simulates this much height. It never reads the real model, so the caption says so
|
||||
// rather than implying the drawing is to scale with the plate.
|
||||
var Z_SPAN = 40;
|
||||
var Z_STEP = 2;
|
||||
|
||||
var form = {};
|
||||
var stored = {};
|
||||
var context = (window.orca && window.orca.getContext) ? window.orca.getContext() : { scope: "global" };
|
||||
var savedNoteTimer = 0;
|
||||
|
||||
function num(value, fallback) {
|
||||
var n = parseFloat(value);
|
||||
return isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function merged(config) {
|
||||
var out = {};
|
||||
Object.keys(DEFAULTS).forEach(function (key) {
|
||||
out[key] = num(config ? config[key] : undefined, DEFAULTS[key]);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function isDirty() {
|
||||
return Object.keys(DEFAULTS).some(function (key) { return form[key] !== stored[key]; });
|
||||
}
|
||||
|
||||
// --- the transform, exactly as _layer_params() applies it -------------------------------------
|
||||
|
||||
function layerAt(z) {
|
||||
var theta = form.twist_deg_per_mm * z * Math.PI / 180;
|
||||
var scale = Math.max(form.min_scale, 1 + form.taper_per_mm * z);
|
||||
var shift = 0;
|
||||
if (form.wobble_ampl_mm !== 0 && form.wobble_period_mm > 0)
|
||||
shift = form.wobble_ampl_mm * Math.sin(2 * Math.PI * z / form.wobble_period_mm);
|
||||
return { theta: theta, scale: scale, shift: shift };
|
||||
}
|
||||
|
||||
function deforms() {
|
||||
return form.twist_deg_per_mm !== 0 || form.taper_per_mm !== 0 || form.wobble_ampl_mm !== 0;
|
||||
}
|
||||
|
||||
// --- drawing ----------------------------------------------------------------------------------
|
||||
|
||||
var ISO_X = 0.866, ISO_Y = 0.5; // isometric: plan outlines stacked by height
|
||||
var ORIGIN_X = 108, ORIGIN_Y = 182; // the z = 0 outline's centre, in viewBox units
|
||||
var PLAN = 20; // half-width of the sample contour
|
||||
var Z_UNITS = 3.3; // viewBox units per millimetre of height
|
||||
var MM = 1.5; // viewBox units per millimetre of wobble travel
|
||||
var REACH = 62; // how far sideways the drawing may run before it is scaled down
|
||||
|
||||
// Plan-view corners of the layer at `z`, before projection.
|
||||
function planOutline(z) {
|
||||
var layer = layerAt(z);
|
||||
var half = PLAN * layer.scale;
|
||||
var cos = Math.cos(layer.theta), sin = Math.sin(layer.theta);
|
||||
return [[-half, -half], [half, -half], [half, half], [-half, half]].map(function (corner) {
|
||||
return [corner[0] * cos - corner[1] * sin + layer.shift * MM,
|
||||
corner[0] * sin + corner[1] * cos];
|
||||
});
|
||||
}
|
||||
|
||||
function points(plan, z, fit) {
|
||||
return plan.map(function (p) {
|
||||
var x = ORIGIN_X + (p[0] - p[1]) * ISO_X * fit;
|
||||
var y = ORIGIN_Y + (p[0] + p[1]) * ISO_Y * fit - z * Z_UNITS;
|
||||
return x.toFixed(1) + "," + y.toFixed(1);
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var svg = [];
|
||||
var layers = [];
|
||||
var reach = 0;
|
||||
for (var z = 0; z <= Z_SPAN + 0.001; z += Z_STEP) {
|
||||
var plan = planOutline(z);
|
||||
layers.push({ z: z, plan: plan });
|
||||
plan.forEach(function (p) { reach = Math.max(reach, Math.abs(p[0] - p[1]) * ISO_X); });
|
||||
}
|
||||
// A wide taper or a big wobble would run out of the frame, so scale down to fit rather than
|
||||
// clipping. Z keeps its scale, so the ruler stays true.
|
||||
var fit = reach > REACH ? REACH / reach : 1;
|
||||
|
||||
// Z ruler: the drawing is a height plot, so it gets an axis.
|
||||
for (var mark = 0; mark <= Z_SPAN; mark += 10) {
|
||||
var y = ORIGIN_Y - mark * Z_UNITS;
|
||||
svg.push('<line class="tick" x1="24" y1="' + y.toFixed(1) + '" x2="30" y2="' + y.toFixed(1) + '"/>');
|
||||
svg.push('<text x="20" y="' + (y + 3).toFixed(1) + '" text-anchor="end">' + mark + '</text>');
|
||||
}
|
||||
svg.push('<text x="30" y="' + (ORIGIN_Y - Z_SPAN * Z_UNITS - 11).toFixed(1) +
|
||||
'" text-anchor="end">mm</text>');
|
||||
|
||||
// The stack, faint at the bed and full strength at the top: the deformation grows with Z.
|
||||
var trace = [];
|
||||
layers.forEach(function (layer) {
|
||||
var ring = points(layer.plan, layer.z, fit);
|
||||
svg.push('<polygon class="ring" points="' + ring + '" stroke-opacity="' +
|
||||
(0.18 + 0.82 * layer.z / Z_SPAN).toFixed(2) + '"/>');
|
||||
trace.push(ring.split(" ")[1]);
|
||||
});
|
||||
|
||||
// One corner followed all the way up: on a twisted model, the helix the walls will run in.
|
||||
svg.push('<polyline class="helix" points="' + trace.join(" ") + '"/>');
|
||||
|
||||
// The datum: the first layer is never transformed, so it doubles as the reference outline.
|
||||
svg.push('<polygon class="datum" points="' + points(layers[0].plan, 0, fit) + '"/>');
|
||||
|
||||
document.getElementById("plot").innerHTML = svg.join("");
|
||||
document.getElementById("plotNote").textContent =
|
||||
deforms() ? "Sample contour, " + Z_SPAN + " mm of Z" : "Sample contour, undeformed";
|
||||
}
|
||||
|
||||
// --- readout ----------------------------------------------------------------------------------
|
||||
|
||||
function describe() {
|
||||
if (!deforms())
|
||||
return "No deformation. Twistify passes every layer through unchanged.";
|
||||
|
||||
var top = layerAt(Z_SPAN);
|
||||
var parts = [];
|
||||
if (form.twist_deg_per_mm !== 0)
|
||||
parts.push("<b>" + (form.twist_deg_per_mm * Z_SPAN).toFixed(0) + "\\u00b0</b> of rotation");
|
||||
if (form.taper_per_mm !== 0)
|
||||
parts.push("<b>" + top.scale.toFixed(2) + "\\u00d7</b> scale" +
|
||||
(top.scale === form.min_scale ? " (at the floor)" : ""));
|
||||
if (form.wobble_ampl_mm !== 0)
|
||||
parts.push("<b>\\u00b1" + form.wobble_ampl_mm.toFixed(1) + "</b> mm of wobble");
|
||||
return "At " + Z_SPAN + " mm above the first layer: " + parts.join(", ") + ".";
|
||||
}
|
||||
|
||||
function setReadout(html) { document.getElementById("readout").innerHTML = html; }
|
||||
|
||||
// Whatever the pointer or the keyboard is on explains itself; with neither, report the totals.
|
||||
function restoreReadout() {
|
||||
var id = document.activeElement ? document.activeElement.id : "";
|
||||
for (var i = 0; i < FIELDS.length; i++)
|
||||
if (id === "n-" + FIELDS[i].key || id === "r-" + FIELDS[i].key)
|
||||
return setReadout(FIELDS[i].hint);
|
||||
setReadout(describe());
|
||||
}
|
||||
|
||||
function refreshState() {
|
||||
var dirty = isDirty();
|
||||
document.getElementById("save").disabled = context.readOnly || !dirty;
|
||||
|
||||
if (savedNoteTimer)
|
||||
return; // the "Saved" note owns the line until it expires
|
||||
var state = document.getElementById("state");
|
||||
state.textContent = context.readOnly ? "This configuration is read-only."
|
||||
: dirty ? "Unsaved changes" : "";
|
||||
state.classList.toggle("dirty", dirty && !context.readOnly);
|
||||
}
|
||||
|
||||
function announce(message) {
|
||||
var state = document.getElementById("state");
|
||||
state.textContent = message;
|
||||
state.classList.remove("dirty");
|
||||
clearTimeout(savedNoteTimer);
|
||||
savedNoteTimer = setTimeout(function () { savedNoteTimer = 0; refreshState(); }, 2200);
|
||||
}
|
||||
|
||||
// --- controls ---------------------------------------------------------------------------------
|
||||
|
||||
function detent(field) {
|
||||
if (field.neutral === undefined) return "";
|
||||
return ((field.neutral - field.min) / (field.max - field.min) * 100).toFixed(2) + "%";
|
||||
}
|
||||
|
||||
function clamp(field, value) { return Math.min(field.max, Math.max(field.min, value)); }
|
||||
|
||||
// `echo` rewrites the number field with the clamped, formatted value. Off while the user is still
|
||||
// typing: "-" and "0.0" are valid partial input, and rewriting them mid-keystroke fights the
|
||||
// typist. The drawing still follows every keystroke.
|
||||
function apply(field, value, echo) {
|
||||
form[field.key] = clamp(field, value);
|
||||
if (echo)
|
||||
document.getElementById("n-" + field.key).value = form[field.key].toFixed(field.dp);
|
||||
document.getElementById("r-" + field.key).value = form[field.key];
|
||||
draw();
|
||||
// A control explains itself while you approach it, but once a value moves what matters is the
|
||||
// total it adds up to.
|
||||
setReadout(describe());
|
||||
refreshState();
|
||||
}
|
||||
|
||||
function buildRows() {
|
||||
var host = document.getElementById("rows");
|
||||
FIELDS.forEach(function (field) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "row";
|
||||
// The number input is the labelled, keyboard-reachable control; the rail is the same value by
|
||||
// pointer, so it stays out of the tab order and the accessibility tree.
|
||||
row.innerHTML =
|
||||
'<label class="row-label" for="n-' + field.key + '">' + field.label + '</label>' +
|
||||
'<span class="row-figure">' +
|
||||
'<input id="n-' + field.key + '" type="number" step="' + field.step + '" ' +
|
||||
'min="' + field.min + '" max="' + field.max + '">' +
|
||||
'<span class="unit">' + field.unit + '</span>' +
|
||||
'</span>' +
|
||||
'<span class="rail"' + (field.neutral === undefined ? "" : ' data-detent style="--detent:' + detent(field) + '"') + '>' +
|
||||
'<input id="r-' + field.key + '" type="range" step="' + field.step + '" ' +
|
||||
'min="' + field.min + '" max="' + field.max + '" tabindex="-1" aria-hidden="true">' +
|
||||
'</span>';
|
||||
host.appendChild(row);
|
||||
|
||||
var number = row.querySelector("#n-" + field.key);
|
||||
var range = row.querySelector("#r-" + field.key);
|
||||
number.addEventListener("input", function () { apply(field, num(number.value, form[field.key]), false); });
|
||||
number.addEventListener("change", function () { apply(field, num(number.value, DEFAULTS[field.key]), true); });
|
||||
range.addEventListener("input", function () { apply(field, num(range.value, form[field.key]), true); });
|
||||
|
||||
[number, range].forEach(function (input) {
|
||||
input.addEventListener("focus", function () { setReadout(field.hint); });
|
||||
input.addEventListener("mouseenter", function () { setReadout(field.hint); });
|
||||
input.addEventListener("blur", restoreReadout);
|
||||
input.addEventListener("mouseleave", restoreReadout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function load(config) {
|
||||
stored = merged(config);
|
||||
form = merged(config);
|
||||
FIELDS.forEach(function (field) {
|
||||
document.getElementById("n-" + field.key).value = form[field.key].toFixed(field.dp);
|
||||
document.getElementById("r-" + field.key).value = form[field.key];
|
||||
});
|
||||
draw();
|
||||
restoreReadout();
|
||||
refreshState();
|
||||
labelRestore();
|
||||
setInputsEnabled(!context.readOnly);
|
||||
}
|
||||
|
||||
// "Restore defaults" writes the plugin's own defaults globally, but in a preset it drops that
|
||||
// preset's override and falls back to the global configuration. Say which one the button does.
|
||||
function labelRestore() {
|
||||
var restore = document.getElementById("restore");
|
||||
var preset = context.scope === "preset";
|
||||
restore.textContent = preset ? "Use global settings" : "Restore defaults";
|
||||
restore.title = preset ? "Discard this preset's override and use the global configuration"
|
||||
: "Replace the saved configuration with Twistify's defaults";
|
||||
restore.disabled = context.readOnly || (preset && !context.hasPresetOverride);
|
||||
}
|
||||
|
||||
function setInputsEnabled(enabled) {
|
||||
FIELDS.forEach(function (field) {
|
||||
document.getElementById("n-" + field.key).disabled = !enabled;
|
||||
document.getElementById("r-" + field.key).disabled = !enabled;
|
||||
});
|
||||
}
|
||||
|
||||
buildRows();
|
||||
load(window.orca ? window.orca.getConfig() : {});
|
||||
|
||||
document.getElementById("save").addEventListener("click", function () {
|
||||
if (!window.orca) return;
|
||||
window.orca.saveConfig(form);
|
||||
});
|
||||
document.getElementById("restore").addEventListener("click", function () {
|
||||
if (window.orca) window.orca.restoreDefaults();
|
||||
});
|
||||
|
||||
if (window.orca && window.orca.onConfig) {
|
||||
var first = true;
|
||||
window.orca.onConfig(function (config) {
|
||||
// Fires once immediately (already handled above) and then on every host save: reload from
|
||||
// what was stored, never from what was typed.
|
||||
if (first) { first = false; return; }
|
||||
if (window.orca.getContext) context = window.orca.getContext();
|
||||
load(config);
|
||||
announce("Saved");
|
||||
});
|
||||
}
|
||||
// No theme handler needed: every colour on the page, the drawing included, resolves through the
|
||||
// --orca-* variables the host rewrites in place.
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
class Twistify(orca.slicing.SlicingPipelineCapabilityBase):
|
||||
def get_name(self):
|
||||
return "Twistify"
|
||||
|
||||
def get_default_config(self):
|
||||
return _DEFAULTS
|
||||
|
||||
def has_config_ui(self):
|
||||
return True
|
||||
|
||||
def get_config_ui(self):
|
||||
return _CONFIG_UI.replace("__ORCA_DEFAULTS__", json.dumps(_DEFAULTS))
|
||||
|
||||
def execute(self, ctx):
|
||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||
return orca.ExecutionResult.success()
|
||||
|
||||
p = _params(self)
|
||||
if _is_identity(p):
|
||||
return orca.ExecutionResult.success("Twistify: identity parameters, nothing to do")
|
||||
|
||||
mm_to_scaled = 1.0 / orca.slicing.unscale(1)
|
||||
|
||||
layers = ctx.object.layers()
|
||||
if not layers:
|
||||
return orca.ExecutionResult.success("Twistify: object has no layers")
|
||||
|
||||
# Twist/taper axis = the object's bounding-box center (scaled coords, same frame
|
||||
# as the slice polygons), so each object on the plate transforms about its own
|
||||
# center. Keep the float center for translate-to-origin/back around scale(), and
|
||||
# a rounded-to-Point center for rotate() (which takes an integer Point).
|
||||
min_x, min_y, max_x, max_y = ctx.object.bounding_box()
|
||||
cx = (min_x + max_x) / 2.0
|
||||
cy = (min_y + max_y) / 2.0
|
||||
center = orca.host.Point(int(round(cx)), int(round(cy)))
|
||||
z0 = float(layers[0].print_z) # z_rel = 0 on the first layer -> footprint untouched
|
||||
|
||||
layers_touched = 0
|
||||
for layer in layers:
|
||||
if ctx.cancelled():
|
||||
break
|
||||
z_rel = float(layer.print_z) - z0
|
||||
theta, s, ox = _layer_params(z_rel, mm_to_scaled, p)
|
||||
if theta == 0.0 and s == 1.0 and ox == 0.0:
|
||||
continue # exact identity (always the first layer)
|
||||
|
||||
edited = False
|
||||
for region in layer.regions():
|
||||
for surface in region.slices.surfaces:
|
||||
ex = surface.expolygon
|
||||
ex.rotate(theta, center) # rotate about the object center (in place)
|
||||
if s != 1.0:
|
||||
# scale() scales about the coordinate ORIGIN, so re-center the
|
||||
# geometry on the origin first and translate back after, making
|
||||
# this a true similarity transform about the object's center.
|
||||
ex.translate(-cx, -cy)
|
||||
ex.scale(s)
|
||||
ex.translate(cx, cy)
|
||||
if ox != 0.0:
|
||||
ex.translate(ox, 0.0) # wobble in X
|
||||
edited = True
|
||||
if edited:
|
||||
# Re-derive the merged islands from the twisted region slices.
|
||||
layer.make_slices()
|
||||
layers_touched += 1
|
||||
|
||||
name = ctx.object.model_object().name or "object"
|
||||
return orca.ExecutionResult.success(
|
||||
f"Twistify: transformed {layers_touched} layer(s) of '{name}' "
|
||||
f"(twist {p['twist_deg_per_mm']} deg/mm, taper {p['taper_per_mm']}/mm, "
|
||||
f"wobble {p['wobble_ampl_mm']} mm)")
|
||||
|
||||
|
||||
@orca.plugin
|
||||
class TwistifyPackage(orca.base):
|
||||
def register_capabilities(self):
|
||||
orca.register_capability(Twistify)
|
||||
@@ -1,142 +0,0 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# name = "Twistify"
|
||||
# description = "Twists, tapers, and wobbles every layer's slice polygons as a function of Z (demo)."
|
||||
# author = "OrcaSlicer"
|
||||
# version = "0.02"
|
||||
# type = "slicing-pipeline"
|
||||
# ///
|
||||
"""Twistify -- twist/taper/wobble any model at slice time.
|
||||
|
||||
At Step.posSlice, every layer's sliced surfaces are transformed by a similarity
|
||||
about the object's bounding-box center as a function of Z -- edited IN PLACE
|
||||
through the host geometry classes (ExPolygon.rotate/scale/translate). Each
|
||||
surface is rotated about the center, then (if tapering) translated to the
|
||||
origin, uniformly scaled, and translated back, so the taper stays centered on
|
||||
the object instead of drifting toward the coordinate origin. An optional X
|
||||
wobble is applied last. After the per-region edits, layer.make_slices()
|
||||
re-derives the layer's merged islands so overhang/bridge/skirt/support stay
|
||||
coherent. The split slice loop runs make_perimeters() right after the hook, so
|
||||
the transform cascades into perimeters, infill, and the final G-code -- the
|
||||
preview corkscrews and the print keeps correct walls/infill/flow.
|
||||
|
||||
Because we edit geometry in place, surface types are preserved automatically
|
||||
(no per-surface type carry needed), and no numpy is required --
|
||||
rotate/scale/translate are host methods. Parameters come from self.get_config()
|
||||
(see _params below), which the host seeds from get_default_config(). The first
|
||||
object layer is untouched (z_rel = 0), so bed adhesion is unaffected.
|
||||
"""
|
||||
import math
|
||||
import json
|
||||
import orca
|
||||
|
||||
_DEFAULTS = {
|
||||
"twist_deg_per_mm": 1.0,
|
||||
"taper_per_mm": 0.0,
|
||||
"wobble_ampl_mm": 0.0,
|
||||
"wobble_period_mm": 20.0,
|
||||
"min_scale": 0.05,
|
||||
}
|
||||
|
||||
|
||||
def _params(self):
|
||||
try:
|
||||
src = json.loads(self.get_config())
|
||||
except (AttributeError, TypeError):
|
||||
src = {}
|
||||
out = {}
|
||||
for key, default in _DEFAULTS.items():
|
||||
try:
|
||||
out[key] = float(src[key])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
out[key] = default
|
||||
return out
|
||||
|
||||
|
||||
def _is_identity(p):
|
||||
return p["twist_deg_per_mm"] == 0.0 and p["taper_per_mm"] == 0.0 and p["wobble_ampl_mm"] == 0.0
|
||||
|
||||
|
||||
def _layer_params(z_rel, mm_to_scaled, p):
|
||||
"""(angle_rad, scale, x_offset_scaled) for one layer. Exact identity at z_rel == 0."""
|
||||
theta = math.radians(p["twist_deg_per_mm"] * z_rel)
|
||||
s = max(p["min_scale"], 1.0 + p["taper_per_mm"] * z_rel)
|
||||
ox = 0.0
|
||||
if p["wobble_ampl_mm"] != 0.0 and p["wobble_period_mm"] > 0.0:
|
||||
ox = p["wobble_ampl_mm"] * math.sin(2.0 * math.pi * z_rel / p["wobble_period_mm"]) * mm_to_scaled
|
||||
return theta, s, ox
|
||||
|
||||
|
||||
class Twistify(orca.slicing.SlicingPipelineCapabilityBase):
|
||||
def get_name(self):
|
||||
return "Twistify"
|
||||
|
||||
def get_default_config(self):
|
||||
return _DEFAULTS
|
||||
|
||||
def execute(self, ctx):
|
||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||
return orca.ExecutionResult.success()
|
||||
|
||||
p = _params(self)
|
||||
if _is_identity(p):
|
||||
return orca.ExecutionResult.success("Twistify: identity parameters, nothing to do")
|
||||
|
||||
mm_to_scaled = 1.0 / orca.slicing.unscale(1)
|
||||
|
||||
layers = ctx.object.layers()
|
||||
if not layers:
|
||||
return orca.ExecutionResult.success("Twistify: object has no layers")
|
||||
|
||||
# Twist/taper axis = the object's bounding-box center (scaled coords, same frame
|
||||
# as the slice polygons), so each object on the plate transforms about its own
|
||||
# center. Keep the float center for translate-to-origin/back around scale(), and
|
||||
# a rounded-to-Point center for rotate() (which takes an integer Point).
|
||||
min_x, min_y, max_x, max_y = ctx.object.bounding_box()
|
||||
cx = (min_x + max_x) / 2.0
|
||||
cy = (min_y + max_y) / 2.0
|
||||
center = orca.host.Point(int(round(cx)), int(round(cy)))
|
||||
z0 = float(layers[0].print_z) # z_rel = 0 on the first layer -> footprint untouched
|
||||
|
||||
layers_touched = 0
|
||||
for layer in layers:
|
||||
if ctx.cancelled():
|
||||
break
|
||||
z_rel = float(layer.print_z) - z0
|
||||
theta, s, ox = _layer_params(z_rel, mm_to_scaled, p)
|
||||
if theta == 0.0 and s == 1.0 and ox == 0.0:
|
||||
continue # exact identity (always the first layer)
|
||||
|
||||
edited = False
|
||||
for region in layer.regions():
|
||||
for surface in region.slices.surfaces:
|
||||
ex = surface.expolygon
|
||||
ex.rotate(theta, center) # rotate about the object center (in place)
|
||||
if s != 1.0:
|
||||
# scale() scales about the coordinate ORIGIN, so re-center the
|
||||
# geometry on the origin first and translate back after, making
|
||||
# this a true similarity transform about the object's center.
|
||||
ex.translate(-cx, -cy)
|
||||
ex.scale(s)
|
||||
ex.translate(cx, cy)
|
||||
if ox != 0.0:
|
||||
ex.translate(ox, 0.0) # wobble in X
|
||||
edited = True
|
||||
if edited:
|
||||
# Re-derive the merged islands from the twisted region slices.
|
||||
layer.make_slices()
|
||||
layers_touched += 1
|
||||
|
||||
name = ctx.object.model_object().name or "object"
|
||||
return orca.ExecutionResult.success(
|
||||
f"Twistify: transformed {layers_touched} layer(s) of '{name}' "
|
||||
f"(twist {p['twist_deg_per_mm']} deg/mm, taper {p['taper_per_mm']}/mm, "
|
||||
f"wobble {p['wobble_ampl_mm']} mm)")
|
||||
|
||||
|
||||
@orca.plugin
|
||||
class TwistifyPackage(orca.base):
|
||||
def register_capabilities(self):
|
||||
orca.register_capability(Twistify)
|
||||
@@ -23,7 +23,7 @@ if %FULL_MODE%==1 (
|
||||
call :prepareGettextList "%list_file%" "%filtered_list%" "%missing_list%"
|
||||
if "!has_sources!"=="1" (
|
||||
if not exist "%generated_i18n%" mkdir "%generated_i18n%"
|
||||
.\tools\xgettext.exe --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_CTX:1,2c --keyword=_CTX_utf8:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "%filtered_list%" -o "%generated_pot%"
|
||||
.\tools\xgettext.exe --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "%filtered_list%" -o "%generated_pot%"
|
||||
if errorlevel 1 (
|
||||
set "script_exit_code=1"
|
||||
) else (
|
||||
|
||||
@@ -85,7 +85,7 @@ if $FULL_MODE; then
|
||||
generated_pot_file="${generated_i18n_dir}/OrcaSlicer.pot"
|
||||
|
||||
mkdir -p "$generated_i18n_dir"
|
||||
xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_CTX:1,2c --keyword=_CTX_utf8:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "$filtered_list" -o "$generated_pot_file"
|
||||
xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "$filtered_list" -o "$generated_pot_file"
|
||||
python3 scripts/HintsToPot.py ./resources "$generated_i18n_dir"
|
||||
|
||||
if [ -f "$pot_file" ] && files_equal_ignoring_pot_date "$pot_file" "$generated_pot_file"; then
|
||||
|
||||
@@ -283,10 +283,6 @@ else ()
|
||||
COMMAND ln -sf OrcaSlicer orca-slicer
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
else ()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:OrcaSlicer>"
|
||||
VERBATIM)
|
||||
endif ()
|
||||
if (XCODE)
|
||||
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
|
||||
|
||||
+20
-3
@@ -24,6 +24,8 @@
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
#include <condition_variable>
|
||||
@@ -7608,6 +7610,9 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
|
||||
}*/
|
||||
|
||||
#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" {
|
||||
__declspec(dllexport) int __stdcall orcaslicer_main(int argc, wchar_t **argv)
|
||||
{
|
||||
@@ -7626,10 +7631,22 @@ extern "C" {
|
||||
//AddVectoredExceptionHandler(1, CBaseException::UnhandledExceptionFilter);
|
||||
SET_DEFULTER_HANDLER();
|
||||
#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([]() {
|
||||
int *a = nullptr;
|
||||
*a = 0;
|
||||
});
|
||||
if (!g_dump_in_progress.exchange(true)) {
|
||||
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.
|
||||
return CLI().run(argc, argv_ptrs.data());
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Loader generated by glad 2.0.8 on Tue Apr 7 00:45:10 2026
|
||||
* Loader generated by glad 2.0.8 on Thu Jul 16 03:42:05 2026
|
||||
*
|
||||
* SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0
|
||||
*
|
||||
* Generator: C/C++
|
||||
* Specification: gl
|
||||
* Extensions: 5
|
||||
* Extensions: 7
|
||||
*
|
||||
* APIs:
|
||||
* - gl:compatibility=4.6
|
||||
@@ -19,10 +19,10 @@
|
||||
* - ON_DEMAND = False
|
||||
*
|
||||
* Commandline:
|
||||
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
|
||||
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_multisample,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
|
||||
*
|
||||
* Online:
|
||||
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
|
||||
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_blit%2CGL_EXT_framebuffer_multisample%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -616,6 +616,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_DRAW_BUFFER9 0x882E
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6
|
||||
#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9
|
||||
#define GL_DRAW_INDIRECT_BUFFER 0x8F3F
|
||||
#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43
|
||||
#define GL_DRAW_PIXEL_TOKEN 0x0705
|
||||
@@ -746,6 +748,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC
|
||||
#define GL_FRAMEBUFFER_RENDERABLE 0x8289
|
||||
@@ -1106,6 +1109,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
|
||||
#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8
|
||||
#define GL_MAX_SAMPLES 0x8D57
|
||||
#define GL_MAX_SAMPLES_EXT 0x8D57
|
||||
#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59
|
||||
#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
|
||||
#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE
|
||||
@@ -1400,6 +1404,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_READ_BUFFER 0x0C02
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
|
||||
#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA
|
||||
#define GL_READ_FRAMEBUFFER_EXT 0x8CA8
|
||||
#define GL_READ_ONLY 0x88B8
|
||||
#define GL_READ_PIXELS 0x828C
|
||||
#define GL_READ_PIXELS_FORMAT 0x828D
|
||||
@@ -1437,6 +1443,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
|
||||
#define GL_RENDERBUFFER_RED_SIZE 0x8D50
|
||||
#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50
|
||||
#define GL_RENDERBUFFER_SAMPLES 0x8CAB
|
||||
#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55
|
||||
#define GL_RENDERBUFFER_WIDTH 0x8D42
|
||||
@@ -2150,6 +2157,10 @@ GLAD_API_CALL int GLAD_GL_VERSION_4_6;
|
||||
GLAD_API_CALL int GLAD_GL_ARB_compatibility;
|
||||
#define GL_ARB_framebuffer_object 1
|
||||
GLAD_API_CALL int GLAD_GL_ARB_framebuffer_object;
|
||||
#define GL_EXT_framebuffer_blit 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_blit;
|
||||
#define GL_EXT_framebuffer_multisample 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_multisample;
|
||||
#define GL_EXT_framebuffer_object 1
|
||||
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_object;
|
||||
#define GL_EXT_texture_compression_s3tc 1
|
||||
@@ -2205,6 +2216,7 @@ typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenu
|
||||
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEIPROC)(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCIPROC)(GLuint buf, GLenum src, GLenum dst);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFEREXTPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBLITNAMEDFRAMEBUFFERPROC)(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
typedef void (GLAD_API_PTR *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void * data, GLenum usage);
|
||||
typedef void (GLAD_API_PTR *PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void * data, GLbitfield flags);
|
||||
@@ -2881,6 +2893,7 @@ typedef GLint (GLAD_API_PTR *PFNGLRENDERMODEPROC)(GLenum mode);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEEXTPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (GLAD_API_PTR *PFNGLRESUMETRANSFORMFEEDBACKPROC)(void);
|
||||
typedef void (GLAD_API_PTR *PFNGLROTATEDPROC)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
|
||||
typedef void (GLAD_API_PTR *PFNGLROTATEFPROC)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
|
||||
@@ -3318,6 +3331,8 @@ GLAD_API_CALL PFNGLBLENDFUNCIPROC glad_glBlendFunci;
|
||||
#define glBlendFunci glad_glBlendFunci
|
||||
GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer;
|
||||
#define glBlitFramebuffer glad_glBlitFramebuffer
|
||||
GLAD_API_CALL PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT;
|
||||
#define glBlitFramebufferEXT glad_glBlitFramebufferEXT
|
||||
GLAD_API_CALL PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer;
|
||||
#define glBlitNamedFramebuffer glad_glBlitNamedFramebuffer
|
||||
GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData;
|
||||
@@ -4670,6 +4685,8 @@ GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT;
|
||||
#define glRenderbufferStorageEXT glad_glRenderbufferStorageEXT
|
||||
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample;
|
||||
#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample
|
||||
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT;
|
||||
#define glRenderbufferStorageMultisampleEXT glad_glRenderbufferStorageMultisampleEXT
|
||||
GLAD_API_CALL PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback;
|
||||
#define glResumeTransformFeedback glad_glResumeTransformFeedback
|
||||
GLAD_API_CALL PFNGLROTATEDPROC glad_glRotated;
|
||||
|
||||
+22
-1
@@ -44,6 +44,8 @@ int GLAD_GL_VERSION_4_5 = 0;
|
||||
int GLAD_GL_VERSION_4_6 = 0;
|
||||
int GLAD_GL_ARB_compatibility = 0;
|
||||
int GLAD_GL_ARB_framebuffer_object = 0;
|
||||
int GLAD_GL_EXT_framebuffer_blit = 0;
|
||||
int GLAD_GL_EXT_framebuffer_multisample = 0;
|
||||
int GLAD_GL_EXT_framebuffer_object = 0;
|
||||
int GLAD_GL_EXT_texture_compression_s3tc = 0;
|
||||
int GLAD_GL_EXT_texture_filter_anisotropic = 0;
|
||||
@@ -97,6 +99,7 @@ PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;
|
||||
PFNGLBLENDFUNCSEPARATEIPROC glad_glBlendFuncSeparatei = NULL;
|
||||
PFNGLBLENDFUNCIPROC glad_glBlendFunci = NULL;
|
||||
PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;
|
||||
PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT = NULL;
|
||||
PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer = NULL;
|
||||
PFNGLBUFFERDATAPROC glad_glBufferData = NULL;
|
||||
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
|
||||
@@ -773,6 +776,7 @@ PFNGLRENDERMODEPROC glad_glRenderMode = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;
|
||||
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT = NULL;
|
||||
PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL;
|
||||
PFNGLROTATEDPROC glad_glRotated = NULL;
|
||||
PFNGLROTATEFPROC glad_glRotatef = NULL;
|
||||
@@ -2249,6 +2253,14 @@ static void glad_gl_load_GL_ARB_framebuffer_object( GLADuserptrloadfunc load, vo
|
||||
glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) load(userptr, "glRenderbufferStorage");
|
||||
glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) load(userptr, "glRenderbufferStorageMultisample");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_blit( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_blit) return;
|
||||
glad_glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC) load(userptr, "glBlitFramebufferEXT");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_multisample( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_multisample) return;
|
||||
glad_glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) load(userptr, "glRenderbufferStorageMultisampleEXT");
|
||||
}
|
||||
static void glad_gl_load_GL_EXT_framebuffer_object( GLADuserptrloadfunc load, void* userptr) {
|
||||
if(!GLAD_GL_EXT_framebuffer_object) return;
|
||||
glad_glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) load(userptr, "glBindFramebufferEXT");
|
||||
@@ -2366,6 +2378,8 @@ static int glad_gl_find_extensions_gl(void) {
|
||||
|
||||
GLAD_GL_ARB_compatibility = glad_gl_has_extension(exts, exts_i, "GL_ARB_compatibility");
|
||||
GLAD_GL_ARB_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_ARB_framebuffer_object");
|
||||
GLAD_GL_EXT_framebuffer_blit = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_blit");
|
||||
GLAD_GL_EXT_framebuffer_multisample = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_multisample");
|
||||
GLAD_GL_EXT_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_object");
|
||||
GLAD_GL_EXT_texture_compression_s3tc = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_compression_s3tc");
|
||||
GLAD_GL_EXT_texture_filter_anisotropic = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_filter_anisotropic");
|
||||
@@ -2451,6 +2465,8 @@ int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) {
|
||||
|
||||
if (!glad_gl_find_extensions_gl()) return 0;
|
||||
glad_gl_load_GL_ARB_framebuffer_object(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_blit(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_multisample(load, userptr);
|
||||
glad_gl_load_GL_EXT_framebuffer_object(load, userptr);
|
||||
|
||||
|
||||
@@ -2574,7 +2590,9 @@ static void* glad_gl_dlopen_handle(void) {
|
||||
"libGL-1.so",
|
||||
#endif
|
||||
"libGL.so.1",
|
||||
"libGL.so"
|
||||
"libGL.so",
|
||||
"libEGL.so.1",
|
||||
"libEGL.so"
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -2597,6 +2615,9 @@ static struct _glad_gl_userptr glad_gl_build_userptr(void *handle) {
|
||||
#else
|
||||
userptr.gl_get_proc_address_ptr =
|
||||
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "glXGetProcAddressARB");
|
||||
if (!userptr.gl_get_proc_address_ptr)
|
||||
userptr.gl_get_proc_address_ptr =
|
||||
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "eglGetProcAddress");
|
||||
#endif
|
||||
|
||||
return userptr;
|
||||
|
||||
@@ -202,6 +202,10 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
if (get("preview_dim_previous_layers").empty())
|
||||
set_bool("preview_dim_previous_layers", false);
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public:
|
||||
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
|
||||
{
|
||||
if (from == to)
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor.");
|
||||
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointType::Scalar>();
|
||||
|
||||
@@ -19,6 +19,7 @@ if (TARGET OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
|
||||
option(USE_SLIC3R_CONSOLE_LOG "Enable console logging in RelWithDebInfo builds" OFF)
|
||||
|
||||
set(lisbslic3r_sources
|
||||
AABBMesh.cpp
|
||||
@@ -499,8 +500,12 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
# CGAL's config resets policies (cmake_minimum_required ...3.23), so a plain SET
|
||||
# can't reach it; the default opts its Boost lookup into BoostConfig (CMP0167).
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0167 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
find_package(OpenCV REQUIRED core)
|
||||
unset(CMAKE_POLICY_DEFAULT_CMP0167)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC
|
||||
@@ -537,7 +542,9 @@ endif ()
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
|
||||
if (USE_SLIC3R_CONSOLE_LOG)
|
||||
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
|
||||
endif()
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
@@ -547,7 +554,7 @@ find_package(OpenCASCADE REQUIRED)
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${OpenCASCADE_INCLUDE_DIR})
|
||||
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(draco REQUIRED)
|
||||
find_package(Draco REQUIRED)
|
||||
|
||||
set(OCCT_LIBS
|
||||
TKXDESTEP
|
||||
|
||||
@@ -57,6 +57,9 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
if (template_string.empty()) {
|
||||
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;
|
||||
ConfigOptionFloats rotate_angles;
|
||||
const std::string search_string = "/NnZz$LlUuQq~^|#";
|
||||
@@ -75,6 +78,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
double angle_start = 0;
|
||||
double limit_fill_z = object->get_layer(0)->bottom_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;
|
||||
auto fill_form = std::string::npos;
|
||||
bool _absolute = false;
|
||||
@@ -84,7 +89,8 @@ double calculate_infill_rotation_angle(const PrintObject* object,
|
||||
for (int i = 0; i <= layer_id; i++) {
|
||||
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
|
||||
limit_fill_z += limit_fill_z - start_fill_z;
|
||||
start_fill_z = fill_z;
|
||||
|
||||
@@ -61,7 +61,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
{
|
||||
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d ")) % opt_key % dependent_opt_key).str());
|
||||
throw FlowErrorMissingVariable((boost::format("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d.") % opt_key % dependent_opt_key).str());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
@@ -129,7 +129,7 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
|
||||
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
|
||||
{
|
||||
if (height <= 0)
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width().");
|
||||
|
||||
float w;
|
||||
if (!width.percent && width.value <= 0.) {
|
||||
@@ -157,7 +157,7 @@ Flow Flow::with_spacing(float new_spacing) const
|
||||
assert(m_width >= m_height);
|
||||
out.m_width += new_spacing - m_spacing;
|
||||
if (out.m_width < out.m_height)
|
||||
throw Slic3r::InvalidArgument(L("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"));
|
||||
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width.");
|
||||
}
|
||||
out.m_spacing = new_spacing;
|
||||
return out;
|
||||
|
||||
+81
-6
@@ -732,6 +732,42 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
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
|
||||
// start_pos refers to the last position before the wipe_tower.
|
||||
// end_pos refers to the wipe tower's start_pos.
|
||||
@@ -3317,7 +3353,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
|
||||
BoundingBoxf bbox;
|
||||
auto pts = std::make_unique<ConfigOptionPoints>();
|
||||
if (print.calib_mode() == CalibMode::Calib_PA_Line || print.calib_mode() == CalibMode::Calib_PA_Pattern) {
|
||||
if (print.calib_mode() == CalibMode::Calib_PA_Pattern) {
|
||||
//PA_Pattern can have any size or arrangement - not dependent on 3mf model size
|
||||
bbox = bbox_bed;
|
||||
bbox.offset(-25.0);
|
||||
// add 4 corner points of bbox into pts
|
||||
@@ -3327,6 +3364,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
|
||||
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
|
||||
|
||||
} else if (print.calib_mode() == CalibMode::Calib_PA_Line) {
|
||||
// Derive X bounds from the actual calibration geometry.
|
||||
CalibPressureAdvanceLine temp_pa_line_forsize(this);
|
||||
BoundingBoxf pattern_extents = temp_pa_line_forsize.print_extents(bbox_bed);
|
||||
|
||||
bbox = bbox_bed;
|
||||
bbox.offset(-25.0);
|
||||
bbox.min.x() = std::max(pattern_extents.min.x(), bbox.min.x());
|
||||
bbox.max.x() = std::min(pattern_extents.max.x(), bbox.max.x());
|
||||
|
||||
pts->values.reserve(4);
|
||||
pts->values.emplace_back(bbox.min.x(), bbox.min.y());
|
||||
pts->values.emplace_back(bbox.max.x(), bbox.min.y());
|
||||
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
|
||||
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
|
||||
|
||||
} else {
|
||||
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
@@ -4455,6 +4508,11 @@ PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
|
||||
ppi.update_from_gcodewriter(m_writer);
|
||||
std::string output = ppi.parser.process(templ, current_filament_id, config_override, &ppi.output_config, &ppi.context);
|
||||
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) {
|
||||
// Update G-code writer.
|
||||
@@ -5791,10 +5849,17 @@ LayerResult GCode::process_layer(
|
||||
for (const auto &layer_to_print : layers) {
|
||||
if (layer_to_print.object_layer) {
|
||||
const auto& regions = layer_to_print.object_layer->regions();
|
||||
const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [this](const LayerRegion* r) {
|
||||
const bool has_extrusions = std::any_of(regions.begin(), regions.end(), [](const LayerRegion* r) {
|
||||
return r->has_extrusions();
|
||||
});
|
||||
const bool enable_overhang_speed = std::any_of(regions.begin(), regions.end(), [this](const LayerRegion* r) {
|
||||
return r->has_extrusions() && r->region().config().enable_overhang_speed.get_at(get_nozzle_config_index(m_writer.filament()->id()));
|
||||
});
|
||||
if (enable_overhang_speed) {
|
||||
const bool enable_overhang_fan = m_enable_cooling_markers && has_extrusions &&
|
||||
std::any_of(m_config.enable_overhang_bridge_fan.values.begin(),
|
||||
m_config.enable_overhang_bridge_fan.values.end(),
|
||||
[](unsigned char value) { return value != 0; });
|
||||
if (enable_overhang_speed || enable_overhang_fan) {
|
||||
m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.original_object,
|
||||
layer_to_print.object_layer);
|
||||
}
|
||||
@@ -7633,7 +7698,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
bool variable_speed = false;
|
||||
std::vector<ProcessedPoint> new_points {};
|
||||
|
||||
if (NOZZLE_CONFIG(enable_overhang_speed) && !this->on_first_layer() && !object_layer_over_raft() &&
|
||||
const bool need_overhang_detection = NOZZLE_CONFIG(enable_overhang_speed) ||
|
||||
(FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers);
|
||||
|
||||
if (need_overhang_detection && !this->on_first_layer() && !object_layer_over_raft() &&
|
||||
(is_bridge(path.role()) || is_perimeter(path.role()))) {
|
||||
bool is_external = is_external_perimeter(path.role());
|
||||
double ref_speed = is_external ? NOZZLE_CONFIG(outer_wall_speed) : NOZZLE_CONFIG(inner_wall_speed);
|
||||
@@ -7692,6 +7760,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
}
|
||||
variable_speed = std::any_of(new_points.begin(), new_points.end(),
|
||||
[speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
|
||||
if (!NOZZLE_CONFIG(enable_overhang_speed) && FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers) {
|
||||
for (ProcessedPoint &point : new_points)
|
||||
point.speed = speed;
|
||||
variable_speed = new_points.size() > 1;
|
||||
}
|
||||
}
|
||||
|
||||
double F = speed * 60; // convert mm/sec to mm/min
|
||||
@@ -7700,7 +7773,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
||||
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
|
||||
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
|
||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
|
||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
|
||||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
|
||||
bool evaluate_adaptive_pa = false;
|
||||
bool role_change = (m_last_extrusion_role != path.role());
|
||||
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
|
||||
@@ -9292,7 +9365,7 @@ std::string GCode::set_object_info(Print *print) {
|
||||
std::ostringstream gcode;
|
||||
size_t object_id = 0;
|
||||
// Orca: check if we are in pa calib mode
|
||||
if (print->calib_mode() == CalibMode::Calib_PA_Line || print->calib_mode() == CalibMode::Calib_PA_Pattern) {
|
||||
if (print->calib_mode() == CalibMode::Calib_PA_Pattern) {
|
||||
BoundingBoxf bbox_bed(print->config().printable_area.values);
|
||||
bbox_bed.offset(-25.0);
|
||||
Polygon polygon_bed;
|
||||
@@ -9303,6 +9376,8 @@ std::string GCode::set_object_info(Print *print) {
|
||||
gcode << "EXCLUDE_OBJECT_DEFINE NAME="
|
||||
<< "Orca-PA-Calibration-Test"
|
||||
<< " CENTER=" << 0 << "," << 0 << " POLYGON=" << polygon_to_string(polygon_bed, print, true) << "\n";
|
||||
} else if (print->calib_mode() == CalibMode::Calib_PA_Line) {
|
||||
// PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed
|
||||
} else {
|
||||
size_t unique_id = 0;
|
||||
for (PrintObject* object : print->objects()) {
|
||||
|
||||
@@ -844,7 +844,10 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
ironing_fan_control = false; // ORCA: Add support for ironing fan speed control
|
||||
ironing_fan_speed = 0; // ORCA: Add support for ironing fan speed control
|
||||
}
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
// A tool change may keep the same configured base fan speed while the physical fan is
|
||||
// still running at the previous filament's overhang speed. Restore the base speed before
|
||||
// emitting G-code for the new tool in that case.
|
||||
if (fan_speed_new != m_fan_speed || (immediately_apply && m_current_fan_speed != fan_speed_new)) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
if (immediately_apply)
|
||||
@@ -1040,8 +1043,10 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
|
||||
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
|
||||
}
|
||||
else
|
||||
else {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
|
||||
m_current_fan_speed = m_fan_speed;
|
||||
}
|
||||
need_set_fan = false;
|
||||
}
|
||||
pos = line_end;
|
||||
|
||||
@@ -614,6 +614,8 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
|
||||
float leftover = 0.0f;
|
||||
for (size_t i = additional_buffer_idx; i < additional_buffer.size(); ++i)
|
||||
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);
|
||||
gcode_time.cache += leftover;
|
||||
} else {
|
||||
@@ -3492,6 +3494,7 @@ void GCodeProcessor::reset()
|
||||
m_extruder_blocks.clear();
|
||||
m_machine_start_gcode_end_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_line_id = 0;
|
||||
@@ -4219,6 +4222,14 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
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
|
||||
// (eg. Happy Hare) in the filament consumption stats.
|
||||
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;
|
||||
if (line.has_value('S', value_s) || line.has_value('P', value_p)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1109,6 +1109,10 @@ class Print;
|
||||
std::vector<ExtruderPreHeating::ExtruderUsageBlcok> m_extruder_blocks;
|
||||
unsigned int m_machine_start_gcode_end_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
|
||||
// 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
|
||||
|
||||
@@ -1091,6 +1091,16 @@ namespace Slic3r
|
||||
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);
|
||||
|
||||
// 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;
|
||||
float tmp_cost = 0;
|
||||
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)
|
||||
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_nozzle_change_perimeter_width = 2*m_perimeter_width;
|
||||
// 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_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))
|
||||
.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,
|
||||
(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.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;
|
||||
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);
|
||||
tool_change.nozzle_change_depth = nozzle_change_depth;
|
||||
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";
|
||||
}
|
||||
|
||||
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
|
||||
// Return index of first toolchange that switches to non-soluble and non-support extruder
|
||||
// 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)) {
|
||||
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;
|
||||
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 nozzle_change_speed = 60.0f * m_filpar[m_current_tool].max_e_speed / nz_extrusion_flow;
|
||||
nozzle_change_speed = solid_infill ? 40.f * 60.f : nozzle_change_speed;//If the contact layers belong to different categories, then reduce the speed.
|
||||
bool extruder_change = !is_in_same_extruder(old_filament_id, new_filament_id);
|
||||
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)) {
|
||||
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);
|
||||
writer.set_extrusion_flow(nz_extrusion_flow)
|
||||
.set_z(m_z_pos)
|
||||
.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))
|
||||
.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);
|
||||
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;
|
||||
nozzle_change_line_count = solid_infill ? std::numeric_limits<int>::max() : nozzle_change_line_count;
|
||||
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;
|
||||
bool need_change_flow = false;
|
||||
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;
|
||||
|
||||
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;
|
||||
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;
|
||||
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.origin_start_pos = initial_position;
|
||||
result.end_pos = writer.pos_rotated();
|
||||
result.gcode = writer.gcode();
|
||||
result.is_extruder_change = extruder_change;
|
||||
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>]
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// only; every other printer's wipe tower is untouched. The M632 M-flag is itself a firmware barrier, so
|
||||
// a preceding M400 wait is subsumed.
|
||||
// only; every other printer's wipe tower is untouched.
|
||||
// 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 = "") {
|
||||
std::string buffer;
|
||||
buffer += "M632 S" + std::to_string(m_current_tool) + " M N\n";
|
||||
if (wait_for_moves)
|
||||
buffer += "M400\n";
|
||||
buffer += "M104";
|
||||
if (target_extruder != -1 && target_extruder < (int) m_physical_extruder_map.size())
|
||||
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
|
||||
if (!comment.empty()) buffer += " ;" + comment;
|
||||
buffer += '\n';
|
||||
buffer += "M633\n";
|
||||
(void) wait_for_moves; // the M632 M-flag barrier replaces the former M400 wait
|
||||
return buffer;
|
||||
};
|
||||
// Suppress the pre-heat M104 on the first layer and on solid (contact) toolchanges (should_heating).
|
||||
// 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.
|
||||
// m_is_multiple_nozzle gate needed because Orca calls toolchange_wipe_new for ALL printers (BBS has it H2C-only).
|
||||
bool should_heating = m_is_multiple_nozzle && m_filpar[m_current_tool].filament_cooling_before_tower > EPSILON &&
|
||||
!solid_tool_toolchange && !is_first_layer();
|
||||
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];
|
||||
}
|
||||
|
||||
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,
|
||||
// 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),
|
||||
@@ -3908,6 +4073,19 @@ void WipeTower::plan_tower_new()
|
||||
nozzle_change_depth = nozzle_change_line_count * m_nozzle_change_perimeter_width;
|
||||
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.required_depth = depth;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
Vec2f origin_start_pos; // not rotated
|
||||
|
||||
std::vector<Vec2f> wipe_path;
|
||||
bool is_extruder_change{true};
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
@@ -309,6 +310,8 @@ public:
|
||||
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; }
|
||||
// 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; }
|
||||
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
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
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);
|
||||
int get_filament_category(int filament_id);
|
||||
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();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
@@ -453,6 +464,7 @@ private:
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
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_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -555,6 +567,7 @@ private:
|
||||
|
||||
bool is_tpu_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
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#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_e = LIMITS(machine_max_jerk_e);
|
||||
m_resolution = print_config.resolution.value;
|
||||
|
||||
#undef LIMITS
|
||||
#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)
|
||||
@@ -748,14 +828,19 @@ std::string GCodeWriter::eager_lift(const LiftType type) {
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
double radius = target_lift / (2 * PI * atan(filament()->travel_slope()));
|
||||
// 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 };
|
||||
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");
|
||||
} 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
|
||||
@@ -810,7 +895,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()));
|
||||
Vec2d ij_offset = radius * delta_no_z.normalized();
|
||||
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
|
||||
else if (m_to_lift_type == LiftType::SlopeLift &&
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <charconv>
|
||||
#include "Extruder.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
|
||||
@@ -132,6 +133,8 @@ public:
|
||||
const bool is_bbl_printers() const {return m_is_bbl_printers;}
|
||||
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
|
||||
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.
|
||||
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
||||
@@ -181,6 +184,14 @@ public:
|
||||
|
||||
// Orca: slicing resolution in mm
|
||||
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_end;
|
||||
@@ -197,6 +208,10 @@ public:
|
||||
|
||||
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);
|
||||
// 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 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
|
||||
|
||||
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.
|
||||
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||
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.
|
||||
color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) {
|
||||
//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 = config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||
double outer_wall_line_width = resolve_outer_wall_line_width(config, print_object.config(), print_object.print()->config());
|
||||
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.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Slic3r {
|
||||
class ExPolygon;
|
||||
class ModelVolume;
|
||||
class PrintObject;
|
||||
class PrintConfig;
|
||||
class PrintObjectConfig;
|
||||
class PrintRegionConfig;
|
||||
class FacetsAnnotation;
|
||||
|
||||
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
|
||||
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 boost::polygon {
|
||||
|
||||
@@ -1209,7 +1209,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"post_process",
|
||||
"slicing_pipeline_plugin",
|
||||
"plugins",
|
||||
"plugin_config_overrides",
|
||||
"print_plugin_config_overrides",
|
||||
"process_change_extrusion_role_gcode",
|
||||
"min_length_factor",
|
||||
"wall_maximum_resolution",
|
||||
@@ -1385,9 +1385,9 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
"filament_ramming_travel_time", "filament_ramming_travel_time_nc",
|
||||
"filament_pre_cooling_temperature", "filament_pre_cooling_temperature_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",
|
||||
"plugin_config_overrides",
|
||||
"filament_plugin_config_overrides",
|
||||
//ams chamber
|
||||
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
|
||||
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
|
||||
@@ -1444,7 +1444,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
|
||||
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
|
||||
"support_fast_purge_mode", "deretract_speed_extruder_change",
|
||||
"plugin_config_overrides"
|
||||
"printer_plugin_config_overrides"
|
||||
};
|
||||
|
||||
static std::vector<std::string> s_Preset_sla_print_options {
|
||||
@@ -1556,6 +1556,15 @@ const std::vector<std::string>& Preset::printer_options()
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const char* Preset::plugin_overrides_key(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case TYPE_PRINTER: return "printer_plugin_config_overrides";
|
||||
case TYPE_FILAMENT: return "filament_plugin_config_overrides";
|
||||
default: return "print_plugin_config_overrides";
|
||||
}
|
||||
}
|
||||
|
||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
|
||||
m_type(type),
|
||||
m_edited_preset(type, "", false),
|
||||
@@ -3524,7 +3533,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)
|
||||
{
|
||||
//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,
|
||||
// thus the vector is presented to the user as a single input.
|
||||
diff.emplace_back(opt_key);
|
||||
|
||||
@@ -407,6 +407,11 @@ public:
|
||||
// Printer machine limits, those are contained in printer_options().
|
||||
static const std::vector<std::string>& machine_limits_options();
|
||||
|
||||
// Option key holding this preset type's plugin capability overrides. Each type has its own key so
|
||||
// the values survive the merge into a single full config; print is the fallback for the types with
|
||||
// no plugin-backed options.
|
||||
static const char* plugin_overrides_key(Type type);
|
||||
|
||||
static const std::vector<std::string>& sla_printer_options();
|
||||
static const std::vector<std::string>& sla_material_options();
|
||||
static const std::vector<std::string>& sla_print_options();
|
||||
|
||||
+87
-19
@@ -286,6 +286,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else if (
|
||||
opt_key == "slicing_pipeline_plugin"
|
||||
|| opt_key == "print_plugin_config_overrides"
|
||||
|| opt_key == "initial_layer_print_height"
|
||||
|| opt_key == "nozzle_diameter"
|
||||
|| opt_key == "filament_shrink"
|
||||
@@ -3649,7 +3650,8 @@ std::vector<std::set<int>> Print::get_physical_unprintable_filaments(const std::
|
||||
return physical_unprintables;
|
||||
|
||||
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) {
|
||||
if (!(status >> i & 1)) {
|
||||
return i;
|
||||
@@ -4061,6 +4063,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());
|
||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||
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
|
||||
// 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.
|
||||
@@ -4096,14 +4100,32 @@ void Print::_make_wipe_tower()
|
||||
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();
|
||||
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();
|
||||
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||
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
|
||||
++layer_idx;
|
||||
|
||||
if (!layer_tools.has_wipe_tower) continue;
|
||||
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);
|
||||
@@ -4114,30 +4136,76 @@ void Print::_make_wipe_tower()
|
||||
if (filament_id == current_filament_id)
|
||||
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;
|
||||
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];
|
||||
// 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(nozzle_id)
|
||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||
volume_to_purge *= flush_multiplier;
|
||||
volume_to_purge = pre_filament_id == -1 ? 0 :
|
||||
layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_filament_id, filament_id, volume_to_purge);
|
||||
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||
if (group_result) {
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||
if (nozzle_info) {
|
||||
int extruder_id = nozzle_info->extruder_id;
|
||||
int nozzle_id = nozzle_info->group_id;
|
||||
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.
|
||||
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);
|
||||
|
||||
// Saving mode reduces the prime volume to 15 mm3; Default is inert.
|
||||
float prime_volume = (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) ? 15.f : (float) m_config.prime_volume;
|
||||
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||
// 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,
|
||||
prime_volume, volume_to_purge);
|
||||
current_filament_id = filament_id;
|
||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||
}
|
||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||
|
||||
|
||||
@@ -1100,16 +1100,21 @@ void PrintConfigDef::init_common_params()
|
||||
def->set_default_value(new ConfigOptionString());
|
||||
}
|
||||
|
||||
def = this->add("plugin_config_overrides", coString);
|
||||
def->label = L("Capabilities");
|
||||
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
|
||||
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
|
||||
"behind the button, never typed in directly.");
|
||||
// Never shown as a text field: GUIType::plugin_config renders a button that opens PluginsConfigDialog.
|
||||
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
// One key per preset type (Preset::plugin_overrides_key), so the print, printer and filament
|
||||
// overrides don't clobber each other when the presets merge into one full config. No handle_legacy
|
||||
// migration from the shared "plugin_config_overrides" they replace: it only ever shipped in
|
||||
// nightlies. Never a text field — GUIType::plugin_config renders a button opening PluginsConfigDialog.
|
||||
for (const char* key : {"print_plugin_config_overrides", "printer_plugin_config_overrides", "filament_plugin_config_overrides"}) {
|
||||
def = this->add(key, coString);
|
||||
def->label = L("Capabilities");
|
||||
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
|
||||
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
|
||||
"behind the button, never typed in directly.");
|
||||
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
|
||||
def->mode = comAdvanced;
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
}
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_fff_params()
|
||||
@@ -2306,8 +2311,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Top surface expansion");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane."
|
||||
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top."
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. "
|
||||
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. "
|
||||
"The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
@@ -2882,7 +2887,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->nullable = true;
|
||||
def->min = 0;
|
||||
def->max = max_temp;
|
||||
def->sidetext = L(u8"℃" /* °C */); // degrees Celsius, CIS languages need translation
|
||||
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
|
||||
def->set_default_value(new ConfigOptionIntsNullable{0});
|
||||
|
||||
def = this->add("filament_flush_volumetric_speed", coFloats);
|
||||
@@ -7531,7 +7536,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->tooltip = L("The flush multiplier used in fast purge mode.");
|
||||
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->label = L("Prime volume");
|
||||
def->tooltip = L("This is the volume of material to prime the extruder with on the tower.");
|
||||
@@ -8107,7 +8112,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->label = L("Extruder change");
|
||||
def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled.");
|
||||
def->mode = comAdvanced;
|
||||
def->sidetext = "°C";
|
||||
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
|
||||
def->min = 0;
|
||||
def->nullable = true;
|
||||
def->set_default_value(new ConfigOptionIntsNullable{0});
|
||||
@@ -8137,7 +8142,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->tooltip = L(
|
||||
"To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled.");
|
||||
def->mode = comAdvanced;
|
||||
def->sidetext = "°C";
|
||||
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
|
||||
def->min = 0;
|
||||
def->nullable = true;
|
||||
def->set_default_value(new ConfigOptionIntsNullable{0});
|
||||
@@ -8177,6 +8182,16 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comDevelop;
|
||||
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->label = L("Hotend change");
|
||||
def->tooltip = L("The volume of material required to prime the extruder for a hotend change on the tower.");
|
||||
@@ -8188,7 +8203,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("filament_preheat_temperature_delta", coFloats);
|
||||
def->label = L("Preheat temperature delta");
|
||||
def->tooltip = L("Temperature delta applied during pre-heating before tool change.");
|
||||
def->sidetext = "°C";
|
||||
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
|
||||
def->mode = comDevelop;
|
||||
def->nullable = true;
|
||||
def->set_default_value(new ConfigOptionFloatsNullable{0});
|
||||
@@ -9197,7 +9212,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
||||
"retraction_distance_when_cut",
|
||||
"internal_bridge_support_thickness", "top_area_threshold", "reduce_wall_solid_infill","filament_load_time","filament_unload_time",
|
||||
"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
|
||||
};
|
||||
|
||||
|
||||
@@ -1817,6 +1817,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionString, filename_format))
|
||||
((ConfigOptionStrings, post_process))
|
||||
((ConfigOptionStrings, slicing_pipeline_plugin))
|
||||
((ConfigOptionString, print_plugin_config_overrides))
|
||||
((ConfigOptionString, printer_model))
|
||||
((ConfigOptionFloat, resolution))
|
||||
((ConfigOptionFloats, retraction_minimum_travel))
|
||||
@@ -1878,6 +1879,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
// BBS: wipe tower is only used for priming
|
||||
((ConfigOptionFloat, prime_volume))
|
||||
// Nozzle-change (nc) prime volume + pre-heat delta
|
||||
((ConfigOptionFloats, filament_prime_volume))
|
||||
((ConfigOptionFloats, filament_prime_volume_nc))
|
||||
((ConfigOptionFloatsNullable, filament_preheat_temperature_delta))
|
||||
((ConfigOptionFloats, flush_multiplier))
|
||||
|
||||
+47
-4
@@ -469,6 +469,31 @@ std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, dou
|
||||
return print_pa_lines(startx, starty, start_pa, step_pa, count);
|
||||
}
|
||||
|
||||
BoundingBoxf CalibPressureAdvanceLine::print_extents(const BoundingBoxf &bed_ext) const
|
||||
{
|
||||
BoundingBoxf adjusted_bed = bed_ext;
|
||||
if (is_delta()) {
|
||||
CalibPressureAdvanceLine::delta_scale_bed_ext(adjusted_bed);
|
||||
}
|
||||
|
||||
double bed_width = adjusted_bed.size().x();
|
||||
// m_length_long adjusts for narrow beds – exactly as in generate_test()
|
||||
double line_long = 40.0 + std::min(bed_width - 120.0, 0.0);
|
||||
double total_line_len = m_length_short * 2 + line_long;
|
||||
double start_x = adjusted_bed.min.x() + (bed_width - 2 * m_length_short - line_long - 20.0) / 2.0;
|
||||
double box_width = m_draw_numbers ? (number_spacing() * 8) : 0.0; // 3.0 * 8 = 24 mm
|
||||
|
||||
BoundingBoxf extent;
|
||||
extent.min.x() = start_x;
|
||||
extent.max.x() = start_x + total_line_len + m_line_width + box_width;
|
||||
|
||||
// Y bounds are the full bed (the caller will inset them by -25)
|
||||
extent.min.y() = adjusted_bed.min.y();
|
||||
extent.max.y() = adjusted_bed.max.y();
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
|
||||
|
||||
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
|
||||
@@ -491,9 +516,10 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
|
||||
std::stringstream gcode;
|
||||
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer + z_offset);
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer + z_offset) << "\n";
|
||||
double y_pos = start_y;
|
||||
|
||||
// prime line
|
||||
// Purge/first perimeter - acts as an anchor to the rest of the model
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
gcode << writer.set_pressure_advance(0.0);
|
||||
auto prime_x = start_x;
|
||||
gcode << move_to(Vec2d(prime_x, y_pos + (num) * m_space_y), writer);
|
||||
@@ -503,10 +529,13 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
for (int i = 0; i < num; ++i) {
|
||||
gcode << writer.set_pressure_advance(start_pa + i * step_pa);
|
||||
gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
gcode << writer.set_speed(slow);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
gcode << writer.set_speed(fast);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
|
||||
gcode << writer.set_speed(slow);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y),
|
||||
e_per_mm * m_length_short);
|
||||
@@ -530,9 +559,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
|
||||
|
||||
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short + m_line_width;
|
||||
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
|
||||
//Draw box
|
||||
default_box_opt_args.is_filled = true;
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
|
||||
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
|
||||
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
|
||||
//Ensure numbers are shown on the next layer in gcode processor, as in reality
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) << "\n";
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer*2 + z_offset) << "\n";
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Top surface\n";
|
||||
gcode << writer.travel_to_z(m_height_layer*2 + z_offset);
|
||||
for (int i = 0; i < num; i += 2) {
|
||||
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
|
||||
@@ -595,12 +630,16 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
speed_adjust(speed_first_layer()));
|
||||
|
||||
// create anchoring frame
|
||||
//pattern uses outer wall speed/width
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
|
||||
|
||||
// create tab for numbers
|
||||
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
|
||||
draw_box_opt_args.is_filled = true;
|
||||
draw_box_opt_args.num_perimeters = wall_count();
|
||||
//draw box as bottom surface, so numbers are clearly visible on top
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
|
||||
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
|
||||
print_size_x(),
|
||||
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
|
||||
@@ -611,7 +650,9 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
const double zhop_config_value = m_config.option<ConfigOptionFloats>("z_hop")->get_at(0);
|
||||
const auto accel = accel_perimeter();
|
||||
|
||||
// draw pressure advance pattern
|
||||
// Draw pressure advance pattern
|
||||
// pattern uses outer wall speed, label it as such
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
for (int i = 0; i < m_num_layers; ++i) {
|
||||
const double layer_height = height_first_layer() + height_z_offset() + (i * height_layer());
|
||||
const double zhop_height = layer_height + zhop_config_value;
|
||||
@@ -643,6 +684,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
|
||||
|
||||
// glyph on every other line
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
for (int j = 0; j < num_patterns; j += 2) {
|
||||
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
|
||||
m_params.start + (j * m_params.step), m_draw_digit_mode, line_width(), number_e_per_mm,
|
||||
@@ -692,7 +734,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
|
||||
for (int j = 0; j < num_patterns; ++j) {
|
||||
// increment pressure advance
|
||||
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
|
||||
|
||||
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
|
||||
for (int k = 0; k < wall_count(); ++k) {
|
||||
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
|
||||
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
|
||||
@@ -729,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 << "; end pressure advance pattern for layer\n";
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ struct Calib_Params
|
||||
{
|
||||
Calib_Params() : mode(CalibMode::Calib_None){};
|
||||
int extruder_id = 0;
|
||||
double start, end, step;
|
||||
bool print_numbers;
|
||||
double freqStartX, freqEndX, freqStartY, freqEndY;
|
||||
int test_model;
|
||||
double start = 0.0, end = 1.0, step = 0.1;
|
||||
bool print_numbers = false;
|
||||
double freqStartX = 0.0, freqEndX = 1.0, freqStartY = 0.0, freqEndY = 1.0;
|
||||
int test_model = 0;
|
||||
std::string shaper_type;
|
||||
std::vector<double> accelerations;
|
||||
std::vector<double> speeds;
|
||||
@@ -254,6 +254,8 @@ class CalibPressureAdvanceLine : public CalibPressureAdvance
|
||||
public:
|
||||
CalibPressureAdvanceLine(GCode* gcodegen);
|
||||
~CalibPressureAdvanceLine(){};
|
||||
// Return the X‑bounds of the pattern on the given bed.
|
||||
BoundingBoxf print_extents(const BoundingBoxf &bed_ext) const;
|
||||
|
||||
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
|
||||
|
||||
@@ -379,4 +381,4 @@ private:
|
||||
const double m_glyph_padding_vertical{1};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -91,6 +91,13 @@ public:
|
||||
//
|
||||
void toggle_top_layer_only_view_range();
|
||||
//
|
||||
// Dim previous layers (ORCA, ported from preFlight)
|
||||
// Whether the layers below the current top layer are rendered darkened while
|
||||
// scrubbing below the full print, so only the current layer is shown at full brightness.
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
//
|
||||
// Returns true if the given option is visible.
|
||||
//
|
||||
bool is_option_visible(EOptionType type) const;
|
||||
|
||||
@@ -19,6 +19,9 @@ struct Settings
|
||||
EViewType view_type{ EViewType::FeatureType };
|
||||
ETimeMode time_mode{ ETimeMode::Normal };
|
||||
bool top_layer_only_view_range{ false };
|
||||
// ORCA: when enabled, all layers below the current top layer are rendered
|
||||
// darkened (keeping their color) while scrubbing below the full print (ported from preFlight)
|
||||
bool dim_previous_layers{ false };
|
||||
bool spiral_vase_mode{ false };
|
||||
//
|
||||
// Required update flags
|
||||
|
||||
@@ -72,6 +72,16 @@ void Viewer::toggle_top_layer_only_view_range()
|
||||
m_impl->toggle_top_layer_only_view_range();
|
||||
}
|
||||
|
||||
bool Viewer::is_dim_previous_layers() const
|
||||
{
|
||||
return m_impl->is_dim_previous_layers();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers(bool value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_option_visible(EOptionType type) const
|
||||
{
|
||||
return m_impl->is_option_visible(type);
|
||||
|
||||
@@ -1223,6 +1223,20 @@ static float encode_color(const Color& color) {
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
// ORCA: how much the layers below the current top layer are darkened when
|
||||
// Settings::dim_previous_layers is enabled (ported from preFlight). 0.0 = no change, 1.0 = black.
|
||||
static constexpr float PREVIOUS_LAYER_DARKEN_FACTOR = 0.60f;
|
||||
|
||||
// ORCA: returns the encoded color scaled towards black by 'factor', preserving its hue
|
||||
static float encode_color_darkened(const Color& color, float factor) {
|
||||
const float keep = 1.0f - factor;
|
||||
const int r = static_cast<int>(color[0] * keep);
|
||||
const int g = static_cast<int>(color[1] * keep);
|
||||
const int b = static_cast<int>(color[2] * keep);
|
||||
const int i_color = r << 16 | g << 8 | b;
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
|
||||
void ViewerImpl::update_colors_texture()
|
||||
{
|
||||
@@ -1234,14 +1248,30 @@ void ViewerImpl::update_colors_texture()
|
||||
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
|
||||
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
|
||||
|
||||
// ORCA: when dim_previous_layers is enabled, darken every layer below the current top layer
|
||||
// (keeping its color) whenever we are not rendering the whole print, so that only the layer
|
||||
// being scrubbed to is shown at full brightness (ported from preFlight). This shares
|
||||
// top_layer_id with the greying path, so it only applies while in top-layer-only mode - that
|
||||
// way the moves slider still animates normally across all layers when that mode is disabled.
|
||||
const bool dim_previous_layers = m_settings.dim_previous_layers && !m_layers.empty();
|
||||
const bool full_render = (m_layers.get_view_range()[0] == 0) &&
|
||||
(m_layers.get_view_range()[1] >= static_cast<uint32_t>(m_layers.count()) - 1) &&
|
||||
(m_view_range.get_visible()[1] == m_view_range.get_full()[1]);
|
||||
|
||||
// Based on current settings and slider position, we might want to render some
|
||||
// vertices as dark grey. Use either that or the normal color (from the cache).
|
||||
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
|
||||
std::vector<float> colors(m_vertices_colors.size());
|
||||
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
|
||||
for (size_t i=0; i<m_vertices.size(); ++i)
|
||||
colors[i] = (color_top_layer_only && m_vertices[i].layer_id < top_layer_id &&
|
||||
(!m_settings.spiral_vase_mode || i != m_view_range.get_enabled()[0])) ?
|
||||
encode_color(DUMMY_COLOR) : m_vertices_colors[i];
|
||||
for (size_t i=0; i<m_vertices.size(); ++i) {
|
||||
const PathVertex& v = m_vertices[i];
|
||||
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
|
||||
if (dim_previous_layers && !full_render && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color_darkened(get_vertex_color(v), PREVIOUS_LAYER_DARKEN_FACTOR);
|
||||
else if (color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color(DUMMY_COLOR);
|
||||
else
|
||||
colors[i] = m_vertices_colors[i];
|
||||
}
|
||||
|
||||
#ifdef ENABLE_OPENGL_ES
|
||||
if (!colors.empty())
|
||||
@@ -1349,6 +1379,17 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// ORCA: enable/disable darkening of the layers below the current top layer (ported from preFlight)
|
||||
void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
{
|
||||
if (m_settings.dim_previous_layers == value)
|
||||
return;
|
||||
m_settings.dim_previous_layers = value;
|
||||
// defer the actual color/texture rebuild to the next render(), when the GL context is current
|
||||
// (this may be toggled from the Preferences dialog, outside the canvas context)
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
|
||||
{
|
||||
std::vector<ETimeMode> ret;
|
||||
|
||||
@@ -85,6 +85,10 @@ public:
|
||||
bool is_top_layer_only_view_range() const { return m_settings.top_layer_only_view_range; }
|
||||
void toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing (ported from preFlight)
|
||||
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
|
||||
void set_dim_previous_layers(bool value);
|
||||
|
||||
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
|
||||
|
||||
std::vector<ETimeMode> get_time_modes() const;
|
||||
|
||||
@@ -548,7 +548,7 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
|
||||
@@ -454,7 +454,7 @@ wxBoxSizer* AMSDryCtrWin::create_normal_state_panel(wxPanel* parent)
|
||||
m_temperature_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
m_temperature_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
|
||||
|
||||
Label* temp_unit_label = new Label(parent, wxString::FromUTF8("℃"));
|
||||
Label* temp_unit_label = new Label(parent, wxString::FromUTF8(u8"\u2103" /* °C */));
|
||||
temp_unit_label->SetForegroundColour(*wxBLACK);
|
||||
temp_sizer->Add(m_temperature_input, 1, wxRIGHT, FromDIP(1));
|
||||
temp_sizer->Add(temp_unit_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -736,7 +736,7 @@ wxBoxSizer* AMSDryCtrWin::create_guide_right_section(wxPanel* parent)
|
||||
|
||||
m_back_button = create_button(
|
||||
parent,
|
||||
wxString::FromUTF8(_CTX_utf8(L_CONTEXT("Back", "amsdrying"), "amsdrying")),
|
||||
wxString::FromUTF8(_u8L_CONTEXT(L_CONTEXT("Back", "amsdrying"), "amsdrying")),
|
||||
wxColour("#F8F8F8"), // Background color - light gray
|
||||
wxColour("#D0D0D0"), // Border color - gray
|
||||
*wxBLACK // Text color - black
|
||||
@@ -1444,9 +1444,9 @@ int AMSDryCtrWin::update_ams_change(DevAms* dev_ams)
|
||||
|
||||
m_ams_info.m_ams_id = dev_ams->GetAmsId();
|
||||
if (dev_ams->GetAmsType() == DevAmsType::N3F) {
|
||||
m_temperature_input->SetHint("45-65" + wxString::FromUTF8("°C"));
|
||||
m_temperature_input->SetHint("45-65" + wxString::FromUTF8(u8"\u2103" /* °C */));
|
||||
} else if (dev_ams->GetAmsType() == DevAmsType::N3S) {
|
||||
m_temperature_input->SetHint("45-85" + wxString::FromUTF8("°C"));
|
||||
m_temperature_input->SetHint("45-85" + wxString::FromUTF8(u8"\u2103" /* °C */));
|
||||
}
|
||||
|
||||
m_time_input->SetHint("1-24 h");
|
||||
@@ -1467,7 +1467,7 @@ int AMSDryCtrWin::update_dryness_status(DevAms* dev_ams)
|
||||
if (m_ams_info.m_temperature != dev_ams->GetCurrentTemperature()) {
|
||||
updated += 1;
|
||||
m_ams_info.m_temperature = dev_ams->GetCurrentTemperature();
|
||||
m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8("°C"));
|
||||
m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8(u8"\u2103" /* °C */));
|
||||
}
|
||||
|
||||
if (is_dry_ctr_idle(dev_ams)) {
|
||||
|
||||
@@ -2100,7 +2100,7 @@ void AmsReplaceMaterialDialog::create()
|
||||
m_top_line->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
m_main_sizer->Add(m_top_line, 0, wxEXPAND, 0);
|
||||
|
||||
m_nozzle_btn_panel = new SwitchBoard(this, _L("Left"), _L("Right"), wxSize(FromDIP(126), FromDIP(26)));
|
||||
m_nozzle_btn_panel = new SwitchBoard(this, _L_CONTEXT("Left", "Nozzle position"), _L_CONTEXT("Right", "Nozzle position"), wxSize(FromDIP(126), FromDIP(26)));
|
||||
m_nozzle_btn_panel->Hide();
|
||||
m_nozzle_btn_panel->Connect(wxCUSTOMEVT_SWITCH_POS, wxCommandEventHandler(AmsReplaceMaterialDialog::on_nozzle_selected), NULL, this);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Slic3r
|
||||
AppConfig* config = GUI::wxGetApp().app_config;
|
||||
if (!config)
|
||||
return;
|
||||
const auto& local_machines = config->get_local_machines();
|
||||
const auto local_machines = config->get_local_machines();
|
||||
for (auto& it : local_machines) {
|
||||
const auto& m = it.second;
|
||||
if (localMachineList.count(m.dev_id))
|
||||
@@ -558,6 +558,7 @@ namespace Slic3r
|
||||
}
|
||||
else
|
||||
{
|
||||
Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning();
|
||||
if (m_agent)
|
||||
{
|
||||
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
|
||||
@@ -591,6 +592,7 @@ namespace Slic3r
|
||||
}
|
||||
|
||||
selected_machine = dev_id;
|
||||
record_user_last_machine(selected_machine);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -853,21 +855,39 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManager::record_user_last_machine(const std::string& dev_id)
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceManager::get_user_last_machine() const
|
||||
{
|
||||
if (Slic3r::GUI::wxGetApp().app_config) {
|
||||
const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine");
|
||||
if (!user_last_machine.empty()) {
|
||||
return user_last_machine;
|
||||
} else if (m_agent) {
|
||||
return m_agent->get_user_selected_machine();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void DeviceManager::load_last_machine()
|
||||
{
|
||||
// Get all available machines, include cloud machines and lan machines that have access right
|
||||
auto all_machines = get_my_machine_list();
|
||||
if (all_machines.empty())
|
||||
return;
|
||||
|
||||
// Then connect to the machine we last selected if available
|
||||
const std::string last_monitor_machine = m_agent ? m_agent->get_user_selected_machine() : "";
|
||||
const auto last_machine = all_machines.find(last_monitor_machine);
|
||||
if (last_machine != all_machines.end()) {
|
||||
this->set_selected_machine(last_machine->second->get_dev_id());
|
||||
if (userMachineList.empty()) return;
|
||||
else if (userMachineList.size() == 1) {
|
||||
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
|
||||
} else {
|
||||
// If not, then select the first available one
|
||||
this->set_selected_machine(all_machines.begin()->second->get_dev_id());
|
||||
const auto& last_monitor_machine = get_user_last_machine();
|
||||
if (userMachineList.find(last_monitor_machine) != userMachineList.end()) {
|
||||
set_selected_machine(last_monitor_machine);
|
||||
} else {
|
||||
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ public:
|
||||
MachineObject* get_selected_machine();
|
||||
bool set_selected_machine(std::string dev_id);
|
||||
|
||||
void record_user_last_machine(const std::string& dev_id);
|
||||
std::string get_user_last_machine() const;
|
||||
|
||||
// local machine
|
||||
void set_local_selected_machine(std::string dev_id) { local_selected_machine = dev_id; };
|
||||
MachineObject* get_local_selected_machine() const { return get_local_machine(local_selected_machine); }
|
||||
|
||||
@@ -637,7 +637,7 @@ ReselectMachineDialog::ReselectMachineDialog(wxWindow* parent)
|
||||
|
||||
wxBoxSizer* okGroupSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
UiAMSSlot* amsSlotOK= new UiAMSSlot(statusBar, colourOK, wxString("Ax\nPLA"), DataStatusType::OK, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(58), FromDIP(90)), 1.0, 1.0);
|
||||
wxStaticText* okLabel = new wxStaticText(statusBar, wxID_ANY, _CTX(L_CONTEXT("OK", "FilamentTrack"), "FilamentTrack"));
|
||||
wxStaticText* okLabel = new wxStaticText(statusBar, wxID_ANY, _L_CONTEXT(L_CONTEXT("OK", "FilamentTrack"), "FilamentTrack"));
|
||||
okLabel->SetFont(wxGetApp().normal_font());
|
||||
okLabel->SetForegroundColour(wxColour("#6B6B6B"));
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ void ExtrusionCalibration::create()
|
||||
m_button_save_result->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
|
||||
m_button_save_result->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_save, this);
|
||||
|
||||
m_button_last_step = new Button(m_step_2_panel, _CTX("Back", "Navigation")); // Back for english
|
||||
m_button_last_step = new Button(m_step_2_panel, _L_CONTEXT("Back", "Navigation")); // Back for english
|
||||
m_button_last_step->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
|
||||
m_button_last_step->Bind(wxEVT_BUTTON, &ExtrusionCalibration::on_click_last, this);
|
||||
|
||||
|
||||
+116
-28
@@ -111,14 +111,25 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
|
||||
{
|
||||
wxString tooltip = _(opt.tooltip);
|
||||
|
||||
std::string opt_id = id;
|
||||
auto hash_pos = opt_id.find("#");
|
||||
std::string parameter_name = id;
|
||||
std::string opt_key = id;
|
||||
int opt_idx = 0;
|
||||
|
||||
const size_t hash_pos = static_cast<std::string>(id).find("#");
|
||||
|
||||
if (hash_pos != std::string::npos) {
|
||||
opt_id.replace(hash_pos, 1,"[");
|
||||
opt_id += "]";
|
||||
parameter_name.replace(hash_pos, 1, "[");
|
||||
parameter_name += "]";
|
||||
|
||||
std::string temp_str = id;
|
||||
boost::erase_head(temp_str, hash_pos + 1);
|
||||
size_t orig_opt_idx = atoi(temp_str.c_str());
|
||||
opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
|
||||
|
||||
boost::erase_tail(opt_key, opt_key.size() - hash_pos);
|
||||
}
|
||||
|
||||
tooltip += (tooltip.empty() ? "" : "\n\n") + _(L("parameter name")) + ": " + opt_id;
|
||||
tooltip += (tooltip.empty() ? "" : "\n\n") + _(L("parameter name")) + ": " + parameter_name;
|
||||
|
||||
// Orca:
|
||||
// We can't use Orca's default values as-is because they sometimes depend on other values.
|
||||
@@ -126,7 +137,7 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
|
||||
if (const Preset* print_parent_preset = wxGetApp().preset_bundle->prints.get_selected_preset_parent()) {
|
||||
const DynamicPrintConfig& parent_config = print_parent_preset->config;
|
||||
|
||||
if (!parent_config.has(opt_id))
|
||||
if (!parent_config.has(opt_key))
|
||||
return tooltip;
|
||||
|
||||
wxString side_text = from_u8(opt.sidetext);
|
||||
@@ -135,18 +146,71 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
|
||||
if (opt.sidetext == L("layers"))
|
||||
side_text = " " + _(side_text);
|
||||
|
||||
if (opt.type == coFloat || opt.type == coInt || opt.type == coPercent || opt.type == coFloatOrPercent) {
|
||||
double default_value = 0.;
|
||||
if (opt.type == coFloat || opt.type == coInt || opt.type == coPercent || opt.type == coFloatOrPercent ||
|
||||
opt.type == coFloats || opt.type == coInts || opt.type == coPercents || opt.type == coFloatsOrPercents) {
|
||||
double default_value = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
if (opt.type == coFloat)
|
||||
default_value = parent_config.option<ConfigOptionFloat>(opt_id)->value;
|
||||
default_value = parent_config.option<ConfigOptionFloat>(opt_key)->value;
|
||||
else if (opt.type == coFloats) {
|
||||
if (opt.nullable) {
|
||||
auto opt_floats_nullable = parent_config.option<ConfigOptionFloatsNullable>(opt_key);
|
||||
if (!opt_floats_nullable->values.empty())
|
||||
default_value = opt_floats_nullable->get_at(opt_idx);
|
||||
} else {
|
||||
auto opt_floats = parent_config.option<ConfigOptionFloats>(opt_key);
|
||||
if (!opt_floats->values.empty())
|
||||
default_value = opt_floats->get_at(opt_idx);
|
||||
}
|
||||
}
|
||||
else if (opt.type == coInt)
|
||||
default_value = parent_config.option<ConfigOptionInt>(opt_id)->value;
|
||||
default_value = parent_config.option<ConfigOptionInt>(opt_key)->value;
|
||||
else if(opt.type == coInts) {
|
||||
if (opt.nullable) {
|
||||
auto opt_ints_nullable = parent_config.option<ConfigOptionIntsNullable>(opt_key);
|
||||
if (!opt_ints_nullable->values.empty())
|
||||
default_value = opt_ints_nullable->get_at(opt_idx);
|
||||
} else {
|
||||
auto opt_ints = parent_config.option<ConfigOptionInts>(opt_key);
|
||||
if (!opt_ints->values.empty())
|
||||
default_value = opt_ints->get_at(opt_idx);
|
||||
}
|
||||
}
|
||||
else if (opt.type == coPercent)
|
||||
default_value = parent_config.option<ConfigOptionPercent>(opt_id)->value;
|
||||
else if (opt.type == coFloatOrPercent) {
|
||||
default_value = parent_config.option<ConfigOptionFloatOrPercent>(opt_id)->value;
|
||||
if (parent_config.option<ConfigOptionFloatOrPercent>(opt_id)->percent)
|
||||
default_value = parent_config.option<ConfigOptionPercent>(opt_key)->value;
|
||||
else if (opt.type == coPercents) {
|
||||
if (opt.nullable) {
|
||||
auto opt_percents_nullable = parent_config.option<ConfigOptionPercentsNullable>(opt_key);
|
||||
if (!opt_percents_nullable->values.empty())
|
||||
default_value = opt_percents_nullable->get_at(opt_idx);
|
||||
} else {
|
||||
auto opt_percents = parent_config.option<ConfigOptionPercents>(opt_key);
|
||||
if (!opt_percents->values.empty())
|
||||
default_value = opt_percents->get_at(opt_idx);
|
||||
}
|
||||
}
|
||||
else if (opt.type == coFloatOrPercent || opt.type == coFloatsOrPercents) {
|
||||
bool is_percent = false;
|
||||
if (opt.type == coFloatOrPercent) {
|
||||
default_value = parent_config.option<ConfigOptionFloatOrPercent>(opt_key)->value;
|
||||
is_percent = parent_config.option<ConfigOptionFloatOrPercent>(opt_key)->percent;
|
||||
} else if (opt.type == coFloatsOrPercents) {
|
||||
if (opt.nullable) {
|
||||
auto opt_floats_or_percents_nullable = parent_config.option<ConfigOptionFloatsOrPercentsNullable>(opt_key);
|
||||
if (!opt_floats_or_percents_nullable->values.empty()) {
|
||||
default_value = opt_floats_or_percents_nullable->get_at(opt_idx).value;
|
||||
is_percent = opt_floats_or_percents_nullable->get_at(opt_idx).percent;
|
||||
}
|
||||
} else {
|
||||
auto opt_floats_or_percents = parent_config.option<ConfigOptionFloatsOrPercents>(opt_key);
|
||||
if (!opt_floats_or_percents->values.empty()) {
|
||||
default_value = opt_floats_or_percents->get_at(opt_idx).value;
|
||||
is_percent = opt_floats_or_percents->get_at(opt_idx).percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_percent)
|
||||
side_text = "%";
|
||||
else if (!side_text.empty()) {
|
||||
static std::string postfix = " or %";
|
||||
@@ -156,20 +220,38 @@ wxString get_formatted_tooltip_text(const ConfigOptionDef& opt, const t_config_o
|
||||
}
|
||||
}
|
||||
|
||||
tooltip += "\n\n" + _(L("Default")) + ": " + _(double_to_string(default_value)) + _(side_text);
|
||||
if (!std::isnan(default_value))
|
||||
tooltip += "\n\n" + _(L("Default")) + ": " + _(double_to_string(default_value)) + _(side_text);
|
||||
|
||||
if (opt.min > -FLT_MAX && opt.max < FLT_MAX) {
|
||||
tooltip += "\n" + _(L("Range")) + ": [" +
|
||||
_(double_to_string(opt.min)) + _(side_text) + ", " +
|
||||
_(double_to_string(opt.max)) + _(side_text) + "]";
|
||||
}
|
||||
} else if (opt.type == coBool || opt.type == coString) {
|
||||
} else if (opt.type == coBool || opt.type == coBools || opt.type == coString || opt.type == coStrings) {
|
||||
std::string default_value = "";
|
||||
|
||||
if (opt.type == coString)
|
||||
default_value = parent_config.option<ConfigOptionString>(opt_id)->value;
|
||||
default_value = parent_config.option<ConfigOptionString>(opt_key)->value;
|
||||
else if (opt.type == coStrings) {
|
||||
auto opt_strings = parent_config.option<ConfigOptionStrings>(opt_key);
|
||||
|
||||
if (!opt_strings->values.empty())
|
||||
default_value = opt_strings->get_at(opt_idx);
|
||||
}
|
||||
else if (opt.type == coBool)
|
||||
default_value = parent_config.option<ConfigOptionBool>(opt_id)->value ? "true" : "false";
|
||||
default_value = parent_config.option<ConfigOptionBool>(opt_key)->value != 0 ? "true" : "false";
|
||||
else if (opt.type == coBools) {
|
||||
if (opt.nullable) {
|
||||
auto opt_bools_nullable = parent_config.option<ConfigOptionBoolsNullable>(opt_key);
|
||||
if (!opt_bools_nullable->values.empty())
|
||||
default_value = opt_bools_nullable->get_at(opt_idx) != 0 ? "true" : "false";
|
||||
} else {
|
||||
auto opt_bools = parent_config.option<ConfigOptionBools>(opt_key);
|
||||
if (!opt_bools->values.empty())
|
||||
default_value = opt_bools->get_at(opt_idx) != 0 ? "true" : "false";
|
||||
}
|
||||
}
|
||||
|
||||
tooltip += "\n\n" + _(L("Default")) + ": " +
|
||||
(default_value.empty() ? _(L("Empty string")) : _(default_value) + _(side_text));
|
||||
@@ -961,14 +1043,14 @@ void TextCtrl::propagate_value()
|
||||
|
||||
void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) {
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
if (m_opt.nullable) {
|
||||
const bool m_is_na_val = value.empty() || (boost::any_cast<wxString>(value) == _(L("N/A")));
|
||||
if (!m_is_na_val)
|
||||
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;
|
||||
|
||||
if (!change_event) {
|
||||
@@ -1105,18 +1187,24 @@ void CheckBox::set_value(const boost::any& value, bool change_event)
|
||||
m_disable_change_event = !change_event;
|
||||
if (m_opt.nullable) {
|
||||
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 &&
|
||||
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 ?
|
||||
boost::any_cast<unsigned char>(value) != 0 :
|
||||
boost::any_cast<bool>(value);
|
||||
dynamic_cast<::CheckBox*>(window)->SetValue(m_is_na_val ? false : bool_value); // BBS
|
||||
if (!m_is_na_val) {
|
||||
bool_value = is_value_unsigned_char ?
|
||||
boost::any_cast<unsigned char>(value) != 0 :
|
||||
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)->SetHalfChecked(value.empty());
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ void FilamentMapManualPanel::OnTimer(wxTimerEvent &)
|
||||
m_suggestion_panel->Hide();
|
||||
} else {
|
||||
m_errors->SetLabel(wxString::Format(_L("Error: %s extruder has no available %s nozzle, current group result is invalid."),
|
||||
invalid_eid == 0 ? _L("Left") : _L("Right"),
|
||||
invalid_eid == 0 ? _L_CONTEXT("Left", "Nozzle position") : _L_CONTEXT("Right", "Nozzle position"),
|
||||
invalid_nozzle == NozzleVolumeType::nvtStandard ? _L("Standard") : _L("High Flow")));
|
||||
// Re-wrap: wrapping only applies to the label text present when Wrap is called,
|
||||
// and the label was empty at construction time.
|
||||
|
||||
@@ -1141,6 +1141,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
if (current_top_layer_only != required_top_layer_only)
|
||||
m_viewer.toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
|
||||
|
||||
// avoid processing if called with the same gcode_result
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
|
||||
//BBS: add logs
|
||||
@@ -4637,7 +4640,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
};
|
||||
|
||||
auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
|
||||
imgui.text(_CTX_utf8("Print", "Noun"));
|
||||
imgui.text(_u8L_CONTEXT("Print", "Noun"));
|
||||
ImGui::SameLine();
|
||||
|
||||
float icon_size = ImGui::GetTextLineHeight();
|
||||
@@ -4671,7 +4674,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
for (const PartialTime& item : partial_times) {
|
||||
switch (item.type)
|
||||
{
|
||||
case PartialTime::EType::Print: { labels.push_back(_CTX_utf8("Print", "Noun")); break; }
|
||||
case PartialTime::EType::Print: { labels.push_back(_u8L_CONTEXT("Print", "Noun")); break; }
|
||||
case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
|
||||
case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
|
||||
}
|
||||
|
||||
@@ -338,6 +338,10 @@ public:
|
||||
|
||||
libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); }
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing the preview (ported from preFlight)
|
||||
void set_dim_previous_layers(bool value) { m_viewer.set_dim_previous_layers(value); }
|
||||
bool is_dim_previous_layers() const { return m_viewer.is_dim_previous_layers(); }
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
bool is_legend_shown() const { return m_legend_visible && m_legend_enabled; }
|
||||
|
||||
@@ -282,7 +282,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D&
|
||||
ImGui::SetCursorPosX(input_align);
|
||||
ImGui::BBLDragFloat("##adaptive_input", &m_adaptive_quality, 0.05f, 0.0f, 0.0f, "%.2f");
|
||||
|
||||
if (imgui.button(_L("Smooth")))
|
||||
if (imgui.button(_L_CONTEXT("Smooth", "Mesh action")))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
|
||||
ImGui::SameLine();
|
||||
text_align = std::max(text_align, ImGui::GetCursorPosX());
|
||||
@@ -6079,11 +6079,11 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _CTX_utf8("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _CTX_utf8("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _CTX_utf8("Right", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
||||
|
||||
float sc = get_scale();
|
||||
#ifdef WIN32
|
||||
@@ -6535,21 +6535,21 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
|
||||
// glsafe(::glEnable(GL_MULTISAMPLE));
|
||||
|
||||
GLint max_samples;
|
||||
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
|
||||
glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples));
|
||||
GLsizei num_samples = max_samples / 2;
|
||||
|
||||
GLuint render_fbo;
|
||||
glsafe(::glGenFramebuffers(1, &render_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
|
||||
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
|
||||
|
||||
GLuint render_tex = 0;
|
||||
GLuint render_tex_buffer = 0;
|
||||
if (multisample) {
|
||||
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
|
||||
glsafe(::glGenRenderbuffers(1, &render_tex_buffer));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer));
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h));
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer));
|
||||
glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer));
|
||||
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer));
|
||||
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h));
|
||||
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer));
|
||||
}
|
||||
else {
|
||||
glsafe(::glGenTextures(1, &render_tex));
|
||||
@@ -6557,31 +6557,31 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
|
||||
}
|
||||
|
||||
GLuint render_depth;
|
||||
glsafe(::glGenRenderbuffers(1, &render_depth));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
|
||||
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
|
||||
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
|
||||
if (multisample)
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
||||
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
||||
else
|
||||
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
|
||||
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h));
|
||||
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
|
||||
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
|
||||
|
||||
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
||||
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0_EXT };
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
|
||||
render_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, model_objects, volumes, extruder_colors, shader, camera_type, camera_view_angle_type,
|
||||
for_picking,
|
||||
ban_light);
|
||||
|
||||
if (multisample) {
|
||||
GLuint resolve_fbo;
|
||||
glsafe(::glGenFramebuffers(1, &resolve_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
|
||||
glsafe(::glGenFramebuffersEXT(1, &resolve_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
|
||||
GLuint resolve_tex;
|
||||
glsafe(::glGenTextures(1, &resolve_tex));
|
||||
@@ -6589,21 +6589,21 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0));
|
||||
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo));
|
||||
glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
|
||||
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
}
|
||||
|
||||
glsafe(::glDeleteTextures(1, &resolve_tex));
|
||||
glsafe(::glDeleteFramebuffers(1, &resolve_fbo));
|
||||
glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo));
|
||||
}
|
||||
else
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
@@ -6613,13 +6613,13 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
glsafe(::glDeleteRenderbuffers(1, &render_depth));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
||||
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
|
||||
if (render_tex_buffer != 0)
|
||||
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
|
||||
glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer));
|
||||
if (render_tex != 0)
|
||||
glsafe(::glDeleteTextures(1, &render_tex));
|
||||
glsafe(::glDeleteFramebuffers(1, &render_fbo));
|
||||
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
|
||||
|
||||
//if (!multisample)
|
||||
// glsafe(::glDisable(GL_MULTISAMPLE));
|
||||
@@ -7579,8 +7579,8 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
|
||||
}
|
||||
else {
|
||||
glsafe(::glGenFramebuffers(1, &render_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
|
||||
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
|
||||
}
|
||||
glsafe(::glGenTextures(1, &render_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
|
||||
@@ -7595,11 +7595,11 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
|
||||
}
|
||||
else {
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
|
||||
glsafe(::glGenRenderbuffers(1, &render_depth));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
|
||||
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height));
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
|
||||
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
|
||||
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
|
||||
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height));
|
||||
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
|
||||
}
|
||||
const GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
@@ -7608,7 +7608,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
|
||||
use_framebuffer = false;
|
||||
}
|
||||
else {
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
|
||||
use_framebuffer = false;
|
||||
}
|
||||
}
|
||||
@@ -7719,11 +7719,11 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
|
||||
glsafe(::glDeleteFramebuffers(1, &render_fbo));
|
||||
}
|
||||
else if (framebuffers_type == OpenGLManager::EFramebufferType::Ext) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
||||
if (render_depth != 0)
|
||||
glsafe(::glDeleteRenderbuffers(1, &render_depth));
|
||||
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
|
||||
if (render_fbo != 0)
|
||||
glsafe(::glDeleteFramebuffers(1, &render_fbo));
|
||||
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
|
||||
}
|
||||
|
||||
if (render_tex != 0)
|
||||
|
||||
+17
-13
@@ -2637,7 +2637,6 @@ void GUI_App::on_start_subscribe_again(std::string dev_id)
|
||||
|
||||
if ( (dev_id == obj->get_dev_id()) && obj->is_connecting() && obj->subscribe_counter > 0) {
|
||||
obj->subscribe_counter--;
|
||||
if(wxGetApp().getAgent()) wxGetApp().getAgent()->set_user_selected_machine(dev_id);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": dev_id=" << obj->get_dev_id();
|
||||
}
|
||||
});
|
||||
@@ -6127,18 +6126,23 @@ bool GUI_App::process_network_msg(std::string dev_id, std::string msg)
|
||||
}
|
||||
else if (msg == "unsigned_studio") {
|
||||
BOOST_LOG_TRIVIAL(info) << "process_network_msg, unsigned_studio";
|
||||
MessageDialog
|
||||
msg_dlg(nullptr,
|
||||
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
||||
"Please go to your printer's settings and:\n"
|
||||
"1. Turn on LAN mode\n"
|
||||
"2. Enable Developer mode\n\n"
|
||||
"Developer mode allows the printer to work exclusively through local network access, "
|
||||
"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;
|
||||
// Plugin re-emits this on every subscribe retry; latch it so it shows
|
||||
// once per connection episode.
|
||||
if (!m_show_error_msgdlg && !m_unsigned_plugin_warning_shown) {
|
||||
m_unsigned_plugin_warning_shown = true;
|
||||
MessageDialog
|
||||
msg_dlg(nullptr,
|
||||
_L("To use OrcaSlicer with Bambu Lab printers, you need to enable LAN mode and Developer mode on your printer.\n\n"
|
||||
"Please go to your printer's settings and:\n"
|
||||
"1. Turn on LAN mode\n"
|
||||
"2. Enable Developer mode\n\n"
|
||||
"Developer mode allows the printer to work exclusively through local network access, "
|
||||
"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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +343,7 @@ private:
|
||||
public:
|
||||
//try again when subscription fails
|
||||
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();
|
||||
bool OnInit() override;
|
||||
int OnExit() override;
|
||||
|
||||
@@ -533,11 +533,11 @@ void MenuFactory::append_menu_item_set_visible(wxMenu* menu)
|
||||
void MenuFactory::append_menu_item_delete(wxMenu* menu)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L("Del"), _L("Delete the selected object"),
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Del", "Keyboard Shortcut"), _L("Delete the selected object"),
|
||||
[](wxCommandEvent&) { plater()->remove_selected(); }, "menu_delete", nullptr,
|
||||
[]() { return plater()->can_delete(); }, m_parent);
|
||||
#else
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L("Backspace"), _L("Delete the selected object"),
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete") + "\t" + _L_CONTEXT("Backspace", "Keyboard Shortcut"), _L("Delete the selected object"),
|
||||
[](wxCommandEvent&) { plater()->remove_selected(); }, "", nullptr,
|
||||
[]() { return plater()->can_delete(); }, m_parent);
|
||||
#endif
|
||||
|
||||
@@ -372,7 +372,7 @@ bool GLGizmoAdvancedCut::on_init()
|
||||
|
||||
std::string GLGizmoAdvancedCut::on_get_name() const
|
||||
{
|
||||
return (_(L("Cut"))).ToUTF8().data();
|
||||
return (_L_CONTEXT("Cut", "Cut tool")).ToUTF8().data();
|
||||
}
|
||||
|
||||
void GLGizmoAdvancedCut::on_load(cereal::BinaryInputArchive &ar)
|
||||
@@ -1905,7 +1905,7 @@ bool GLGizmoAdvancedCut::render_slider_double_input(const std::string &label, fl
|
||||
mean_size *= float(units_mm_to_in);
|
||||
min_size *= float(units_mm_to_in);
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ std::string GLGizmoAssembly::on_get_name() const
|
||||
{
|
||||
if (!on_is_activable() && m_state == EState::Off) {
|
||||
if (wxGetApp().plater()->canvas3D()->get_canvas_type() == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
||||
return _u8L("Assemble") + ":\n" + _u8L("Please confirm explosion ratio = 1 and select at least two volumes.");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool") + ":\n" + _u8L("Please confirm explosion ratio = 1 and select at least two volumes.");
|
||||
}
|
||||
else {
|
||||
return _u8L("Assemble") + ":\n" + _u8L("Please select at least two volumes.");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool") + ":\n" + _u8L("Please select at least two volumes.");
|
||||
}
|
||||
} else {
|
||||
return _u8L("Assemble");
|
||||
return _u8L_CONTEXT("Assemble", "Assembly tool");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ std::string GLGizmoCut3D::get_tooltip() const
|
||||
std::string tooltip;
|
||||
if (m_hover_id == Z || (m_dragging && m_hover_id == CutPlane)) {
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
std::string unit_str = " " + (m_imperial_units ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
std::string unit_str = " " + (m_imperial_units ? _u8L_CONTEXT("in", "inches") : _u8L("mm"));
|
||||
const BoundingBoxf3& tbb = m_transformed_bounding_box;
|
||||
|
||||
const std::string name = m_keep_as_parts ? _u8L("Part") : _u8L("Object");
|
||||
@@ -541,7 +541,7 @@ bool GLGizmoCut3D::render_double_input(const std::string& label, double& value_i
|
||||
ImGui::InputDouble(("##" + label).c_str(), &value, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
|
||||
|
||||
ImGui::SameLine();
|
||||
m_imgui->text(m_imperial_units ? _CTX("in", "inches") : _L("mm"));
|
||||
m_imgui->text(m_imperial_units ? _L_CONTEXT("in", "inches") : _L("mm"));
|
||||
|
||||
value_in = value * (m_imperial_units ? GizmoObjectManipulation::in_to_mm : 1.0);
|
||||
return !is_approx(old_val, value);
|
||||
@@ -582,7 +582,7 @@ bool GLGizmoCut3D::render_slider_two_input(const std::string& label, float& valu
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, mean_size, format.c_str());
|
||||
|
||||
@@ -653,7 +653,7 @@ bool GLGizmoCut3D::render_slider_input(const std::string& label, float& value_in
|
||||
if (m_imperial_units) {
|
||||
min_size *= f_mm_to_in;
|
||||
}
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _CTX_utf8("in", "inches") : "%.2f " + _u8L("mm");
|
||||
std::string format = value_in < 0.f ? " " : m_imperial_units ? "%.4f " + _u8L_CONTEXT("in", "inches") : "%.2f " + _u8L("mm");
|
||||
|
||||
m_imgui->bbl_slider_float_style(("##" + label).c_str(), &value, min_size, max_value, format.c_str());
|
||||
|
||||
@@ -1386,7 +1386,7 @@ void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const
|
||||
|
||||
std::string GLGizmoCut3D::on_get_name() const
|
||||
{
|
||||
return _u8L("Cut");
|
||||
return _u8L_CONTEXT("Cut", "Cut tool");
|
||||
}
|
||||
|
||||
void GLGizmoCut3D::apply_color_clip_plane_colors()
|
||||
@@ -2472,7 +2472,7 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors, flo
|
||||
void GLGizmoCut3D::render_build_size()
|
||||
{
|
||||
double koef = m_imperial_units ? GizmoObjectManipulation::mm_to_in : 1.0;
|
||||
wxString unit_str = m_imperial_units ? _CTX("in", "inches") : _L("mm");
|
||||
wxString unit_str = m_imperial_units ? _L_CONTEXT("in", "inches") : _L("mm");
|
||||
Vec3d tbb_sz = m_transformed_bounding_box.size() * koef; // ORCA
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
|
||||
@@ -1961,7 +1961,7 @@ void GLGizmoEmboss::draw_model_type()
|
||||
|
||||
ImGui::SameLine();
|
||||
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
|
||||
if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
if (ImGui::RadioButton(_u8L_CONTEXT(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
new_type = negative;
|
||||
else if (ImGui::IsItemHovered()) {
|
||||
if (is_last_solid_part)
|
||||
@@ -2811,29 +2811,29 @@ void GLGizmoEmboss::draw_advanced()
|
||||
ImGui::SameLine(input_offset);
|
||||
if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Left", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Center", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Center", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Right", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.x * 2.f); // ORCA use wider spacing for separation between horizontal / vertical alignment
|
||||
|
||||
if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Top", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Middle", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
else if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L_CONTEXT(L_CONTEXT("Bottom", "Alignment"), "Alignment"), m_gui_cfg->max_tooltip_width);
|
||||
return is_change;
|
||||
};
|
||||
const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr;
|
||||
|
||||
@@ -455,8 +455,8 @@ bool GLGizmoMeasure::on_init()
|
||||
m_shortcuts = {
|
||||
{_L("Left mouse button"), _L("Select")},
|
||||
{shift + _L("Left mouse button"), _L("Select point")},
|
||||
{_L("Delete"), _L("Restart selection")},
|
||||
{_L("Esc"), _L("Cancel a feature until exit")},
|
||||
{_L_CONTEXT("Delete", "Keyboard Shortcut"), _L("Restart selection")},
|
||||
{_L_CONTEXT("Esc", "Keyboard Shortcut"), _L("Cancel a feature until exit")},
|
||||
};
|
||||
|
||||
return true;
|
||||
@@ -1252,7 +1252,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
const bool use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance;
|
||||
const std::string curr_value_str = format_double(curr_value);
|
||||
const std::string units = use_inches ? _CTX_utf8("in", "inches") : _u8L("mm");
|
||||
const std::string units = use_inches ? _u8L_CONTEXT("in", "inches") : _u8L("mm");
|
||||
const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x;
|
||||
static double edit_value = 0.0;
|
||||
|
||||
@@ -1303,7 +1303,7 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
return;
|
||||
|
||||
const double ratio = new_value / old_value;
|
||||
wxGetApp().plater()->take_snapshot(_CTX_utf8("Scale", "Verb"), UndoRedo::SnapshotType::GizmoAction);
|
||||
wxGetApp().plater()->take_snapshot(_u8L_CONTEXT("Scale", "Verb"), UndoRedo::SnapshotType::GizmoAction);
|
||||
// apply scale
|
||||
TransformationType type;
|
||||
type.set_world();
|
||||
@@ -1359,10 +1359,10 @@ void GLGizmoMeasure::render_dimensioning()
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGuiWrapper::push_confirm_button_style();
|
||||
//if (m_imgui->button(_CTX(L_CONTEXT("Scale part", "Verb"), "Verb")))
|
||||
//if (m_imgui->button(_L_CONTEXT(L_CONTEXT("Scale part", "Verb"), "Verb")))
|
||||
//action_scale(edit_value, curr_value, true);
|
||||
//ImGui::SameLine();
|
||||
if (m_imgui->button(_CTX(L_CONTEXT("Scale all", "Verb"), "Verb")))
|
||||
if (m_imgui->button(_L_CONTEXT(L_CONTEXT("Scale all", "Verb"), "Verb")))
|
||||
action_scale(edit_value, curr_value, false);
|
||||
ImGuiWrapper::pop_confirm_button_style();
|
||||
ImGui::SameLine();
|
||||
@@ -2130,7 +2130,7 @@ void GLGizmoMeasure::show_face_face_assembly_senior()
|
||||
void GLGizmoMeasure::init_render_input_window()
|
||||
{
|
||||
m_use_inches = wxGetApp().app_config->get_bool("use_inches");
|
||||
m_units = " " + (m_use_inches ? _CTX_utf8("in", "inches") : _u8L("mm"));
|
||||
m_units = " " + (m_use_inches ? _u8L_CONTEXT("in", "inches") : _u8L("mm"));
|
||||
m_space_size = ImGui::CalcTextSize(" ").x * 2;
|
||||
m_input_size_max = ImGui::CalcTextSize("-100.00").x * 1.2;
|
||||
m_same_model_object = is_two_volume_in_same_model_object();
|
||||
|
||||
@@ -122,7 +122,7 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
std::pair<wxString, wxString> paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
|
||||
std::pair<wxString, wxString> erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
|
||||
std::pair<wxString, wxString> clipping_shortcut = {alt + _L("Mouse wheel"), m_desc["clipping_of_view"]};
|
||||
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L("Enter"), m_desc["toggle_wireframe"]};
|
||||
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]};
|
||||
|
||||
m_shortcuts_brush = {
|
||||
paint_shortcut,
|
||||
|
||||
@@ -1704,7 +1704,7 @@ void GLGizmoSVG::draw_size()
|
||||
|
||||
if (m_keep_ratio) {
|
||||
std::stringstream ss;
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? _CTX("in", "inches") : _L("mm"));
|
||||
ss << std::setprecision(2) << std::fixed << width << " x " << height << " " << (use_inch ? _L_CONTEXT("in", "inches") : _L("mm"));
|
||||
|
||||
ImGui::SameLine(m_gui_cfg->input_offset);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
@@ -2016,7 +2016,7 @@ void GLGizmoSVG::draw_model_type()
|
||||
ImGui::SameLine();
|
||||
|
||||
std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object.");
|
||||
if (ImGui::RadioButton(_CTX_utf8(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
if (ImGui::RadioButton(_u8L_CONTEXT(L_CONTEXT("Cut", "EmbossOperation"), "EmbossOperation").c_str(), type == negative))
|
||||
new_type = negative;
|
||||
else if (ImGui::IsItemHovered()) {
|
||||
if (is_last_solid_part)
|
||||
|
||||
@@ -143,9 +143,9 @@ bool GLGizmoScale3D::on_init()
|
||||
std::string GLGizmoScale3D::on_get_name() const
|
||||
{
|
||||
if (!on_is_activable() && m_state == EState::Off) {
|
||||
return _CTX_utf8("Scale", "Verb") + ":\n" + _u8L("Please select at least one object.");
|
||||
return _u8L_CONTEXT("Scale", "Verb") + ":\n" + _u8L("Please select at least one object.");
|
||||
} else {
|
||||
return _CTX_utf8("Scale", "Verb");
|
||||
return _u8L_CONTEXT("Scale", "Verb");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1213,8 +1213,8 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog()
|
||||
shortcuts.push_back(std::make_pair(_L"Del", _L("Remove selected points")));
|
||||
shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane")));
|
||||
shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane")));
|
||||
shortcuts.push_back(std::make_pair(_L("Enter"), _L("Apply changes")));
|
||||
shortcuts.push_back(std::make_pair(_L("Esc"), _L("Discard changes")));
|
||||
shortcuts.push_back(std::make_pair(_L_CONTEXT("Enter", "Keyboard Shortcut"), _L("Apply changes")));
|
||||
shortcuts.push_back(std::make_pair(_L_CONTEXT("Esc", "Keyboard Shortcut"), _L("Discard changes")));
|
||||
shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode")));
|
||||
shortcuts.push_back(std::make_pair("A", _L("Auto-generate points")));
|
||||
|
||||
|
||||
@@ -1161,7 +1161,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
imgui_wrapper->calc_text_size(_L("Part")).x
|
||||
}) + imgui_wrapper->calc_text_size("xxx"sv).x + imgui_wrapper->scaled(3.5f);
|
||||
float label_max = std::max({
|
||||
imgui_wrapper->calc_text_size(_CTX("Scale", "Noun")).x,
|
||||
imgui_wrapper->calc_text_size(_L_CONTEXT("Scale", "Noun")).x,
|
||||
imgui_wrapper->calc_text_size(_L("Size")).x
|
||||
});
|
||||
float caption_max = std::max(label_max, coord_combo_width - 3 * space_size);
|
||||
@@ -1218,7 +1218,7 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
|
||||
|
||||
//ImGui::PushItemWidth(unit_size * 2);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui_wrapper->text(_CTX("Scale", "Noun"));
|
||||
imgui_wrapper->text(_L_CONTEXT("Scale", "Noun"));
|
||||
ImGui::SameLine(caption_max + space_size);
|
||||
ImGui::PushItemWidth(unit_size);
|
||||
ImGui::BBLInputDouble(label_scale_values[0][0], &scale[0], 0.0f, 0.0f, "%.2f");
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#define _u8L(s) Slic3r::GUI::I18N::translate_utf8((s))
|
||||
#endif /* _ */
|
||||
|
||||
#ifndef _CTX
|
||||
#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
|
||||
#define _CTX_utf8(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
|
||||
#ifndef _L_CONTEXT
|
||||
#define _L_CONTEXT(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx))
|
||||
#define _u8L_CONTEXT(s, ctx) Slic3r::GUI::I18N::translate_utf8((s), (ctx))
|
||||
#endif /* _ */
|
||||
|
||||
#ifndef L
|
||||
|
||||
@@ -644,7 +644,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
|
||||
if (m_file_sys->GetFileType() == PrinterFileSystem::F_MODEL) {
|
||||
if (secondAction != _L("Play"))
|
||||
thirdAction = secondAction;
|
||||
secondAction = _CTX("Print", "Verb");
|
||||
secondAction = _L_CONTEXT("Print", "Verb");
|
||||
}
|
||||
// Draw buttons on hovered item
|
||||
wxRect rect{pt.x, pt.y + m_content_rect.GetBottom() - m_buttons_background.GetHeight(), m_content_rect.GetWidth(), m_buttons_background.GetHeight()};
|
||||
|
||||
@@ -201,7 +201,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
#ifdef __APPLE__
|
||||
{"fn+⌫", L("Delete Selected")},
|
||||
#else
|
||||
{L("Del"), L("Delete Selected")},
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete Selected")},
|
||||
#endif
|
||||
// Help
|
||||
{ "?", L("Show keyboard shortcuts list") }
|
||||
@@ -231,12 +231,12 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{alt + L("Left mouse button"), L("Select a part")},
|
||||
{ctrl + L("Left mouse button"), L("Select multiple objects")},
|
||||
{shift + L("Left mouse button"), L("Select objects by rectangle")},
|
||||
{L("Arrow Up"), L("Move selection 10mm in positive Y direction")},
|
||||
{L("Arrow Down"), L("Move selection 10mm in negative Y direction")},
|
||||
{L("Arrow Left"), L("Move selection 10mm in negative X direction")},
|
||||
{L("Arrow Right"), L("Move selection 10mm in positive X direction")},
|
||||
{L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Move selection 10mm in positive Y direction")},
|
||||
{L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Move selection 10mm in negative Y direction")},
|
||||
{L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Move selection 10mm in negative X direction")},
|
||||
{L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Move selection 10mm in positive X direction")},
|
||||
{shift + L("Any arrow"), L("Movement step set to 1mm")},
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{"1-9", L("Keyboard 1-9: set filament for object/part")},
|
||||
{ctrl + "0", L("Camera view - Default")},
|
||||
{ctrl + "1", L("Camera view - Top")},
|
||||
@@ -266,14 +266,14 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ "I", L("Zoom in") },
|
||||
{ "O", L("Zoom out") },
|
||||
{ "V", L("Toggle printable for object/part") },
|
||||
{ L("Tab"), L("Switch between Prepare/Preview") },
|
||||
{ L("Space"), L("Open actions speed dial") },
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview") },
|
||||
{ L_CONTEXT("Space", "Keyboard Shortcut"), L("Open actions speed dial") },
|
||||
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Plater"), "" }, plater_shortcuts });
|
||||
|
||||
Shortcuts gizmos_shortcuts = {
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{shift, L("Move: press to snap by 1mm")},
|
||||
{ctrl + L("Mouse wheel"), L("Support/Color Painting: adjust pen radius")},
|
||||
{alt + L("Mouse wheel"), L("Support/Color Painting: adjust section position")},
|
||||
@@ -282,8 +282,8 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
|
||||
Shortcuts object_list_shortcuts = {
|
||||
{"1-9", L("Set extruder number for the objects and parts") },
|
||||
{L("Del"), L("Delete objects, parts, modifiers")},
|
||||
{L("Esc"), L("Deselect All")},
|
||||
{L_CONTEXT("Del", "Keyboard Shortcut"), L("Delete objects, parts, modifiers")},
|
||||
{L_CONTEXT("Esc", "Keyboard Shortcut"), L("Deselect All")},
|
||||
{ctrl + "C", L("Copy to clipboard")},
|
||||
{ctrl + "V", L("Paste from clipboard")},
|
||||
{ctrl + "X", L("Cut")},
|
||||
@@ -291,26 +291,26 @@ void KBShortcutsDialog::fill_shortcuts()
|
||||
{ctrl + "K", L("Clone Selected")},
|
||||
{ctrl + "Z", L("Undo")},
|
||||
{ctrl + "Y", L("Redo")},
|
||||
{L("Space"), L("Select the object/part and press space to change the name")},
|
||||
{L_CONTEXT("Space", "Keyboard Shortcut"), L("Select the object/part and press space to change the name")},
|
||||
{L("Mouse click"), L("Select the object/part and mouse click to change the name")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts });
|
||||
}
|
||||
|
||||
Shortcuts preview_shortcuts = {
|
||||
{ L("Arrow Up"), L("Vertical slider - Move active thumb Up")},
|
||||
{ L("Arrow Down"), L("Vertical slider - Move active thumb Down")},
|
||||
{ L("Arrow Left"), L("Horizontal slider - Move active thumb Left")},
|
||||
{ L("Arrow Right"), L("Horizontal slider - Move active thumb Right")},
|
||||
{ L_CONTEXT("Arrow Up", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Up")},
|
||||
{ L_CONTEXT("Arrow Down", "Keyboard Shortcut"), L("Vertical slider - Move active thumb Down")},
|
||||
{ L_CONTEXT("Arrow Left", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Left")},
|
||||
{ L_CONTEXT("Arrow Right", "Keyboard Shortcut"), L("Horizontal slider - Move active thumb Right")},
|
||||
{ "L", L("On/Off one layer mode of the vertical slider")},
|
||||
{ "C", L("On/Off G-code window")},
|
||||
{ L("Tab"), L("Switch between Prepare/Preview")},
|
||||
{ L_CONTEXT("Tab", "Keyboard Shortcut"), L("Switch between Prepare/Preview")},
|
||||
{shift + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{shift + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Any arrow"), L("Move slider 5x faster")},
|
||||
{ctrl + L("Mouse wheel"), L("Move slider 5x faster")},
|
||||
{ L("Home"), L("Horizontal slider - Move to start position")},
|
||||
{ L("End"), L("Horizontal slider - Move to last position")},
|
||||
{ L_CONTEXT("Home", "Keyboard Shortcut"), L("Horizontal slider - Move to start position")},
|
||||
{ L_CONTEXT("End", "Keyboard Shortcut"), L("Horizontal slider - Move to last position")},
|
||||
};
|
||||
m_full_shortcuts.push_back({ { _L("Preview"), "" }, preview_shortcuts });
|
||||
}
|
||||
@@ -341,7 +341,10 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const ShortcutsItem& s
|
||||
|
||||
for (int i = 0; i < items_count; ++i) {
|
||||
const auto &[shortcut, description] = shortcuts.second[i];
|
||||
auto key = new wxStaticText(scrollable_panel, wxID_ANY, _(shortcut));
|
||||
// Keyboard keys carry a "Keyboard Shortcut" context so translators keep them in English;
|
||||
// mouse-input labels are ordinary phrases and use the plain lookup.
|
||||
const bool is_mouse = shortcut.find("Mouse") != std::string::npos || shortcut.find("mouse") != std::string::npos;
|
||||
auto key = new wxStaticText(scrollable_panel, wxID_ANY, is_mouse ? _(shortcut) : _L_CONTEXT(shortcut, "Keyboard Shortcut"));
|
||||
key->SetForegroundColour(wxColour(50, 58, 61));
|
||||
key->SetFont(bold_font);
|
||||
grid_sizer->Add(key, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
|
||||
@@ -215,8 +215,8 @@ GCodeInputData convert(const Slic3r::GCodeProcessorResult& result, const std::ve
|
||||
const EOptionType option_type = move_type_to_option(curr_type);
|
||||
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
|
||||
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
||||
|| prev.mm3_per_mm != curr.mm3_per_mm) {
|
||||
// ORCA: Split the path when a preview value changes.
|
||||
|| 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
|
||||
// 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user