Merge branch 'main' into main

This commit is contained in:
Rodrigo Faselli
2026-07-25 19:19:07 -03:00
committed by GitHub
164 changed files with 22716 additions and 9447 deletions
+1 -1
View File
@@ -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
View File
@@ -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()
+25 -7
View File
@@ -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
![mac_security_setting](./SoftFever_doc/mac_security_setting.png)
</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>
+4
View File
@@ -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
View File
@@ -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
+390 -110
View File
@@ -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
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 -1
View File
@@ -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>
+2 -34
View File
@@ -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.
+105
View File
@@ -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"]
});
}
@@ -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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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":
+618
View File
@@ -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)
+1 -1
View File
@@ -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 (
+1 -1
View File
@@ -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
-4
View File
@@ -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
View File
@@ -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());
}
+21 -4
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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");
+1 -1
View File
@@ -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>();
+9 -2
View File
@@ -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
+7 -1
View File
@@ -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;
+3 -3
View File
@@ -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
View File
@@ -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()) {
+7 -2
View File
@@ -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;
+15
View File
@@ -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);
}
}
+4
View File
@@ -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
+10
View File
@@ -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;
+197 -19
View File
@@ -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;
}
+13
View File
@@ -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);
+98 -5
View File
@@ -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 &center, 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 &&
+15
View File
@@ -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 &center, 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);
+13 -2
View File
@@ -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 &region_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 &region_config, const PrintObjectConfig &object_config, const PrintConfig &print_config);
} // namespace Slic3r
namespace boost::polygon {
+14 -5
View File
@@ -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);
+5
View File
@@ -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
View File
@@ -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);
+33 -18
View File
@@ -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
};
+2
View File
@@ -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
View File
@@ -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";
+7 -5
View File
@@ -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
+7
View File
@@ -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;
+3
View File
@@ -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
+10
View File
@@ -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);
+46 -5
View File
@@ -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;
+4
View File
@@ -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;
+1 -1
View File
@@ -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))
+5 -5
View File
@@ -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)) {
+1 -1
View File
@@ -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);
+33 -13
View File
@@ -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());
}
}
}
+3
View File
@@ -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"));
+1 -1
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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.
+5 -2
View File
@@ -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; }
}
+4
View File
@@ -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; }
+43 -43
View File
@@ -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
View File
@@ -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;
}
}
+1
View File
@@ -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;
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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());
+3 -3
View File
@@ -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");
}
}
+6 -6
View File
@@ -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();
+7 -7
View File
@@ -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;
+7 -7
View File
@@ -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,
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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");
}
}
+2 -2
View File
@@ -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");
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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()};
+23 -20
View File
@@ -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