mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ead05bdd |
@@ -13,7 +13,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v7
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ ctest --test-dir ./tests/fff_print
|
||||
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
|
||||
- `#pragma once` for headers. Smart pointers and RAII preferred
|
||||
- Parallelization via TBB — be mindful of shared state
|
||||
- Always use `SetSizerAndFit(sizer)` instead of `SetSizer(sizer)` on top level window. Unless `SetSizer` must be called before the full layout is built, call `sizer->SetSizeHints(window)` afterwards in this case.
|
||||
|
||||
## Key Entry Points
|
||||
|
||||
@@ -60,31 +59,7 @@ ctest --test-dir ./tests/fff_print
|
||||
|
||||
## Localization & translations
|
||||
|
||||
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
|
||||
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_guide.md) for the human-facing version of these principles.
|
||||
|
||||
### Terminology
|
||||
|
||||
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync.
|
||||
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
||||
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
||||
|
||||
### Editing rules
|
||||
|
||||
- Only edit `msgstr` — **never** change `msgid`, and never "fix" wrong English in the translation alone. Report the source string instead.
|
||||
- Preserve exactly: placeholders (`%s`, `%d`, `%1%`, `%zu`, `%%`), every `\n` (count *and* position, including leading/trailing), leading/trailing spaces, HTML tags, `℃`, and the file's encoding and line endings.
|
||||
- **Never reorder positional arguments** in a `c-format` string. If the msgid is `%d` then `%s`, that order must hold — swapping them breaks at runtime.
|
||||
- `msgctxt` separates homonyms — always read it. `Back`/`Camera View` is the rear view of the 3D navigator, while `Back`/`Navigation` is the go-back button; `Top` exists in the *Alignment*, *Layers* and *Camera View* senses.
|
||||
- When a string needs disambiguating, add context in the source (`_L_CONTEXT`/`_u8L_CONTEXT`), don't work around it in the translation.
|
||||
- A literal `%` inside a string xgettext flagged `possible-c-format` will fail `msgfmt`. Fix it with a `// xgettext:no-c-format, no-boost-format` comment above the string in the source — do not mangle the translation or use `%%` in text that is never passed through printf.
|
||||
- Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian.
|
||||
- An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete.
|
||||
- Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite.
|
||||
- Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed.
|
||||
|
||||
### Verifying
|
||||
|
||||
- `scripts/run_gettext.bat --full` (Windows) regenerates the template, merges every catalog and compiles the `.mo` files. It must exit 0.
|
||||
- Or check a single catalog with `msgfmt --check-format -o <out>.mo localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- Fuzzy entries are not shown to users. If you correct one, clear its `fuzzy` flag, otherwise the fix never ships.
|
||||
- Translation catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- When creating or reviewing translations, use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language and terms that must stay in English (brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary so they stay in sync.
|
||||
- Only edit `msgstr` (never `msgid`); keep placeholders (`%s`, `%1%`, `\n`), context (`msgctxt`), and file encoding/line endings intact.
|
||||
|
||||
@@ -119,10 +119,6 @@ Download the **Windows Installer exe** for your preferred version from the [rele
|
||||
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
|
||||
</details>
|
||||
|
||||
### Microsoft Store
|
||||
|
||||
Install from the [Microsoft Store](https://apps.microsoft.com/detail/9mv6gl23xm59) when you prefer a Store-signed package (helps on Windows 11 Smart App Control).
|
||||
|
||||
### Windows Package Manager
|
||||
|
||||
```shell
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-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"
|
||||
@@ -982,7 +982,7 @@ msgstr ""
|
||||
#, possible-boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -3385,6 +3385,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -3608,9 +3609,6 @@ msgstr ""
|
||||
msgid "Arranging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging "
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging canceled."
|
||||
msgstr ""
|
||||
|
||||
@@ -5472,24 +5470,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5870,13 +5854,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -8664,17 +8654,6 @@ 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 "Dimmed layer brightness"
|
||||
msgstr ""
|
||||
|
||||
msgid "%"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -12371,26 +12350,12 @@ msgstr ""
|
||||
msgid "Intra-layer order"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n"
|
||||
"\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The object instance order is decided once for the whole print, while the ordering of individual islands is decided per layer, so different layers may end up using different strategies. Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of many small parts.\n"
|
||||
"\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: objects are grouped by filament first and this setting only orders the instances within each filament group, so the overall sequence may not look like the shortest path across the plate."
|
||||
msgid "Print order within a single layer."
|
||||
msgstr ""
|
||||
|
||||
msgid "As object list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Best of all (shortest path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Snake"
|
||||
msgstr ""
|
||||
|
||||
msgid "Slow printing down for better layer cooling"
|
||||
msgstr ""
|
||||
|
||||
@@ -14764,7 +14729,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, possible-c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -17246,15 +17211,6 @@ msgid ""
|
||||
"Please select one that should be used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-scale for nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."
|
||||
msgstr ""
|
||||
|
||||
msgid "PA Calibration"
|
||||
msgstr ""
|
||||
|
||||
@@ -17377,12 +17333,6 @@ msgstr ""
|
||||
msgid "End speed: "
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-adjust to max volumetric speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer height (keeping standard values and staying within the machine's limits) to reach it. If even the minimum layer height is not enough, lower the end speed instead."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Please input valid values:\n"
|
||||
"start > 10\n"
|
||||
@@ -17390,39 +17340,6 @@ msgid ""
|
||||
"end > start + step"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and layer height.\n"
|
||||
" Speeds above this will be clamped, so the upper blocks of the tower will not print at the requested speed.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s) at the default layer height (%.2f mm).\n"
|
||||
"\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's profiles) so the tower can reach the requested speed."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"Even at the smallest layer height used by this printer's profiles (%.2f mm) the end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n"
|
||||
"\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start retraction length: "
|
||||
msgstr ""
|
||||
|
||||
|
||||
+1434
-2418
File diff suppressed because it is too large
Load Diff
+1403
-2339
File diff suppressed because it is too large
Load Diff
+929
-1146
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-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"
|
||||
@@ -978,7 +978,7 @@ msgstr ""
|
||||
#, boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -3381,6 +3381,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -3604,9 +3605,6 @@ msgstr ""
|
||||
msgid "Arranging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging "
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging canceled."
|
||||
msgstr ""
|
||||
|
||||
@@ -5468,24 +5466,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5866,13 +5850,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -8660,17 +8650,6 @@ 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 "Dimmed layer brightness"
|
||||
msgstr ""
|
||||
|
||||
msgid "%"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -12367,26 +12346,12 @@ msgstr ""
|
||||
msgid "Intra-layer order"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n"
|
||||
"\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The object instance order is decided once for the whole print, while the ordering of individual islands is decided per layer, so different layers may end up using different strategies. Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of many small parts.\n"
|
||||
"\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: objects are grouped by filament first and this setting only orders the instances within each filament group, so the overall sequence may not look like the shortest path across the plate."
|
||||
msgid "Print order within a single layer."
|
||||
msgstr ""
|
||||
|
||||
msgid "As object list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Best of all (shortest path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Snake"
|
||||
msgstr ""
|
||||
|
||||
msgid "Slow printing down for better layer cooling"
|
||||
msgstr ""
|
||||
|
||||
@@ -14760,7 +14725,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -17242,15 +17207,6 @@ msgid ""
|
||||
"Please select one that should be used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-scale for nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."
|
||||
msgstr ""
|
||||
|
||||
msgid "PA Calibration"
|
||||
msgstr ""
|
||||
|
||||
@@ -17373,12 +17329,6 @@ msgstr ""
|
||||
msgid "End speed: "
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-adjust to max volumetric speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer height (keeping standard values and staying within the machine's limits) to reach it. If even the minimum layer height is not enough, lower the end speed instead."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Please input valid values:\n"
|
||||
"start > 10\n"
|
||||
@@ -17386,39 +17336,6 @@ msgid ""
|
||||
"end > start + step"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and layer height.\n"
|
||||
" Speeds above this will be clamped, so the upper blocks of the tower will not print at the requested speed.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s) at the default layer height (%.2f mm).\n"
|
||||
"\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's profiles) so the tower can reach the requested speed."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"Even at the smallest layer height used by this printer's profiles (%.2f mm) the end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n"
|
||||
"\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start retraction length: "
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-0300\n"
|
||||
"POT-Creation-Date: 2026-07-23 15:24-0300\n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Ian A. Bassi <>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -983,11 +983,11 @@ msgstr "Conector"
|
||||
#, boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
"Los objetos(%1%) tienen conectores duplicados. Es posible que falten algunos conectores en el resultado del laminado.\n"
|
||||
"Informe al equipo de OrcaSlicer sobre el escenario en el que se produjo este problema.\n"
|
||||
"Informe al equipo de PrusaSlicer sobre el escenario en el que se produjo este problema.\n"
|
||||
"Gracias."
|
||||
|
||||
msgid "Cut by Plane"
|
||||
@@ -3459,6 +3459,7 @@ msgstr "Recámara"
|
||||
msgid "Innerloop"
|
||||
msgstr "Bucle interno"
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr "Superior"
|
||||
|
||||
@@ -3686,10 +3687,6 @@ msgstr "Organizando..."
|
||||
msgid "Arranging"
|
||||
msgstr "Organizando"
|
||||
|
||||
# AI Translated
|
||||
msgid "Arranging "
|
||||
msgstr "Organizando "
|
||||
|
||||
msgid "Arranging canceled."
|
||||
msgstr "Organización cancelada."
|
||||
|
||||
@@ -5633,27 +5630,10 @@ msgstr "Evitar la zona de calibración del extrusor"
|
||||
msgid "Align to Y axis"
|
||||
msgstr "Alinear con el eje Y"
|
||||
|
||||
# AI Translated
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr "Frontal"
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr "Posterior"
|
||||
|
||||
# AI Translated
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr "Superior"
|
||||
|
||||
# AI Translated
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr "Inferior"
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr "Izquierda"
|
||||
@@ -6040,14 +6020,19 @@ msgstr "Vista por Defecto"
|
||||
msgid "Top View"
|
||||
msgstr "Vista superior"
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr "Inferior"
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr "Vista inferior"
|
||||
|
||||
msgid "Front"
|
||||
msgstr "Frontal"
|
||||
|
||||
msgid "Front View"
|
||||
msgstr "Vista frontal"
|
||||
|
||||
# AI Translated
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr "Posterior"
|
||||
|
||||
@@ -8935,21 +8920,6 @@ msgstr "Atenuar las capas inferiores"
|
||||
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 "Al desplazar el control deslizante de capas en la vista previa laminada, oscurece las capas inferiores a la actual para que solo la capa visualizada se muestre a pleno brillo."
|
||||
|
||||
# AI Translated
|
||||
msgid "Dimmed layer brightness"
|
||||
msgstr "Brillo de las capas atenuadas"
|
||||
|
||||
msgid "%"
|
||||
msgstr "%"
|
||||
|
||||
# AI Translated
|
||||
msgid ""
|
||||
"How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."
|
||||
msgstr ""
|
||||
"Con qué brillo se muestran las capas atenuadas cuando \"Atenuar las capas inferiores\" está activado.\n"
|
||||
"99% apenas se oscurece y 0% las muestra en negro. Está limitado al 99% porque el 100% equivaldría a desactivar la opción."
|
||||
|
||||
msgid "Login region"
|
||||
msgstr "Región de inicio de sesión"
|
||||
|
||||
@@ -12900,37 +12870,12 @@ msgstr "Por objeto"
|
||||
msgid "Intra-layer order"
|
||||
msgstr "Orden dentro de la capa"
|
||||
|
||||
# AI Translated
|
||||
msgid ""
|
||||
"Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n"
|
||||
"\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The object instance order is decided once for the whole print, while the ordering of individual islands is decided per layer, so different layers may end up using different strategies. Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of many small parts.\n"
|
||||
"\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: objects are grouped by filament first and this setting only orders the instances within each filament group, so the overall sequence may not look like the shortest path across the plate."
|
||||
msgstr ""
|
||||
"Orden en el que se recorren las instancias de objeto dentro de una misma capa, lo que determina cuánto desplazamiento se emplea para moverse entre ellas.\n"
|
||||
"\n"
|
||||
"Por defecto: encadenado por vecino más cercano, refinado con 2-opt y eliminación de cruces. Una buena opción general.\n"
|
||||
"Como lista de objetos: las instancias se imprimen en el mismo orden que la lista de objetos, sin ninguna optimización de trayectoria. Úselo cuando necesite un orden predecible y controlado manualmente.\n"
|
||||
"La mejor de todas (trayectoria más corta): se evalúan todas las estrategias y se utiliza la más corta. El orden de las instancias de objeto se decide una sola vez para toda la impresión, mientras que el orden de las islas individuales se decide capa por capa, por lo que distintas capas pueden acabar usando estrategias diferentes. El laminado es algo más lento.\n"
|
||||
"Serpentina: recorrido serpenteante fila por fila, refinado con 2-opt. Muy adecuado para rejillas regulares de muchas piezas pequeñas.\n"
|
||||
"\n"
|
||||
"Con varios filamentos o herramientas en la misma capa, minimizar los cambios de herramienta tiene prioridad: los objetos se agrupan primero por filamento y este ajuste solo ordena las instancias dentro de cada grupo de filamento, por lo que la secuencia global puede no parecer la trayectoria más corta a lo largo de la cama."
|
||||
msgid "Print order within a single layer."
|
||||
msgstr "Orden de impresión dentro de cada capa."
|
||||
|
||||
msgid "As object list"
|
||||
msgstr "Como lista de objetos"
|
||||
|
||||
# AI Translated
|
||||
msgid "Best of all (shortest path)"
|
||||
msgstr "La mejor de todas (trayectoria más corta)"
|
||||
|
||||
# AI Translated
|
||||
msgid "Snake"
|
||||
msgstr "Serpentina"
|
||||
|
||||
msgid "Slow printing down for better layer cooling"
|
||||
msgstr "Reducir la velocidad de impresión para mejorar la refrigeración de las capas"
|
||||
|
||||
@@ -15545,7 +15490,7 @@ msgstr "La longitud de la retracción rápida antes de la purga, en relación co
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr "Cantidad de retracción después de la limpieza"
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -18166,20 +18111,6 @@ msgstr ""
|
||||
"Hay varias direcciones IP resueltas del nombre del host %1%.\n"
|
||||
"Por favor, seleccione la que debe usarse."
|
||||
|
||||
# AI Translated
|
||||
msgid "Auto-scale for nozzle"
|
||||
msgstr "Escalar automáticamente para la boquilla"
|
||||
|
||||
# AI Translated
|
||||
msgid ""
|
||||
"This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."
|
||||
msgstr ""
|
||||
"Este modelo está diseñado para una boquilla de 0,4 mm con una altura de la capa de 0,2 mm. \n"
|
||||
"Cuando la opción de escalado está activada (recomendado), el modelo se redimensiona dinámicamente para adaptarse al diámetro de boquilla actual y a una altura de capa adecuada, lo que hace que la prueba sea precisa y fácil de leer.\n"
|
||||
"Desactive el escalado solo si desea imprimir el modelo de referencia exactamente tal cual."
|
||||
|
||||
msgid "PA Calibration"
|
||||
msgstr "Calibración PA"
|
||||
|
||||
@@ -18316,14 +18247,6 @@ msgstr "Velocidad inicial: "
|
||||
msgid "End speed: "
|
||||
msgstr "Velocidad final: "
|
||||
|
||||
# AI Translated
|
||||
msgid "Auto-adjust to max volumetric speed"
|
||||
msgstr "Ajustar automáticamente a la velocidad volumétrica máxima"
|
||||
|
||||
# AI Translated
|
||||
msgid "If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer height (keeping standard values and staying within the machine's limits) to reach it. If even the minimum layer height is not enough, lower the end speed instead."
|
||||
msgstr "Si la velocidad final superara la velocidad volumétrica máxima del filamento, se reduce automáticamente la altura de la capa (manteniendo valores estándar y dentro de los límites de la máquina) para alcanzarla. Si ni siquiera la altura de capa mínima es suficiente, se reduce la velocidad final."
|
||||
|
||||
msgid ""
|
||||
"Please input valid values:\n"
|
||||
"start > 10\n"
|
||||
@@ -18335,57 +18258,6 @@ msgstr ""
|
||||
"incremento >=0\n"
|
||||
"final > inicio + paso"
|
||||
|
||||
# AI Translated
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and layer height.\n"
|
||||
" Speeds above this will be clamped, so the upper blocks of the tower will not print at the requested speed.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"La velocidad final (%.0f mm/s) supera la velocidad volumétrica máxima del filamento (%.1f mm³/s), lo que limita el perímetro externo a unos %.0f mm/s con este ancho de línea y esta altura de capa.\n"
|
||||
" Las velocidades superiores se recortarán, por lo que los bloques superiores de la torre no se imprimirán a la velocidad solicitada.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
|
||||
# AI Translated
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s) at the default layer height (%.2f mm).\n"
|
||||
"\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's profiles) so the tower can reach the requested speed."
|
||||
msgstr ""
|
||||
"La velocidad final (%.0f mm/s) supera la velocidad volumétrica máxima del filamento (%.1f mm³/s) con la altura de capa predeterminada (%.2f mm).\n"
|
||||
"\n"
|
||||
"La altura de la capa se ha reducido a %.2f mm (un valor usado por los perfiles de esta impresora) para que la torre pueda alcanzar la velocidad solicitada."
|
||||
|
||||
# AI Translated
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"Even at the smallest layer height used by this printer's profiles (%.2f mm) the end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n"
|
||||
"\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
"Incluso con la altura de capa más pequeña usada por los perfiles de esta impresora (%.2f mm), la velocidad final (%.0f mm/s) supera la velocidad volumétrica máxima del filamento (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"La altura de la capa se establecerá en %.2f mm y la velocidad final se reducirá a %.0f mm/s.\n"
|
||||
"\n"
|
||||
"¿Continuar?"
|
||||
|
||||
# AI Translated
|
||||
msgid "Continue anyway?"
|
||||
msgstr "¿Continuar de todos modos?"
|
||||
|
||||
# AI Translated
|
||||
msgid "Enable \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr "¿Activar \"Ajuste automático\" para corregir esto automáticamente o continuar de todos modos?"
|
||||
|
||||
# AI Translated
|
||||
msgid "Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr "¿Activar \"Escalar automáticamente para la boquilla\" y \"Ajuste automático\" para corregir esto automáticamente o continuar de todos modos?"
|
||||
|
||||
msgid "Start retraction length: "
|
||||
msgstr "Longitud de retracción inicial: "
|
||||
|
||||
@@ -20861,18 +20733,6 @@ msgstr ""
|
||||
"Evita la deformación\n"
|
||||
"¿Sabías que al imprimir materiales propensos a la deformación como el ABS, aumentar adecuadamente la temperatura de la cama térmica puede reducir la probabilidad de deformaciones?"
|
||||
|
||||
#~ msgid "Print order within a single layer."
|
||||
#~ msgstr "Orden de impresión dentro de cada capa."
|
||||
|
||||
#~ msgid "Bottom"
|
||||
#~ msgstr "Inferior"
|
||||
|
||||
#~ msgid "Front"
|
||||
#~ msgstr "Frontal"
|
||||
|
||||
#~ msgid "Rear"
|
||||
#~ msgstr "Posterior"
|
||||
|
||||
#~ msgid "Enter"
|
||||
#~ msgstr "Enter"
|
||||
|
||||
@@ -20957,6 +20817,9 @@ msgstr ""
|
||||
#~ msgid "°C"
|
||||
#~ msgstr "°C"
|
||||
|
||||
#~ msgid "%"
|
||||
#~ msgstr "%"
|
||||
|
||||
#~ msgid "Anisotropic surfaces"
|
||||
#~ msgstr "Superficies anisótropas"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1669
-2308
File diff suppressed because it is too large
Load Diff
+1183
-1866
File diff suppressed because it is too large
Load Diff
+1675
-3453
File diff suppressed because it is too large
Load Diff
+1604
-2878
File diff suppressed because it is too large
Load Diff
+432
-1064
File diff suppressed because it is too large
Load Diff
+3162
-6573
File diff suppressed because it is too large
Load Diff
+1710
-3146
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
+3425
-7186
File diff suppressed because it is too large
Load Diff
+514
-1293
File diff suppressed because it is too large
Load Diff
+1403
-2396
File diff suppressed because it is too large
Load Diff
+1461
-2591
File diff suppressed because it is too large
Load Diff
+2223
-4194
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
@@ -70,7 +70,7 @@
|
||||
"wipe_tower_no_sparse_layers": "0",
|
||||
"wipe_tower_cone_angle": "30",
|
||||
"wipe_tower_wall_type": "rib",
|
||||
"wipe_tower_extra_rib_length": "0",
|
||||
"wipe_tower_extra_rib_length": "8",
|
||||
"prime_tower_width": "35",
|
||||
"prime_volume": "30",
|
||||
"wall_generator": "arachne",
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
//BBS: add grey and orange
|
||||
@@ -39,14 +36,7 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -109,85 +99,43 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
{
|
||||
// -------------------------------------------
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// r0 : \ : r1
|
||||
// : \ :
|
||||
// : \ :
|
||||
// o---x2 ___ x3
|
||||
//
|
||||
// r0 and r1 are the differences between actual
|
||||
// and expected (as if x0..3 where on the same
|
||||
// plane) depth values.
|
||||
// -------------------------------------------
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
|
||||
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
|
||||
|
||||
float r0 = x1 + d0 - x2;
|
||||
float r1 = x2 + d1 - x1;
|
||||
|
||||
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
}
|
||||
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(coord, ivec2(1,0), s), // Horizontal
|
||||
DetectSilho(coord, ivec2(0,1), s) // Vertical
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)), // Horizontal
|
||||
DetectSilho(fragCoord, vec2(0,1)) // Vertical
|
||||
);
|
||||
}
|
||||
|
||||
// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is
|
||||
@@ -276,7 +224,14 @@ void main()
|
||||
//BBS: add outline_color
|
||||
if (is_outline) {
|
||||
color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a);
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
// Makes silhouettes thicker.
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(color.rgb, getBackfaceColor(color.rgb), s), color.a);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const vec3 LightRed = vec3(0.78, 0.0, 0.0);
|
||||
@@ -54,14 +51,7 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -110,27 +100,12 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
{
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
{
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
@@ -141,43 +116,15 @@ float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
}
|
||||
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(coord, ivec2(1,0), s),
|
||||
DetectSilho(coord, ivec2(0,1), s)
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)),
|
||||
DetectSilho(fragCoord, vec2(0,1))
|
||||
);
|
||||
}
|
||||
|
||||
float compute_ssao_factor(vec3 normal, vec3 view_dir, vec3 eye_pos)
|
||||
@@ -323,7 +270,13 @@ void main()
|
||||
if (is_outline) {
|
||||
vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade;
|
||||
vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a);
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(shaded_color.rgb, getBackfaceColor(shaded_color.rgb), s), shaded_color.a);
|
||||
|
||||
@@ -602,17 +602,15 @@ function SourceLabel(source) {
|
||||
return "Mine";
|
||||
case "subscribed":
|
||||
return "Subscribed";
|
||||
case "orphaned":
|
||||
return "Orphaned";
|
||||
default:
|
||||
return "Local";
|
||||
}
|
||||
}
|
||||
|
||||
// Shared source pill, used both after the row name and in the info panel.
|
||||
// Shared Local/Subscribed/Mine pill, used both after the row name and in the info panel.
|
||||
function SourceBadge(source) {
|
||||
const normalized = String(source || "").toLowerCase();
|
||||
const variant = (normalized === "mine" || normalized === "subscribed" || normalized === "orphaned") ? normalized : "local";
|
||||
const variant = (normalized === "mine" || normalized === "subscribed") ? normalized : "local";
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `plugin-source-badge source-${variant}`;
|
||||
badge.textContent = SourceLabel(source);
|
||||
@@ -656,7 +654,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
|
||||
const labelCell = document.createElement("span");
|
||||
labelCell.className = "label-cell";
|
||||
|
||||
const hasCloudLink = plugin.source === "mine" || plugin.source === "subscribed" || plugin.source === "orphaned";
|
||||
const hasCloudLink = plugin.source === "mine" || plugin.source === "subscribed";
|
||||
const pluginLabelText = plugin.label || plugin.name || plugin.plugin_id || "";
|
||||
const canExpand = capabilityCount > 0;
|
||||
|
||||
@@ -707,7 +705,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
|
||||
function SourceCell(plugin) {
|
||||
const cell = document.createElement("span");
|
||||
const normalized = String(plugin.source || "").toLowerCase();
|
||||
const variant = (normalized === "mine" || normalized === "subscribed" || normalized === "orphaned") ? normalized : "local";
|
||||
const variant = (normalized === "mine" || normalized === "subscribed") ? normalized : "local";
|
||||
cell.className = `source-cell source-${variant}`;
|
||||
|
||||
const sourceLabel = document.createElement("span");
|
||||
@@ -1235,7 +1233,7 @@ function RenderDescription(plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isCloud = plugin && (plugin.source === "mine" || plugin.source === "subscribed" || plugin.source === "orphaned");
|
||||
const isCloud = plugin && (plugin.source === "mine" || plugin.source === "subscribed");
|
||||
if (isCloud && String(plugin?.sharing_token || "")) {
|
||||
node.appendChild(document.createTextNode("View on OrcaCloud "));
|
||||
const link = document.createElement("a");
|
||||
@@ -1372,13 +1370,6 @@ function RenderDetailSummary(container, plugin) {
|
||||
message.textContent = errorText || StatusDescription(plugin);
|
||||
container.appendChild(message);
|
||||
|
||||
if (plugin.orphaned === true) {
|
||||
const warning = document.createElement("div");
|
||||
warning.className = "detail-description detail-warning-text";
|
||||
warning.textContent = "Orphaned: This plugin is no longer subscribed or available in OrcaCloud. The local copy remains installed and can still be used.";
|
||||
container.appendChild(warning);
|
||||
}
|
||||
|
||||
const updateStatus = GetUpdateStatus(plugin);
|
||||
if (updateStatus === "update_available") {
|
||||
const note = document.createElement("div");
|
||||
|
||||
@@ -251,11 +251,6 @@ body.pane-resizing {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.source-cell.source-orphaned {
|
||||
color: var(--plugin-status-warn);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.source-cell.source-local {
|
||||
color: var(--plugin-source-neutral-text);
|
||||
}
|
||||
@@ -653,10 +648,6 @@ body.pane-resizing {
|
||||
color: var(--plugin-status-danger);
|
||||
}
|
||||
|
||||
.detail-warning-text {
|
||||
color: var(--plugin-status-warn);
|
||||
}
|
||||
|
||||
.detail-status-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -924,11 +915,6 @@ body.pane-resizing {
|
||||
color: var(--plugin-source-subscribed-text);
|
||||
}
|
||||
|
||||
.plugin-source-badge.source-orphaned {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
}
|
||||
|
||||
.plugin-cloud-link {
|
||||
color: var(--plugin-link-text);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
|
||||
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
|
||||
IgnorableNamespaces="uap uap3 rescap rescap3 desktop desktop6 virtualization">
|
||||
IgnorableNamespaces="uap rescap rescap3 desktop6 virtualization">
|
||||
|
||||
<Identity Name="@MSIX_IDENTITY_NAME@"
|
||||
Publisher="@MSIX_PUBLISHER@"
|
||||
@@ -66,20 +64,6 @@
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="orcaslicer" />
|
||||
</uap:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="prusaslicer" />
|
||||
</uap:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="bambustudio" />
|
||||
</uap:Extension>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="cura" />
|
||||
</uap:Extension>
|
||||
<uap3:Extension Category="windows.appExecutionAlias" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="orca-slicer.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
@@ -202,25 +202,10 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
// 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);
|
||||
|
||||
// ORCA: brightness of those dimmed layers, in percent. 0 = black, capped at 99 because
|
||||
// 100 would render them unchanged, which is what disabling the option already does
|
||||
if (get("preview_dim_previous_layers_brightness").empty())
|
||||
set("preview_dim_previous_layers_brightness", "40");
|
||||
else {
|
||||
int brightness = 40;
|
||||
try {
|
||||
brightness = std::stoi(get("preview_dim_previous_layers_brightness"));
|
||||
}
|
||||
catch (...) {
|
||||
brightness = 40;
|
||||
}
|
||||
set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99))));
|
||||
}
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to) : BoundingBoxBase()
|
||||
BoundingBoxBase(It from, It to)
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const PointsType &points)
|
||||
|
||||
+8
-14
@@ -32,13 +32,15 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
// Orca: Translate the brim area into print coordinates and store it per instance.
|
||||
static void append_and_translate(const ExPolygons& src, const PrintInstance& instance,
|
||||
size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||
// BBS: generate brim area by objs
|
||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
||||
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||
ExPolygons srcShifted = src;
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (ExPolygon& expoly : srcShifted)
|
||||
expoly.translate(instance_shift);
|
||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance_shift);
|
||||
srcShifted = diff_ex(srcShifted, dst);
|
||||
//expolygons_append(dst, temp2);
|
||||
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
||||
}
|
||||
|
||||
@@ -570,7 +572,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
||||
const PrintInstance& instance = object->instances()[instance_idx];
|
||||
if (!brim_area_object.empty())
|
||||
append_and_translate(brim_area_object, instance, instance_idx, brimAreaMap);
|
||||
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||
append_and_translate(holes, holes_object, instance);
|
||||
append_and_translate(objectIslands, objectIsland, instance);
|
||||
@@ -873,14 +875,6 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
||||
|
||||
if (!print.config().combine_brims) {
|
||||
ExPolygons claimed_area;
|
||||
for (auto& [_, areas] : brimAreaMap) {
|
||||
areas = diff_ex(areas, claimed_area);
|
||||
expolygons_append(claimed_area, areas);
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: Find boundingbox of the first layer
|
||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||
BoundingBox bbx;
|
||||
|
||||
@@ -248,8 +248,6 @@ set(lisbslic3r_sources
|
||||
GCode/Thumbnails.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/OrderingStrategies.cpp
|
||||
GCode/OrderingStrategies.hpp
|
||||
GCode/WipeTower2.cpp
|
||||
GCode/WipeTower2.hpp
|
||||
GCode/WipeTower.cpp
|
||||
|
||||
@@ -1857,12 +1857,12 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI
|
||||
const bool first = graph.first(cp);
|
||||
int extend_next_idx = -1;
|
||||
int extend_prev_idx = -1;
|
||||
coord_t dist_y_prev = 0;
|
||||
coord_t dist_y_next = 0;
|
||||
double arc_len_prev = 0;
|
||||
double arc_len_next = 0;
|
||||
coord_t dist_y_prev;
|
||||
coord_t dist_y_next;
|
||||
double arc_len_prev;
|
||||
double arc_len_next;
|
||||
|
||||
if (! graph.next_vertical(cp)) {
|
||||
if (! graph.next_vertical(cp)){
|
||||
size_t i = cp.point_idx;
|
||||
size_t j = next_idx_modulo(i, contour);
|
||||
while (j != cp.next_on_contour->point_idx) {
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
Standard_Boolean UserBreak() override { return should_stop.load(); }
|
||||
|
||||
void Show(const Message_ProgressScope&, const Standard_Boolean) override {
|
||||
std::cout << "Progress: " << std::fixed << std::setprecision(2) << 100.0 * GetPosition() << "%" << std::endl;
|
||||
std::cout << "Progress: " << GetPosition() << "%" << std::endl;
|
||||
}
|
||||
private:
|
||||
std::atomic<bool>& should_stop;
|
||||
|
||||
+45
-191
@@ -15,7 +15,6 @@
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
@@ -1510,19 +1509,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
if (travel_to_tower_now) {
|
||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||
// then we could simplify the condition and make it more readable.
|
||||
|
||||
// Orca: pass the configured lift type, as append_tcr does above. lazy_lift() keeps
|
||||
// the first type it is given, so the NormalLift default would pin this hop to a
|
||||
// standing move. Slope and spiral both need a known head position.
|
||||
LiftType lift_type = LiftType::NormalLift;
|
||||
if (gcodegen.writer().filament() != nullptr && gcodegen.writer().is_current_position_clear()) {
|
||||
ZHopType z_hop_type = ZHopType(gcodegen.config().z_hop_types.get_at(
|
||||
gcodegen.get_filament_config_index((int) gcodegen.writer().filament()->id())));
|
||||
if (z_hop_type == ZHopType::zhtAuto)
|
||||
z_hop_type = ZHopType::zhtSpiral;
|
||||
lift_type = gcodegen.to_lift_type(z_hop_type);
|
||||
}
|
||||
gcode += gcodegen.retract(false, false, lift_type);
|
||||
gcode += gcodegen.retract();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
if (!tcr.priming && gcodegen.last_pos_defined())
|
||||
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
|
||||
@@ -2896,7 +2883,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
m_role_based_fan_marker_layer.fill(-1);
|
||||
|
||||
m_fan_mover.release();
|
||||
m_ordering_cache.clear();
|
||||
|
||||
m_writer.set_is_bbl_machine(is_bbl_printers);
|
||||
|
||||
@@ -3217,20 +3203,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
|
||||
// Therefore initialize the printing extruders from there.
|
||||
this->set_extruders(tool_ordering.all_extruders());
|
||||
print_object_instances_ordering =
|
||||
// By default, order object instances using nearest-neighbor chaining plus
|
||||
// 2-opt and crossing-removal post-processing.
|
||||
(print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
||||
// Snake: serpentine row traversal + 2-opt
|
||||
: (print.config().print_order == PrintOrder::Snake ? chain_print_object_instances_snake(print)
|
||||
// Best of all: run every strategy, pick the shortest total path
|
||||
: (print.config().print_order == PrintOrder::BestOfStrategies ? chain_print_object_instances_best_of(print)
|
||||
print_object_instances_ordering =
|
||||
// By default, order object instances using a nearest neighbor search.
|
||||
print.config().print_order == PrintOrder::Default ? chain_print_object_instances(print)
|
||||
// Otherwise same order as the object list
|
||||
: sort_object_instances_by_model_order(print))));
|
||||
|
||||
|
||||
|
||||
|
||||
: sort_object_instances_by_model_order(print);
|
||||
}
|
||||
if (initial_extruder_id == (unsigned int)-1) {
|
||||
// Nothing to print!
|
||||
@@ -5621,9 +5598,7 @@ LayerResult GCode::process_layer(
|
||||
//Calibration Layer-specific GCode
|
||||
switch (print.calib_mode()) {
|
||||
case CalibMode::Calib_PA_Tower: {
|
||||
gcode += writer().set_pressure_advance(this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||
static_cast<float>(print.calib_params().end),
|
||||
static_cast<float>(print.calib_params().step)));
|
||||
gcode += writer().set_pressure_advance(print.calib_params().start + static_cast<int>(print_z) * print.calib_params().step);
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_Temp_Tower: {
|
||||
@@ -5631,12 +5606,7 @@ LayerResult GCode::process_layer(
|
||||
break;
|
||||
}
|
||||
case CalibMode::Calib_VFA_Tower: {
|
||||
// Step the outer wall speed from start to end across the tower's layers. Plater::calib_VFA sizes the
|
||||
// geometry so each speed step spans one visual block (a fixed number of layers), so the layer-based
|
||||
// stepping stays aligned with the blocks regardless of nozzle size / layer height.
|
||||
float _speed = this->interpolate_value_across_layers(static_cast<float>(print.calib_params().start),
|
||||
static_cast<float>(print.calib_params().end),
|
||||
static_cast<float>(print.calib_params().step));
|
||||
auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step;
|
||||
m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable({std::round(_speed)}));
|
||||
break;
|
||||
}
|
||||
@@ -6068,128 +6038,41 @@ LayerResult GCode::process_layer(
|
||||
if (m_farthest_point_timelapse.enabled)
|
||||
compute_farthest_point(layers, most_used_extruder, support_filaments);
|
||||
|
||||
// Per filament: instances to print, and the visit sequence over them. Island-level ordering
|
||||
// may visit an instance more than once per layer; otherwise one visit per instance.
|
||||
std::map<unsigned int, std::pair<std::vector<InstanceToPrint>, std::vector<InstanceVisit>>> filament_to_print_instances;
|
||||
std::map<unsigned int, std::vector<InstanceToPrint>> filament_to_print_instances;
|
||||
{
|
||||
// Order individual islands rather than whole instances. Off for by-object sequencing,
|
||||
// sequential printing, and the explicit AsObjectList order, which tour whole instances.
|
||||
const bool island_level_ordering = print.config().print_sequence != PrintSequence::ByObject &&
|
||||
single_object_instance_idx == size_t(-1) &&
|
||||
print.config().print_order != PrintOrder::AsObjectList;
|
||||
for (unsigned int filament_id : layer_tools.extruders) {
|
||||
auto objects_by_extruder_it = by_extruder.find(filament_id);
|
||||
if (objects_by_extruder_it == by_extruder.end()) continue;
|
||||
|
||||
auto &filament_plan = filament_to_print_instances[filament_id];
|
||||
|
||||
if (!island_level_ordering) {
|
||||
// One visit per instance, printing all of its islands.
|
||||
filament_plan.first = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||
filament_plan.second.reserve(filament_plan.first.size());
|
||||
for (size_t i = 0; i < filament_plan.first.size(); ++i)
|
||||
filament_plan.second.push_back({i, {}, true});
|
||||
continue;
|
||||
}
|
||||
|
||||
int plate_idx = print.get_plate_index();
|
||||
Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx));
|
||||
|
||||
// Build the instances and one tour node per non-empty island (a single node for
|
||||
// instances without chainable islands). Positions quantized to 1 mm so small
|
||||
// centroid drift between layers still hits the tour cache below.
|
||||
std::vector<GCode::ObjectByExtruder> &objects_by_extruder = objects_by_extruder_it->second;
|
||||
std::vector<InstanceToPrint> &instances = filament_plan.first;
|
||||
std::vector<IslandOrderNode> nodes;
|
||||
std::vector<size_t> node_instances;
|
||||
auto quantize_to_mm = [](const Point &pt) -> Point {
|
||||
const coord_t grid = coord_t(scale_(1.));
|
||||
// Round to the nearest 1 mm symmetrically (integer division truncates toward
|
||||
// zero, which would make the bucket straddling the origin twice as wide).
|
||||
auto q = [grid](coord_t v) -> coord_t {
|
||||
return ((v >= 0 ? v + grid / 2 : v - grid / 2) / grid) * grid;
|
||||
};
|
||||
return Point(q(pt.x()), q(pt.y()));
|
||||
};
|
||||
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
||||
std::vector<const PrintObject *> print_objects;
|
||||
for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) {
|
||||
auto &object_by_extruder = objects_by_extruder[obj_idx];
|
||||
if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) continue;
|
||||
|
||||
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
||||
const PrintObject *print_object = layers[layer_id].original_object;
|
||||
if (print_object == nullptr)
|
||||
continue;
|
||||
const Layer *obj_layer = layers[layer_id].object_layer;
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_by_extruder.islands;
|
||||
const bool islands_chainable = obj_layer != nullptr && islands.size() == obj_layer->lslices.size() + 1;
|
||||
for (size_t instance_id = 0; instance_id < print_object->instances().size(); ++instance_id) {
|
||||
const size_t instance_idx = instances.size();
|
||||
instances.emplace_back(object_by_extruder, layer_id, *print_object, instance_id,
|
||||
print_object->instances()[instance_id].model_instance->get_labeled_id());
|
||||
const Point &shift = print_object->instances()[instance_id].shift;
|
||||
const size_t first_node = nodes.size();
|
||||
if (islands_chainable)
|
||||
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||
if (!islands[i].by_region.empty()) {
|
||||
nodes.push_back({print_object->id(), instance_id, i,
|
||||
quantize_to_mm(obj_layer->lslices[i].contour.centroid() + shift)});
|
||||
node_instances.emplace_back(instance_idx);
|
||||
}
|
||||
if (nodes.size() == first_node) {
|
||||
// No chainable islands: tour the whole instance as one stop.
|
||||
nodes.push_back({print_object->id(), instance_id, size_t(-1), quantize_to_mm(shift)});
|
||||
node_instances.emplace_back(instance_idx);
|
||||
}
|
||||
}
|
||||
print_objects.push_back(print.get_object(obj_idx));
|
||||
}
|
||||
|
||||
// Reuse the cached tour while this filament's island layout is unchanged.
|
||||
auto &cache_entry = m_ordering_cache[filament_id];
|
||||
if (!(cache_entry.first == nodes)) {
|
||||
cache_entry.first = nodes;
|
||||
Points node_points;
|
||||
node_points.reserve(nodes.size());
|
||||
for (const IslandOrderNode &node : nodes)
|
||||
node_points.emplace_back(node.pos);
|
||||
std::vector<size_t> tour = order_points_with_strategy(node_points, print.config().print_order, &wt_pos);
|
||||
// Chained starting near the wipe tower, reversed so the layer ends near it.
|
||||
std::reverse(tour.begin(), tour.end());
|
||||
std::vector<const PrintInstance *> new_ordering = chain_print_object_instances(print_objects, &wt_pos);
|
||||
std::reverse(new_ordering.begin(), new_ordering.end());
|
||||
|
||||
// Group consecutive tour stops of the same instance into visits.
|
||||
std::vector<InstanceVisit> visits;
|
||||
std::vector<bool> instance_seen(instances.size(), false);
|
||||
std::vector<int> last_visit_of_instance(instances.size(), -1);
|
||||
for (size_t node_idx : tour) {
|
||||
const size_t instance_idx = node_instances[node_idx];
|
||||
if (visits.empty() || visits.back().instance_idx != instance_idx) {
|
||||
visits.push_back({instance_idx, {}, !instance_seen[instance_idx]});
|
||||
instance_seen[instance_idx] = true;
|
||||
}
|
||||
if (nodes[node_idx].island_idx != size_t(-1))
|
||||
visits.back().islands.emplace_back(nodes[node_idx].island_idx);
|
||||
last_visit_of_instance[instance_idx] = int(visits.size()) - 1;
|
||||
}
|
||||
// The trailing catch-all island has no geometry to chain by; append it to the
|
||||
// instance's last visit.
|
||||
for (size_t i = 0; i < instances.size(); ++i) {
|
||||
if (last_visit_of_instance[i] < 0)
|
||||
continue;
|
||||
InstanceVisit &last_visit = visits[size_t(last_visit_of_instance[i])];
|
||||
if (last_visit.islands.empty())
|
||||
// A visit without explicit islands already prints everything.
|
||||
continue;
|
||||
std::vector<ObjectByExtruder::Island> &islands = instances[i].object_by_extruder.islands;
|
||||
if (!islands.back().by_region.empty())
|
||||
last_visit.islands.emplace_back(islands.size() - 1);
|
||||
}
|
||||
cache_entry.second = std::move(visits);
|
||||
if (print.config().print_sequence == PrintSequence::ByObject) {
|
||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||
} else {
|
||||
|
||||
// PrintSequence::ByLayer to use global ordering ( per object ordering ) if intra-layer order PrintOrder::AsObjectList is specified while keeping behaviour of PrintSequence::ByLayer
|
||||
const std::vector<const PrintInstance*>* ordering_for_filament = (print.config().print_order == PrintOrder::AsObjectList && ordering != nullptr) ? ordering: &new_ordering;
|
||||
filament_to_print_instances[filament_id] = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering_for_filament, single_object_instance_idx);
|
||||
}
|
||||
filament_plan.second = cache_entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
std::set<size_t> layer_object_label_ids;
|
||||
for (auto iter = filament_to_print_instances.begin(); iter != filament_to_print_instances.end(); ++iter) {
|
||||
for (const InstanceToPrint &instance : iter->second.first) {
|
||||
for (const InstanceToPrint &instance : iter->second) {
|
||||
layer_object_label_ids.insert(instance.label_object_id);
|
||||
}
|
||||
}
|
||||
@@ -6259,7 +6142,7 @@ LayerResult GCode::process_layer(
|
||||
|
||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||
std::vector<size_t> filament_instances_id;
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first) filament_instances_id.emplace_back(instance.label_object_id);
|
||||
for (InstanceToPrint &instance : filament_to_print_instances[extruder_id]) filament_instances_id.emplace_back(instance.label_object_id);
|
||||
m_filament_instances_code = _encode_label_ids_to_base64(filament_instances_id);
|
||||
}
|
||||
|
||||
@@ -6340,9 +6223,7 @@ LayerResult GCode::process_layer(
|
||||
if (layer_tools.has_wipe_tower && m_wipe_tower)
|
||||
m_last_processor_extrusion_role = erWipeTower;
|
||||
|
||||
auto &filament_plan = filament_to_print_instances[extruder_id];
|
||||
std::vector<InstanceToPrint> &instances_to_print = filament_plan.first;
|
||||
const std::vector<InstanceVisit> &instance_visits = filament_plan.second;
|
||||
std::vector<InstanceToPrint> &instances_to_print = filament_to_print_instances[extruder_id];
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
||||
@@ -6350,11 +6231,10 @@ LayerResult GCode::process_layer(
|
||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||
gcode+="; PURGING FINISHED\n";
|
||||
|
||||
for (const InstanceVisit &visit : instance_visits) {
|
||||
InstanceToPrint &instance_to_print = instances_to_print[visit.instance_idx];
|
||||
for (InstanceToPrint &instance_to_print : instances_to_print) {
|
||||
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
||||
if (visit.first_visit && print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
||||
if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
|
||||
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
|
||||
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
|
||||
}
|
||||
@@ -6407,7 +6287,7 @@ LayerResult GCode::process_layer(
|
||||
m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
m_last_obj_copy = this_object_copy;
|
||||
this->set_origin(unscale(offset));
|
||||
if (visit.first_visit && instance_to_print.object_by_extruder.support != nullptr) {
|
||||
if (instance_to_print.object_by_extruder.support != nullptr) {
|
||||
m_layer = layers[instance_to_print.layer_id].support_layer;
|
||||
m_object_layer_over_raft = false;
|
||||
|
||||
@@ -6441,42 +6321,9 @@ LayerResult GCode::process_layer(
|
||||
m_layer = layer_to_print.layer();
|
||||
m_object_layer_over_raft = object_layer_over_raft;
|
||||
}
|
||||
//FIXME order islands?
|
||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||
// Island print order. Use the islands the tour assigned to this visit; if none,
|
||||
// chain all islands nearest-neighbor from the current nozzle position (last_pos(),
|
||||
// in this instance's frame after set_origin() above). Empty islands are skipped;
|
||||
// the trailing catch-all island has no centroid to chain by and always goes last.
|
||||
std::vector<ObjectByExtruder::Island> &islands = instance_to_print.object_by_extruder.islands;
|
||||
std::vector<size_t> island_order = visit.islands;
|
||||
if (island_order.empty()) {
|
||||
island_order.reserve(islands.size());
|
||||
if (layer_to_print.object_layer != nullptr && islands.size() == layer_to_print.object_layer->lslices.size() + 1) {
|
||||
for (size_t i = 0; i + 1 < islands.size(); ++i)
|
||||
if (!islands[i].by_region.empty())
|
||||
island_order.emplace_back(i);
|
||||
if (island_order.size() > 1) {
|
||||
Points island_centroids;
|
||||
island_centroids.reserve(island_order.size());
|
||||
for (size_t i : island_order)
|
||||
island_centroids.emplace_back(layer_to_print.object_layer->lslices[i].contour.centroid());
|
||||
const Point start_near = this->last_pos();
|
||||
std::vector<size_t> chain = chain_points(island_centroids, this->last_pos_defined() ? &start_near : nullptr);
|
||||
std::vector<size_t> ordered;
|
||||
ordered.reserve(island_order.size());
|
||||
for (size_t k : chain)
|
||||
ordered.emplace_back(island_order[k]);
|
||||
island_order = std::move(ordered);
|
||||
}
|
||||
if (!islands.back().by_region.empty())
|
||||
island_order.emplace_back(islands.size() - 1);
|
||||
} else {
|
||||
// Unexpected islands layout, keep the stored order.
|
||||
for (size_t i = 0; i < islands.size(); ++i)
|
||||
island_order.emplace_back(i);
|
||||
}
|
||||
}
|
||||
for (size_t island_idx : island_order) {
|
||||
ObjectByExtruder::Island &island = islands[island_idx];
|
||||
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
|
||||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast<unsigned int>(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region;
|
||||
// When starting a new object, use the external motion planner for the first travel move.
|
||||
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
|
||||
@@ -8441,22 +8288,29 @@ std::string GCode::extrusion_role_to_string_for_parser(const ExtrusionRole & rol
|
||||
}
|
||||
|
||||
// Calculate the interpolated value for the current layer between start_value and end_value.
|
||||
// Step > 0 splits the range into equal-width bands from first to last value (both inclusive).
|
||||
// Step will create equal layers steps from first to last value.
|
||||
// Step = 0 means gradual interpolation finishing at last value.
|
||||
float GCode::interpolate_value_across_layers(float start_value, float end_value, float step) const
|
||||
{
|
||||
if (m_layer_index <= 1) {
|
||||
return start_value;
|
||||
}
|
||||
const float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||
if (step > 0.f) {
|
||||
// Discrete equal-width bands. band is clamped to the last band so the result can't overshoot the range:
|
||||
// at the top layer ratio * n_bands == n_bands, which would otherwise index one band past the end.
|
||||
const int n_bands = std::lround(std::abs(end_value - start_value) / step) + 1;
|
||||
const int band = std::min(n_bands - 1, static_cast<int>(ratio * n_bands));
|
||||
return start_value + (end_value >= start_value ? 1.f : -1.f) * band * step;
|
||||
else {
|
||||
bool use_steps = step > 0.f;
|
||||
if (use_steps) {
|
||||
if (start_value > end_value) {
|
||||
start_value += step;
|
||||
} else {
|
||||
end_value += step;
|
||||
}
|
||||
}
|
||||
float ratio = m_layer_index / (m_layer_count - 1.f);
|
||||
float value = start_value + ratio * (end_value - start_value);
|
||||
if (use_steps) {
|
||||
value = trunc(value / step) * step;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return start_value + ratio * (end_value - start_value);
|
||||
}
|
||||
|
||||
std::string encodeBase64(uint64_t value)
|
||||
|
||||
+1
-35
@@ -184,7 +184,7 @@ struct LayerResult {
|
||||
// It is used for the pressure equalizer because it needs to buffer one layer back.
|
||||
bool nop_layer_result { false };
|
||||
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<size_t>::max(), false, false, true}; }
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
class GCode {
|
||||
@@ -542,40 +542,6 @@ private:
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
|
||||
// One stop of the island-level tour: consecutive islands of a single instance. An instance
|
||||
// can have several visits per layer when its islands are toured non-consecutively.
|
||||
struct InstanceVisit
|
||||
{
|
||||
// Index into the per-filament InstanceToPrint vector.
|
||||
size_t instance_idx;
|
||||
// Islands to print, in order (indices into ObjectByExtruder::islands). Empty: print all
|
||||
// islands, ordered at extrusion time.
|
||||
std::vector<size_t> islands;
|
||||
// First visit of this instance this layer; skirt, brim and support are emitted here.
|
||||
bool first_visit;
|
||||
};
|
||||
|
||||
// One node of the island-level tour, also used as cache key: identity plus quantized position.
|
||||
struct IslandOrderNode
|
||||
{
|
||||
ObjectID object_id;
|
||||
size_t instance_id;
|
||||
// Index into ObjectByExtruder::islands, or size_t(-1) for an instance without chainable
|
||||
// islands (e.g. support only), which is toured as a single stop.
|
||||
size_t island_idx;
|
||||
// Island centroid in G-code coordinates, quantized to 1 mm for cache stability.
|
||||
Point pos;
|
||||
bool operator==(const IslandOrderNode &rhs) const {
|
||||
return object_id == rhs.object_id && instance_id == rhs.instance_id &&
|
||||
island_idx == rhs.island_idx && pos == rhs.pos;
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the per-filament island tour to avoid recomputing while the layer's island layout is
|
||||
// unchanged. Key: filament_id. Value: {nodes the tour was computed from, resulting visits}.
|
||||
std::map<unsigned int, std::pair<std::vector<IslandOrderNode>, std::vector<InstanceVisit>>>
|
||||
m_ordering_cache;
|
||||
|
||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||
|
||||
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
// Print-object ordering strategies: implementation.
|
||||
// Consolidates TSP post-processing, Snake, and Best-of-Strategies.
|
||||
|
||||
#include "OrderingStrategies.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/* ====================================================================
|
||||
* TSP post-processing utilities
|
||||
* ==================================================================== */
|
||||
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 2) return false;
|
||||
|
||||
// Pre-compute edge lengths once per pass to avoid redundant norm() calls.
|
||||
auto recompute_edges = [&]() {
|
||||
std::vector<double> el(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
el[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).norm();
|
||||
}
|
||||
return el;
|
||||
};
|
||||
std::vector<double> el = recompute_edges();
|
||||
|
||||
// Pre-compute squared edge lengths for early rejection in the inner loop.
|
||||
auto recompute_edges_sq = [&]() {
|
||||
std::vector<double> elsq(pn);
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
size_t ni = (i + 1) % pn;
|
||||
elsq[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).squaredNorm();
|
||||
}
|
||||
return elsq;
|
||||
};
|
||||
std::vector<double> elsq = recompute_edges_sq();
|
||||
|
||||
bool improved = false;
|
||||
for (int pass = 0; max_passes <= 0 || pass < max_passes; ++pass) {
|
||||
size_t best_i = pn, best_j = pn;
|
||||
double best_gain = 0;
|
||||
|
||||
for (size_t i = 0; i < pn; ++i) {
|
||||
const Vec2d& pi = centers[path[i]].cast<double>();
|
||||
const Vec2d& p_in = centers[path[(i + 1) % pn]].cast<double>();
|
||||
double d_i = el[i];
|
||||
double d_i_sq = elsq[i];
|
||||
|
||||
for (size_t j = i + 2; j < pn; ++j) {
|
||||
size_t j_next = (j + 1) % pn;
|
||||
// Skip the swap that would reverse the entire cycle (removes both
|
||||
// edges (0,1) and (pn-1,0), equivalent to traversing the cycle backwards).
|
||||
if (i == 0 && j_next == 0) continue;
|
||||
|
||||
const Vec2d& pj = centers[path[j]].cast<double>();
|
||||
const Vec2d& p_jn = centers[path[j_next]].cast<double>();
|
||||
double d_j = el[j];
|
||||
|
||||
// Early rejection using squared distances (avoids 2 sqrt calls).
|
||||
double new_a_sq = (pj - pi).squaredNorm();
|
||||
double new_b_sq = (p_jn - p_in).squaredNorm();
|
||||
if (new_a_sq >= d_i_sq && new_b_sq >= elsq[j]) continue;
|
||||
|
||||
double new_a = std::sqrt(new_a_sq);
|
||||
double new_b = std::sqrt(new_b_sq);
|
||||
double gain = d_i + d_j - new_a - new_b;
|
||||
|
||||
if (gain > best_gain) {
|
||||
best_gain = gain;
|
||||
best_i = i; best_j = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_i == pn) break;
|
||||
improved = true;
|
||||
// Reverse the best swap segment
|
||||
std::reverse(path.begin() + best_i + 1, path.begin() + best_j + 1);
|
||||
|
||||
// Recompute edge lengths after reversal
|
||||
el = recompute_edges();
|
||||
elsq = recompute_edges_sq();
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
// Fast bounding-box overlap test (rejects most non-intersecting pairs).
|
||||
static inline bool bboxes_overlap(const Point& a, const Point& b, const Point& c, const Point& d)
|
||||
{
|
||||
return !(std::max(a.x(), b.x()) < std::min(c.x(), d.x()) ||
|
||||
std::max(c.x(), d.x()) < std::min(a.x(), b.x()) ||
|
||||
std::max(a.y(), b.y()) < std::min(c.y(), d.y()) ||
|
||||
std::max(c.y(), d.y()) < std::min(a.y(), b.y()));
|
||||
}
|
||||
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
if (pn <= 3) return false;
|
||||
|
||||
// Treat path as a cycle: include the closing edge (pn-1 -> 0), consistent with the other
|
||||
// TSP helpers (2-opt, closing-edge rotation) that operate on the full cycle.
|
||||
size_t n_edges = pn;
|
||||
|
||||
// Scan for first crossing; returns {i, j} or {npos, npos} if none.
|
||||
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
|
||||
for (size_t i = 0; i < n_edges; ++i) {
|
||||
const Point& ai = centers[path[i]];
|
||||
const Point& bi = centers[path[(i + 1) % pn]];
|
||||
|
||||
for (size_t j = i + 2; j < n_edges; ++j) {
|
||||
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
||||
if (i == 0 && j == pn - 1) continue;
|
||||
|
||||
const Point& aj = centers[path[j]];
|
||||
const Point& bj = centers[path[(j + 1) % pn]];
|
||||
|
||||
if (!bboxes_overlap(ai, bi, aj, bj)) continue;
|
||||
if (Geometry::segments_intersect(ai, bi, aj, bj))
|
||||
return {i, j};
|
||||
}
|
||||
}
|
||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||
};
|
||||
|
||||
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||
int max_iters = static_cast<int>(pn * pn);
|
||||
bool improved = false;
|
||||
while (max_iters-- > 0) {
|
||||
auto [ci, cj] = find_crossing();
|
||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||
improved = true;
|
||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
size_t pn = path.size();
|
||||
size_t best_start = 0;
|
||||
double best_closing2 = std::numeric_limits<double>::max();
|
||||
for (size_t start = 0; start < pn; ++start) {
|
||||
size_t last = (start + pn - 1) % pn;
|
||||
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
||||
if (d2 < best_closing2) { best_closing2 = d2; best_start = start; }
|
||||
}
|
||||
std::rotate(path.begin(), path.begin() + best_start, path.end());
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Snake ordering
|
||||
* ==================================================================== */
|
||||
|
||||
struct SnakeRow { double avg_y; std::vector<size_t> indices; };
|
||||
|
||||
// --- Row threshold computation ---
|
||||
// Extract unique Y values and use the median gap between them to determine
|
||||
// the row threshold.
|
||||
static double compute_row_threshold(const std::vector<double>& sorted_ys,
|
||||
double y_min, double y_max,
|
||||
size_t n,
|
||||
double fraction_of_y_range,
|
||||
double min_threshold_um)
|
||||
{
|
||||
constexpr double MIN_GAP_FILTER = 1.0; // ignore sub-micron gaps (coord_t = 1/100mm)
|
||||
|
||||
// Extract unique Y values
|
||||
std::vector<double> unique_ys;
|
||||
unique_ys.reserve(sorted_ys.size());
|
||||
unique_ys.push_back(sorted_ys[0]);
|
||||
for (size_t i = 1; i < sorted_ys.size(); ++i) {
|
||||
if (sorted_ys[i] - sorted_ys[i - 1] > MIN_GAP_FILTER)
|
||||
unique_ys.push_back(sorted_ys[i]);
|
||||
}
|
||||
|
||||
double fallback_threshold = (y_max - y_min) * fraction_of_y_range;
|
||||
if (unique_ys.size() <= 1) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Compute gaps between consecutive unique Y values
|
||||
std::vector<double> gaps;
|
||||
gaps.reserve(unique_ys.size() - 1);
|
||||
for (size_t i = 1; i < unique_ys.size(); ++i)
|
||||
gaps.push_back(unique_ys[i] - unique_ys[i - 1]);
|
||||
|
||||
if (gaps.empty()) {
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// Sort gaps to find the median
|
||||
std::sort(gaps.begin(), gaps.end());
|
||||
double median_gap = gaps[gaps.size() / 2];
|
||||
double min_gap = gaps.front();
|
||||
|
||||
// Threshold: half the gap between consecutive unique Y values.
|
||||
double threshold = (median_gap < min_gap * 1.5) ? min_gap * 0.5 : median_gap * 0.5;
|
||||
|
||||
bool has_row_structure;
|
||||
if (unique_ys.size() * 2 <= n) {
|
||||
has_row_structure = true;
|
||||
} else {
|
||||
// Single-column or sparse: uniform gaps indicate a deliberate grid
|
||||
double max_gap = *std::max_element(gaps.begin(), gaps.end());
|
||||
has_row_structure = (max_gap < min_gap * 2.0);
|
||||
}
|
||||
|
||||
if (has_row_structure) {
|
||||
// For grid-like data, use the gap-based threshold directly.
|
||||
return threshold;
|
||||
}
|
||||
|
||||
return std::max(fallback_threshold, min_threshold_um);
|
||||
}
|
||||
|
||||
// --- Row grouping ---
|
||||
// Bin points into rows by quantising Y / threshold
|
||||
static std::vector<SnakeRow> group_into_rows(const Points& centers, double row_threshold)
|
||||
{
|
||||
size_t n = centers.size();
|
||||
std::unordered_map<int64_t, std::vector<size_t>> row_map;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int64_t y_key = static_cast<int64_t>(std::floor(static_cast<double>(centers[i].y()) / row_threshold));
|
||||
row_map[y_key].push_back(i);
|
||||
}
|
||||
|
||||
std::vector<SnakeRow> rows;
|
||||
rows.reserve(row_map.size());
|
||||
for (auto& [key, indices] : row_map) {
|
||||
double avg_y = std::accumulate(indices.begin(), indices.end(), 0.0,
|
||||
[&](double acc, size_t idx) { return acc + static_cast<double>(centers[idx].y()); })
|
||||
/ indices.size();
|
||||
rows.push_back({avg_y, std::move(indices)});
|
||||
}
|
||||
|
||||
std::sort(rows.begin(), rows.end(),
|
||||
[](const SnakeRow& a, const SnakeRow& b) { return a.avg_y < b.avg_y; });
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Sort each row by X and greedily pick the direction (left->right or right->left)
|
||||
// that minimises the transition distance from the previous row's endpoint.
|
||||
static std::vector<size_t> build_serpentine_path(const Points& centers,
|
||||
std::vector<SnakeRow>& rows)
|
||||
{
|
||||
std::vector<size_t> path;
|
||||
path.reserve(centers.size());
|
||||
|
||||
for (size_t ri = 0; ri < rows.size(); ++ri) {
|
||||
auto& row = rows[ri].indices;
|
||||
std::sort(row.begin(), row.end(),
|
||||
[&](size_t a, size_t b) { return centers[a].x() < centers[b].x(); });
|
||||
|
||||
if (ri == 0) {
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
} else {
|
||||
const Point& prev_end = centers[path.back()];
|
||||
double dist_to_left = (prev_end.cast<double>() - centers[row.front()].cast<double>()).squaredNorm();
|
||||
double dist_to_right = (prev_end.cast<double>() - centers[row.back()].cast<double>()).squaredNorm();
|
||||
|
||||
if (dist_to_left <= dist_to_right)
|
||||
path.insert(path.end(), row.begin(), row.end());
|
||||
else
|
||||
path.insert(path.end(), row.rbegin(), row.rend());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Row-based serpentine traversal: detect rows, bin points, snake through them.
|
||||
static std::vector<size_t> row_serpentine_path(const Points& centers,
|
||||
double fraction_of_y_range = 0.02,
|
||||
double min_threshold_um = 1e4)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
size_t n = centers.size();
|
||||
|
||||
// Collect and sort Y coordinates.
|
||||
std::vector<double> sorted_ys;
|
||||
sorted_ys.reserve(n);
|
||||
for (const auto& p : centers) sorted_ys.push_back(static_cast<double>(p.y()));
|
||||
std::sort(sorted_ys.begin(), sorted_ys.end());
|
||||
|
||||
auto [ymin, ymax] = std::minmax_element(sorted_ys.begin(), sorted_ys.end());
|
||||
double y_min = *ymin, y_max = *ymax;
|
||||
|
||||
double row_threshold = compute_row_threshold(sorted_ys, y_min, y_max, n,
|
||||
fraction_of_y_range, min_threshold_um);
|
||||
|
||||
auto rows = group_into_rows(centers, row_threshold);
|
||||
return build_serpentine_path(centers, rows);
|
||||
}
|
||||
|
||||
std::vector<size_t> snake_core(const Points& centers)
|
||||
{
|
||||
if (centers.empty()) return {};
|
||||
|
||||
std::vector<size_t> path = row_serpentine_path(centers);
|
||||
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, centers);
|
||||
improved |= tsp_remove_crossings(path, centers);
|
||||
if (!improved) break;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
return chain_instances_with_core(print_objects, start_near, snake_core);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_snake(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Best-of-strategies meta-strategy
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
||||
{
|
||||
if (print_objects.empty())
|
||||
return {};
|
||||
|
||||
// Run all strategies.
|
||||
std::vector<std::vector<const PrintInstance*>> candidates;
|
||||
candidates.push_back(chain_print_object_instances(print_objects, start_near));
|
||||
candidates.push_back(chain_print_object_instances_snake(print_objects, start_near));
|
||||
|
||||
// Compute metrics for each candidate.
|
||||
struct Candidate { double total_len; double max_edge; };
|
||||
std::vector<Candidate> metrics;
|
||||
metrics.reserve(candidates.size());
|
||||
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double total = 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t j = 0; j < candidates[i].size(); ++j) {
|
||||
size_t k = (j + 1) % candidates[i].size();
|
||||
double d = (candidates[i][j]->shift.cast<double>() - candidates[i][k]->shift.cast<double>()).norm();
|
||||
total += d;
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
metrics.push_back({total, mx});
|
||||
}
|
||||
|
||||
// Pick shortest total path; tiebreak on smallest max edge.
|
||||
auto best_it = std::min_element(metrics.begin(), metrics.end(),
|
||||
[](const Candidate& a, const Candidate& b) {
|
||||
return a.total_len < b.total_len ||
|
||||
(a.total_len == b.total_len && a.max_edge < b.max_edge);
|
||||
});
|
||||
size_t best = static_cast<size_t>(std::distance(metrics.begin(), best_it));
|
||||
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print)
|
||||
{
|
||||
return chain_print_object_instances_best_of(print.objects().vector(), nullptr);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Island-level ordering entry point
|
||||
* ==================================================================== */
|
||||
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near)
|
||||
{
|
||||
if (points.empty())
|
||||
return {};
|
||||
|
||||
if (print_order != PrintOrder::Snake && print_order != PrintOrder::BestOfStrategies)
|
||||
// Nearest neighbor + post-processing; honours start_near natively.
|
||||
return chain_points_with_postprocessing(points, start_near);
|
||||
|
||||
auto run_snake = [&points, start_near]() {
|
||||
std::vector<size_t> path = snake_core(points);
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Start the cycle at the point closest to start_near.
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < points.size(); ++k) {
|
||||
double d2 = (points[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
auto it = std::find(path.begin(), path.end(), best_start);
|
||||
if (it != path.begin() && it != path.end())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
if (print_order == PrintOrder::Snake)
|
||||
return run_snake();
|
||||
|
||||
// Best-of: pick the shortest total cycle; tiebreak on smallest max edge.
|
||||
std::vector<std::vector<size_t>> candidates;
|
||||
candidates.emplace_back(chain_points_with_postprocessing(points, start_near));
|
||||
candidates.emplace_back(run_snake());
|
||||
|
||||
size_t best = 0;
|
||||
double best_len = std::numeric_limits<double>::max();
|
||||
double best_edge = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
double len = tsp_cycle_path_length(candidates[i], points);
|
||||
double edge = tsp_max_edge_length(candidates[i], points);
|
||||
if (len < best_len || (len == best_len && edge < best_edge)) {
|
||||
best_len = len; best_edge = edge; best = i;
|
||||
}
|
||||
}
|
||||
return candidates[best];
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -1,148 +0,0 @@
|
||||
// Print-object ordering strategies and shared TSP post-processing utilities.
|
||||
|
||||
#ifndef slic3r_OrderingStrategies_hpp_
|
||||
#define slic3r_OrderingStrategies_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Point.hpp"
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
#include "../Print.hpp"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// --- Path improvement (operate on index vectors into `centers`) ---
|
||||
|
||||
// 2-opt improvement: reverses segments that reduce total cycle path length.
|
||||
// Returns true if any improvement was made.
|
||||
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes = 10);
|
||||
|
||||
// Crossing removal: reverse any segment pair whose edges geometrically cross.
|
||||
// Returns true if any crossing was removed.
|
||||
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Rotate the cycle so the closing edge (last -> first) is minimized.
|
||||
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers);
|
||||
|
||||
// Total Euclidean path length of a cycle (including closing edge).
|
||||
inline double tsp_cycle_path_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double total = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
total += (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Maximum edge length of a cycle (including closing edge).
|
||||
inline double tsp_max_edge_length(const std::vector<size_t>& path, const Points& centers)
|
||||
{
|
||||
if (path.size() < 2) return 0.0;
|
||||
double mx = 0.0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t next = (i + 1) % path.size();
|
||||
double d = (centers[path[i]].cast<double>() - centers[path[next]].cast<double>()).norm();
|
||||
if (d > mx) mx = d;
|
||||
}
|
||||
return mx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Wrapper boilerplate ---
|
||||
|
||||
// Collect instance centers from PrintObjects, optionally pre-rotate to honour
|
||||
// start_near, call a core algorithm, and map the result back to PrintInstance*.
|
||||
template<typename CoreFn>
|
||||
std::vector<const PrintInstance*> chain_instances_with_core(
|
||||
const std::vector<const PrintObject*>& print_objects,
|
||||
const Point* start_near,
|
||||
CoreFn&& core_fn)
|
||||
{
|
||||
Points instance_centers;
|
||||
std::vector<std::pair<size_t, size_t>> instances;
|
||||
for (size_t i = 0; i < print_objects.size(); ++i) {
|
||||
const PrintObject& object = *print_objects[i];
|
||||
for (size_t j = 0; j < object.instances().size(); ++j) {
|
||||
instance_centers.emplace_back(object.instances()[j].shift);
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance_centers.empty()) return {};
|
||||
|
||||
// If start_near is provided, pre-rotate so closest point is first.
|
||||
if (start_near != nullptr) {
|
||||
size_t best_start = 0;
|
||||
double best_d2 = std::numeric_limits<double>::max();
|
||||
for (size_t k = 0; k < instance_centers.size(); ++k) {
|
||||
double d2 = (instance_centers[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
||||
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
||||
}
|
||||
std::rotate(instance_centers.begin(), instance_centers.begin() + best_start, instance_centers.end());
|
||||
std::rotate(instances.begin(), instances.begin() + best_start, instances.end());
|
||||
}
|
||||
|
||||
auto path = core_fn(instance_centers);
|
||||
|
||||
// Rotate the cycle so the first element is the best starting point.
|
||||
// When start_near is provided, pick the point closest to it (preserving
|
||||
// the pre-rotation). Otherwise minimise the closing edge.
|
||||
if (start_near != nullptr && !path.empty()) {
|
||||
// Pre-rotation already put the closest point at index 0.
|
||||
// Find where index 0 appears in the path and rotate it to the front.
|
||||
auto it = std::find(path.begin(), path.end(), size_t(0));
|
||||
if (it != path.begin())
|
||||
std::rotate(path.begin(), it, path.end());
|
||||
} else {
|
||||
tsp_rotate_minimize_closing(path, instance_centers);
|
||||
}
|
||||
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(path.size());
|
||||
for (size_t step : path) {
|
||||
out.emplace_back(&print_objects[instances[step].first]->instances()[instances[step].second]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Core algorithms (operate on raw Points, return index permutations) ---
|
||||
|
||||
// Snake ordering: row grouping + serpentine traversal + post-processing.
|
||||
std::vector<size_t> snake_core(const Points& centers);
|
||||
|
||||
#ifndef SLIC3R_TEST_HARNESS
|
||||
|
||||
// --- Production wrappers ---
|
||||
|
||||
// Snake ordering.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print);
|
||||
|
||||
// Best-of-strategies: run all strategies and return the shortest result.
|
||||
// Primary: shortest total path; secondary tiebreaker: smallest max edge.
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near);
|
||||
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print);
|
||||
|
||||
// Order raw points with the selected strategy, returning an index permutation. Island-level
|
||||
// counterpart of the chain_print_object_instances_* helpers. The returned cycle starts at the
|
||||
// point closest to start_near; orders without a dedicated strategy use nearest-neighbor chaining.
|
||||
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near);
|
||||
|
||||
#endif // SLIC3R_TEST_HARNESS
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_OrderingStrategies_hpp_ */
|
||||
@@ -593,7 +593,7 @@ Polylines remove_points_from_polygon(const Polygon &polygon_ori, const std::vect
|
||||
return result;
|
||||
}
|
||||
|
||||
Polylines construct_gap_for_skip_points(const Polygon &polygon, const std::vector<Vec2f> & skip_points ,float wt_width,float gap_length,Polygon& insert_skip_polygon)
|
||||
Polylines contrust_gap_for_skip_points(const Polygon &polygon, const std::vector<Vec2f> & skip_points ,float wt_width,float gap_length,Polygon& insert_skip_polygon)
|
||||
{
|
||||
if (skip_points.empty()) {
|
||||
insert_skip_polygon = polygon;
|
||||
@@ -5101,7 +5101,7 @@ Polygon WipeTower::generate_support_wall_new(WipeTowerWriter &writer, const box_
|
||||
if (!extrude_perimeter) return wall_polygon;
|
||||
|
||||
if (skip_points) {
|
||||
result_wall = construct_gap_for_skip_points(wall_polygon, m_wall_skip_points[m_cur_layer_id], m_wipe_tower_width, 2.5 * m_perimeter_width, insert_skip_polygon);
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, m_wall_skip_points[m_cur_layer_id], m_wipe_tower_width, 2.5 * m_perimeter_width, insert_skip_polygon);
|
||||
}
|
||||
else {
|
||||
result_wall.push_back(to_polyline(wall_polygon));
|
||||
|
||||
@@ -23,7 +23,7 @@ enum GCodeFlavor : unsigned char;
|
||||
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
|
||||
// so the entry travel can pass through instead of crossing the printed wall. Defined in
|
||||
// WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
Polylines construct_gap_for_skip_points(
|
||||
Polylines contrust_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
||||
|
||||
class WipeTower
|
||||
|
||||
@@ -2461,7 +2461,7 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
const size_t layer_id = size_t(m_layer_info - m_plan.begin());
|
||||
const std::vector<Vec2f>& layer_skip_points =
|
||||
layer_id < m_wall_skip_points.size() ? m_wall_skip_points[layer_id] : no_skip_points;
|
||||
result_wall = construct_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
insert_skip_polygon);
|
||||
} else {
|
||||
result_wall.push_back(to_polyline(wall_polygon));
|
||||
|
||||
@@ -360,14 +360,6 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
return clipped_paths;
|
||||
}
|
||||
|
||||
static double clipper_z_path_length(const ClipperLib_Z::Path &path)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += (Vec2d(double(path[i].x()), double(path[i].y())) - Vec2d(double(path[i - 1].x()), double(path[i - 1].y()))).norm();
|
||||
return len;
|
||||
}
|
||||
|
||||
struct PerimeterGeneratorArachneExtrusion
|
||||
{
|
||||
Arachne::ExtrusionLine* extrusion = nullptr;
|
||||
@@ -579,156 +571,17 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top acts on top surfaces, so without a top shell there is nothing for it to act on: zero top
|
||||
// shell layers retype the top surfaces as internal, see LayerRegion::prepare_fill_surfaces(). A 0% top surface
|
||||
// density does leave a top surface - just an unfilled one - so it does not disable the feature.
|
||||
// ConfigManipulation::toggle_print_fff_options() hides the option under the same condition, so a profile that left
|
||||
// it enabled does not act behind a hidden checkbox.
|
||||
static bool has_top_shell_layers(const PrintRegionConfig &config)
|
||||
// ORCA: only_one_wall_top detects the top as "slice − upper", so a feature rising from the middle of a
|
||||
// top surface becomes an enclosed hole that gets ringed with extra inner walls. Fill those holes back
|
||||
// into the top. Only holes that are both covered by the upper layer (excludes bridges) and backed by
|
||||
// solid material (excludes voids) are filled.
|
||||
static ExPolygons fill_enclosed_top_feature_holes(const ExPolygons &top, const Polygons &covered_by_upper, const ExPolygons &solid)
|
||||
{
|
||||
return config.top_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_first_layer thins the first layer to a single wall, the bottom counterpart of the above and
|
||||
// gated the same way: zero bottom shell layers retype the bottom surfaces as internal, so that wall would ring
|
||||
// sparse infill on the bed. The bottom surface density plays no part - an unfilled bottom surface is still a bottom
|
||||
// surface, exactly as for the top - and it cannot reach zero anyway, being capped at a 10% minimum.
|
||||
static bool has_bottom_shell_layers(const PrintRegionConfig &config)
|
||||
{
|
||||
return config.bottom_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: the inner walls are only given up when a top fill takes their space, and it has to actually reach it -
|
||||
// a 0% top surface density leaves no fill at all, and without top_surface_expansion the fill never grows over
|
||||
// them. Either way the original generation is kept (re-onion the not-top region), which is what users of
|
||||
// only_one_wall_top alone have always got.
|
||||
static bool top_fill_replaces_inner_walls(const PrintRegionConfig &config)
|
||||
{
|
||||
return has_top_shell_layers(config) && config.top_surface_density.value > 0 && config.top_surface_expansion.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - cheap per-vertex classification of a wall against the top surface. Only Partial
|
||||
// needs the geometry clipped or measured; a segment crossing the top with no vertex inside is rare enough to ignore.
|
||||
enum class TopOverlap { None, Partial, Full };
|
||||
|
||||
static bool point_over_top(const Point &p, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
if (! top_region_bbox.contains(p))
|
||||
return false;
|
||||
for (const ExPolygon &ex : top_region)
|
||||
if (ex.contains(p, false))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Points &pts, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Point &p : pts)
|
||||
if (point_over_top(p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == pts.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Arachne::ExtrusionLine &el, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
if (point_over_top(j.p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == el.junctions.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top for Arachne - cut out of the already generated inner walls the parts running over the top
|
||||
// surface, so geometry that continues upward keeps its walls. A wall too short over the top to be worth slitting open
|
||||
// is left whole, its footprint reported in kept_over_top for the caller to withhold from the top fill.
|
||||
static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &inner_perimeters, const ExPolygons &top_region, coord_t perimeter_width, Polygons &kept_over_top)
|
||||
{
|
||||
const BoundingBox top_region_bbox = get_extents(top_region).inflated(SCALED_EPSILON);
|
||||
auto covered_by = [](const Arachne::ExtrusionLine &el) {
|
||||
Polyline centerline;
|
||||
centerline.points.reserve(el.junctions.size());
|
||||
coord_t width = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions) {
|
||||
centerline.points.emplace_back(j.p);
|
||||
width = std::max(width, j.w);
|
||||
}
|
||||
return offset(centerline, float(width) / 2.f);
|
||||
};
|
||||
// Pull the cut back by half a wall width: the clip severs the centerline, but the bead's rounded end
|
||||
// extends half a width past its endpoint and would otherwise overlap the top fill.
|
||||
ClipperLib_Z::Paths top_paths_z;
|
||||
for (const Polygon &poly : to_polygons(offset_ex(top_region, float(perimeter_width) / 2.f))) {
|
||||
top_paths_z.emplace_back();
|
||||
ClipperLib_Z::Path &out = top_paths_z.back();
|
||||
out.reserve(poly.points.size());
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
}
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
Arachne::VariableWidthLines kept;
|
||||
kept.reserve(inner_perimeter.size());
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter) {
|
||||
if (el.empty())
|
||||
continue;
|
||||
const TopOverlap overlap = classify_over_top(el, top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None) {
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
if (overlap == TopOverlap::Full)
|
||||
continue; // the clip below would return nothing anyway
|
||||
ClipperLib_Z::Path subject;
|
||||
subject.reserve(el.size());
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
subject.emplace_back(j.p.x(), j.p.y(), j.w);
|
||||
ClipperLib_Z::Paths pieces = clip_extrusion(subject, top_paths_z, ClipperLib_Z::ctDifference);
|
||||
|
||||
// Clipper treats the subject as an open polyline, so it also cuts a closed loop at its (arbitrary)
|
||||
// start vertex and may reverse pieces. Stitch pieces sharing an endpoint back together.
|
||||
auto same_pt = [](const ClipperLib_Z::IntPoint &p, const ClipperLib_Z::IntPoint &q) {
|
||||
return std::abs(p.x() - q.x()) <= SCALED_EPSILON && std::abs(p.y() - q.y()) <= SCALED_EPSILON;
|
||||
};
|
||||
for (size_t i = 0; i < pieces.size(); ++ i) {
|
||||
for (size_t j = i + 1; j < pieces.size();) {
|
||||
ClipperLib_Z::Path &a = pieces[i];
|
||||
ClipperLib_Z::Path &b = pieces[j];
|
||||
if (same_pt(a.front(), b.front()) || same_pt(a.front(), b.back()))
|
||||
std::reverse(a.begin(), a.end());
|
||||
if (same_pt(a.back(), b.back()))
|
||||
std::reverse(b.begin(), b.end());
|
||||
if (same_pt(a.back(), b.front())) {
|
||||
a.insert(a.end(), b.begin() + 1, b.end());
|
||||
pieces.erase(pieces.begin() + j);
|
||||
j = i + 1; // the merged path has new endpoints, restart the scan
|
||||
} else
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
|
||||
// If the clip removed next to nothing, keep the loop untouched instead of slitting it open. The
|
||||
// half-width pull-back above already costs about one width per crossing, hence two widths.
|
||||
double kept_length = 0.;
|
||||
for (const ClipperLib_Z::Path &path : pieces)
|
||||
kept_length += clipper_z_path_length(path);
|
||||
if (clipper_z_path_length(subject) - kept_length < 2. * double(perimeter_width)) {
|
||||
append(kept_over_top, covered_by(el));
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ClipperLib_Z::Path &path : pieces) {
|
||||
Arachne::ExtrusionLine clipped(el.inset_idx, el.is_odd);
|
||||
clipped.junctions.reserve(path.size());
|
||||
for (const ClipperLib_Z::IntPoint &pt : path)
|
||||
clipped.junctions.emplace_back(Point(pt.x(), pt.y()), coord_t(pt.z()), el.inset_idx);
|
||||
// Discard tiny leftovers that would print as zits.
|
||||
if (clipped.size() >= 2 && clipped.getLength() >= perimeter_width)
|
||||
kept.emplace_back(std::move(clipped));
|
||||
}
|
||||
}
|
||||
inner_perimeter = std::move(kept);
|
||||
}
|
||||
ExPolygons filled = top;
|
||||
for (ExPolygon &ex : filled)
|
||||
ex.holes.clear();
|
||||
const ExPolygons feature_holes = intersection_ex(intersection_ex(diff_ex(filled, top), covered_by_upper), solid);
|
||||
return feature_holes.empty() ? top : union_ex(top, feature_holes);
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
@@ -796,6 +649,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
top_polygons = fill_enclosed_top_feature_holes(top_polygons, upper_polygons_series_clipped, orig_polygons);
|
||||
|
||||
// get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
|
||||
// min_width_top_surface we removed a bit before)
|
||||
@@ -1380,11 +1234,6 @@ void PerimeterGenerator::process_classic()
|
||||
for (const Surface &surface : all_surfaces)
|
||||
surface_exp.push_back(surface.expolygon);
|
||||
std::vector<size_t> surface_order = chain_expolygons(surface_exp);
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) {
|
||||
const Surface &surface = all_surfaces[surface_order[order_idx]];
|
||||
// detect how many perimeters must be generated for this island
|
||||
@@ -1392,23 +1241,16 @@ void PerimeterGenerator::process_classic()
|
||||
int sparse_infill_density = this->config->sparse_infill_density.value;
|
||||
if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
|
||||
loop_number++;
|
||||
if (this->layer_id == object_config->raft_layers && only_one_wall_first_layer)
|
||||
if (this->layer_id == object_config->raft_layers && this->config->only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
// Set the topmost layer to be one wall
|
||||
if (loop_number > 0 && only_one_wall_top && this->upper_slices == nullptr)
|
||||
if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
|
||||
ExPolygons gaps;
|
||||
ExPolygons top_fills;
|
||||
ExPolygons fill_clip;
|
||||
// ORCA: only_one_wall_top, all empty unless this island has a top surface on this layer. See the
|
||||
// post-onion reduction below: the region to keep clear of inner walls, the space freed by the dropped
|
||||
// walls (goes to infill, not left as a void) and the space held by the kept ones (withheld from the fill).
|
||||
ExPolygons one_wall_top_region;
|
||||
ExPolygons one_wall_top_reclaimed;
|
||||
Polygons one_wall_top_kept_bands;
|
||||
bool apply_one_wall_top = false;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
@@ -1547,19 +1389,8 @@ void PerimeterGenerator::process_classic()
|
||||
|
||||
//BBS: refer to superslicer
|
||||
//store surface for top infill if only_one_wall_top
|
||||
if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
if (top_fill_replaces_inner_walls(*this->config)) {
|
||||
// ORCA: take the top fill and the keep-out region but leave `last` as the real geometry,
|
||||
// so the onion follows it and the walls over the top are reduced in one step below.
|
||||
ExPolygons non_top_polygons;
|
||||
this->split_top_surfaces(last, top_fills, non_top_polygons, fill_clip);
|
||||
apply_one_wall_top = !top_fills.empty();
|
||||
if (apply_one_wall_top)
|
||||
one_wall_top_region = diff_ex(last, non_top_polygons);
|
||||
} else {
|
||||
// Onion the not-top region only, so the remaining walls stop at the top boundary.
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
}
|
||||
if (i == 0 && i!=loop_number && config->only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
}
|
||||
|
||||
if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) {
|
||||
@@ -1569,46 +1400,6 @@ void PerimeterGenerator::process_classic()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top reduction - drop the inner walls (depth > 0) running over the top surface and
|
||||
// take that space back from the gaps, leaving the top with the outer wall and the top infill. Classic
|
||||
// perimeters are closed loops, so a wall can only be kept or dropped whole; one that merely grazes the
|
||||
// top (same tolerance as the Arachne clip) is kept and withheld from the top fill instead.
|
||||
if (apply_one_wall_top) {
|
||||
const BoundingBox top_region_bbox = get_extents(one_wall_top_region).inflated(SCALED_EPSILON);
|
||||
const double grazing_tolerance = 2. * double(perimeter_width);
|
||||
// The band a wall covers, taken around its centerline so the orientation of holes does not matter.
|
||||
auto wall_band = [perimeter_spacing](const Polygon &poly) {
|
||||
Polygon centerline = poly;
|
||||
centerline.make_counter_clockwise();
|
||||
return diff(offset(centerline, float(perimeter_spacing) / 2.f),
|
||||
offset(centerline, -float(perimeter_spacing) / 2.f));
|
||||
};
|
||||
Polygons dropped_wall_bands;
|
||||
auto reduce_over_top = [&](PerimeterGeneratorLoops &loops) {
|
||||
loops.erase(std::remove_if(loops.begin(), loops.end(), [&](const PerimeterGeneratorLoop &loop) {
|
||||
const TopOverlap overlap = classify_over_top(loop.polygon.points, one_wall_top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None)
|
||||
return false;
|
||||
// Only a wall straddling the boundary is worth measuring; a wall wholly over the top goes.
|
||||
if (overlap == TopOverlap::Partial &&
|
||||
total_length(intersection_pl(Polylines{ loop.polygon.split_at_first_point() }, one_wall_top_region)) < grazing_tolerance) {
|
||||
append(one_wall_top_kept_bands, wall_band(loop.polygon));
|
||||
return false;
|
||||
}
|
||||
append(dropped_wall_bands, wall_band(loop.polygon));
|
||||
return true;
|
||||
}), loops.end());
|
||||
};
|
||||
for (int d = 1; d <= loop_number; ++ d) {
|
||||
reduce_over_top(contours[d]);
|
||||
reduce_over_top(holes[d]);
|
||||
}
|
||||
if (! gaps.empty())
|
||||
gaps = diff_ex(gaps, one_wall_top_region);
|
||||
if (! dropped_wall_bands.empty())
|
||||
one_wall_top_reclaimed = diff_ex(dropped_wall_bands, one_wall_top_region);
|
||||
}
|
||||
|
||||
// nest loops: holes first
|
||||
for (int d = 0; d <= loop_number; ++ d) {
|
||||
PerimeterGeneratorLoops &holes_d = holes[d];
|
||||
@@ -1843,10 +1634,7 @@ void PerimeterGenerator::process_classic()
|
||||
and use zigzag). */
|
||||
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
||||
// therefore it may cover the area, but no the volume.
|
||||
Polygons gap_fill_covered = gap_fill.polygons_covered_by_width(10.f);
|
||||
last = diff_ex(last, gap_fill_covered);
|
||||
if (! one_wall_top_reclaimed.empty())
|
||||
one_wall_top_reclaimed = diff_ex(one_wall_top_reclaimed, gap_fill_covered);
|
||||
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
|
||||
this->gap_fill->append(std::move(gap_fill.entities));
|
||||
|
||||
}
|
||||
@@ -1891,15 +1679,9 @@ void PerimeterGenerator::process_classic()
|
||||
// append infill areas to fill_surfaces
|
||||
//if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
|
||||
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
||||
// ORCA: only_one_wall_top - route the top fill around the walls kept despite grazing the top.
|
||||
if (!one_wall_top_kept_bands.empty())
|
||||
top_infill_exp = diff_ex(top_infill_exp, one_wall_top_kept_bands);
|
||||
if (!top_fills.empty()) {
|
||||
infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(top_infill_peri_overlap)));
|
||||
}
|
||||
// ORCA: only_one_wall_top - what the top fill does not cover of the dropped walls goes to infill.
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
infill_exp = union_ex(infill_exp, one_wall_top_reclaimed);
|
||||
this->fill_surfaces->append(infill_exp, stInternal);
|
||||
|
||||
apply_extra_perimeters(infill_exp);
|
||||
@@ -1918,8 +1700,6 @@ void PerimeterGenerator::process_classic()
|
||||
double(-inset - infill_peri_overlap));
|
||||
if (!top_fills.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, one_wall_top_reclaimed);
|
||||
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
||||
}
|
||||
|
||||
@@ -2358,11 +2138,6 @@ void PerimeterGenerator::process_arachne()
|
||||
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
for (const Surface& surface : all_surfaces) {
|
||||
@@ -2375,12 +2150,12 @@ void PerimeterGenerator::process_arachne()
|
||||
|
||||
// Set the bottommost layer to be one wall
|
||||
const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
|
||||
if (is_bottom_layer && only_one_wall_first_layer)
|
||||
if (is_bottom_layer && this->config->only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
|
||||
// Orca: set the topmost layer to be one wall according to the config
|
||||
const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
|
||||
if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
|
||||
if (is_topmost_layer && loop_number > 0 && config->only_one_wall_top)
|
||||
loop_number = 0;
|
||||
|
||||
auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
|
||||
@@ -2400,10 +2175,10 @@ void PerimeterGenerator::process_arachne()
|
||||
//PS: One wall top surface for Arachne
|
||||
ExPolygons top_expolygons;
|
||||
// Calculate how many inner loops remain when TopSurfaces is selected.
|
||||
const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
const int inner_loop_number = (config->only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
|
||||
// Set one perimeter when TopSurfaces is selected.
|
||||
if (only_one_wall_top && loop_number > 0)
|
||||
if (config->only_one_wall_top && loop_number > 0)
|
||||
loop_number = 0;
|
||||
|
||||
Arachne::WallToolPathsParams input_params_tmp = input_params;
|
||||
@@ -2434,6 +2209,7 @@ void PerimeterGenerator::process_arachne()
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
top_expolygons = fill_enclosed_top_feature_holes(top_expolygons, upper_slices_clipped, infill_contour);
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
@@ -2454,33 +2230,25 @@ void PerimeterGenerator::process_arachne()
|
||||
// due to thin lines being generated
|
||||
top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
|
||||
|
||||
// Get final top ExPolygons (bridges were excluded above, so they stay walled).
|
||||
// Get the not-top ExPolygons (including bridges) from current slices and expanded real top ExPolygons (without bridges).
|
||||
const ExPolygons not_top_expolygons = diff_ex(infill_contour, top_expolygons);
|
||||
|
||||
// Get final top ExPolygons.
|
||||
top_expolygons = intersection_ex(top_expolygons, infill_contour);
|
||||
|
||||
// ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
|
||||
// geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
|
||||
// instead - the fallback when there is no top fill - walls the top/non-top interface and rings
|
||||
// top-surface islands with inner walls that don't exist when the feature is disabled.
|
||||
const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
|
||||
const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
|
||||
: diff_ex(infill_contour, top_expolygons),
|
||||
wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
const Polygons not_top_polygons = to_polygons(offset_ex(not_top_expolygons,wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
std::vector<Arachne::VariableWidthLines> inner_perimeters = inner_wall_tool_paths.getToolPaths();
|
||||
|
||||
if (clip_walls_over_top) {
|
||||
Polygons kept_over_top;
|
||||
clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
|
||||
// Route the top fill around the walls kept despite grazing the top.
|
||||
if (! kept_over_top.empty())
|
||||
top_expolygons = diff_ex(top_expolygons, kept_over_top);
|
||||
}
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
|
||||
if (!perimeters.empty())
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
|
||||
// Recalculate indexes of inner perimeters before merging them.
|
||||
if (!perimeters.empty()) {
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
if (inner_perimeter.empty())
|
||||
continue;
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter)
|
||||
++el.inset_idx;
|
||||
}
|
||||
}
|
||||
|
||||
perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
|
||||
infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
|
||||
|
||||
@@ -331,8 +331,6 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintSequence)
|
||||
static t_config_enum_values s_keys_map_PrintOrder{
|
||||
{ "default", int(PrintOrder::Default) },
|
||||
{ "as_obj_list", int(PrintOrder::AsObjectList)},
|
||||
{ "best_of", int(PrintOrder::BestOfStrategies)},
|
||||
{ "snake", int(PrintOrder::Snake)},
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintOrder)
|
||||
|
||||
@@ -2001,30 +1999,12 @@ void PrintConfigDef::init_fff_params()
|
||||
|
||||
def = this->add("print_order", coEnum);
|
||||
def->label = L("Intra-layer order");
|
||||
def->tooltip = L("Order in which object instances are visited within a single layer, which controls how much "
|
||||
"travel is spent moving between them.\n\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general "
|
||||
"choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path "
|
||||
"optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The "
|
||||
"object instance order is decided once for the whole print, while the ordering of individual "
|
||||
"islands is decided per layer, so different layers may end up using different strategies. "
|
||||
"Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of "
|
||||
"many small parts.\n\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: "
|
||||
"objects are grouped by filament first and this setting only orders the instances within each "
|
||||
"filament group, so the overall sequence may not look like the shortest path across the plate.");
|
||||
def->tooltip = L("Print order within a single layer.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<PrintOrder>::get_enum_values();
|
||||
def->enum_values.push_back("default");
|
||||
def->enum_values.push_back("as_obj_list");
|
||||
def->enum_values.push_back("best_of");
|
||||
def->enum_values.push_back("snake");
|
||||
def->enum_labels.push_back(L("Default"));
|
||||
def->enum_labels.push_back(L("As object list"));
|
||||
def->enum_labels.push_back(L("Best of all (shortest path)"));
|
||||
def->enum_labels.push_back(L("Snake"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintOrder>(PrintOrder::Default));
|
||||
|
||||
@@ -5649,7 +5629,6 @@ void PrintConfigDef::init_fff_params()
|
||||
// Orca:
|
||||
def = this->add("retract_after_wipe", coPercents);
|
||||
def->label = L("Retract amount after wipe");
|
||||
// xgettext:no-c-format, no-boost-format
|
||||
def->tooltip = L("The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value.");
|
||||
def->sidetext = "%";
|
||||
|
||||
@@ -214,8 +214,6 @@ enum class PrintOrder
|
||||
{
|
||||
Default,
|
||||
AsObjectList,
|
||||
BestOfStrategies, // run all custom strategies, pick the shortest total path
|
||||
Snake, // snake-like row traversal (back-and-forth) + 2-opt
|
||||
Count,
|
||||
};
|
||||
|
||||
|
||||
@@ -1364,6 +1364,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_combination_max_layer_height"
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "top_surface_expansion"
|
||||
|| opt_key == "top_surface_expansion_margin"
|
||||
|| opt_key == "top_surface_expansion_direction"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
@@ -1399,6 +1400,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_anchor"
|
||||
|| opt_key == "infill_anchor_max"
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "top_surface_density"
|
||||
|| opt_key == "bottom_surface_density"
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
@@ -1432,24 +1434,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
is_approx(new_density->value, 0.) || is_approx(new_density->value, 100.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "top_surface_density") {
|
||||
// ORCA: 0% means no top solid fill, which switches off both the top surface expansion and the wall
|
||||
// removal over top surfaces. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_density = old_config.option<ConfigOptionPercent>(opt_key);
|
||||
const auto *new_density = new_config.option<ConfigOptionPercent>(opt_key);
|
||||
assert(old_density && new_density);
|
||||
if (is_approx(old_density->value, 0.) || is_approx(new_density->value, 0.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "top_surface_expansion") {
|
||||
// ORCA: without the expansion the top fill never reaches the space freed by only_one_wall_top, so the
|
||||
// walls over top surfaces are kept. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_expansion = old_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *new_expansion = new_config.option<ConfigOptionFloat>(opt_key);
|
||||
assert(old_expansion && new_expansion);
|
||||
if (old_expansion->value <= 0. || new_expansion->value <= 0.)
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "internal_solid_infill_line_width") {
|
||||
// This value is used for calculating perimeter - infill overlap, thus perimeters need to be recalculated.
|
||||
steps.emplace_back(posPerimeters);
|
||||
@@ -1776,50 +1760,51 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Grow the top surfaces by top_surface_expansion, so the top solid infill also covers the
|
||||
// material left by features rising from the middle of a top surface (filling the holes and
|
||||
// joining the tops, so the features rest on solid infill). Each connected island is grown and
|
||||
// clipped separately: growing one island's top across a gap into another - which may have no top
|
||||
// surface at all, leaving a partially filled layer - is never allowed. The original top is
|
||||
// unioned back in and bottom surfaces are never claimed, so this can only add area.
|
||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||
const double top_expansion = region_config.top_surface_expansion.value;
|
||||
// Nothing to expand without a top fill: a 0% top surface density leaves the top layer with
|
||||
// walls only, and zero top shell layers retypes it as internal in prepare_fill_surfaces().
|
||||
if (top_expansion > 0. && region_config.top_shell_layers.value > 0 &&
|
||||
region_config.top_surface_density.value > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
// Walls are laid out on spacing, not width; and only_one_wall_top leaves a single wall over
|
||||
// a top surface, which is exactly the situation handled here.
|
||||
const int wall_loops = region_config.only_one_wall_top.value ? std::min(region_config.wall_loops.value, 1)
|
||||
: region_config.wall_loops.value;
|
||||
// ORCA: Expand the top surfaces outward by top_surface_expansion in every direction. This
|
||||
// enlarges the top solid infill and, in particular, grows it over the covered material left
|
||||
// by features rising from the middle of a top surface (filling holes and joining tops so the
|
||||
// features rest on it). The expansion stays inside the section it belongs to: each connected
|
||||
// solid island has its own outer wall, so the top is grown within each island separately and
|
||||
// clipped to it - growing one island's top across the gap into another island (which may have
|
||||
// no top surface, leaving a partially filled layer) is never allowed. The top infill sits
|
||||
// inside the perimeters, so the margin is measured from the walls: the island is inset by the
|
||||
// band the walls consume (outer wall + inner walls) plus the configured margin, making that
|
||||
// value the real clearance between the expanded top and the walls (avoiding a hull line). The
|
||||
// original top is unioned back in, so where it already sits within that band it is kept as-is.
|
||||
// Never claims a bottom surface.
|
||||
const double top_expansion = layerm->region().config().top_surface_expansion.value;
|
||||
if (top_expansion > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const auto jt = Clipper2Lib::JoinType::Miter;
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
const int wall_loops = layerm->region().config().wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_spacing()) * double(wall_loops - 1);
|
||||
const double margin = scale_(region_config.top_surface_expansion_margin.value);
|
||||
double(layerm->flow(frPerimeter).scaled_width()) * double(wall_loops - 1);
|
||||
const double margin = scale_(layerm->region().config().top_surface_expansion_margin.value);
|
||||
// minimum real top to act on: ignore anything thinner than ~2 top-infill lines
|
||||
const float min_top = float(layerm->flow(frTopSolidInfill).scaled_width());
|
||||
const auto direction = region_config.top_surface_expansion_direction.value;
|
||||
const auto direction = layerm->region().config().top_surface_expansion_direction.value;
|
||||
|
||||
ExPolygons grown;
|
||||
for (const ExPolygon &island : union_ex(layerm_slices_surfaces)) {
|
||||
// The top infill only exists inside the perimeters, so seed and measure from the infill
|
||||
// region (the island minus the wall band), not the raw slice: a section whose exposed top
|
||||
// is just the walls themselves is then skipped instead of being flooded inward. Clip the
|
||||
// layer's tops to the island first, to keep the boolean ops proportional to the island.
|
||||
// region (the island minus the wall band), not the raw slice. A section whose only
|
||||
// exposed top lies in the wall band - i.e. a layer where the top is just the walls
|
||||
// themselves - has no infill here and is skipped, instead of being flooded inward by
|
||||
// the expansion. Thin slivers inside the infill region are dropped by the opening too.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(T, get_extents(island).inflated(SCALED_EPSILON)),
|
||||
infill_region);
|
||||
const ExPolygons island_top = intersection_ex(T, infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// Grow, then keep only what the configured direction allows, using the top's own filled
|
||||
// outline (same outer edge, holes closed) to tell the two apart.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, Clipper2Lib::JoinType::Miter);
|
||||
// grow by d, then keep only the part allowed by the configured direction: inward fills
|
||||
// the holes/gaps left by features (clip the growth back to the top's own filled outline,
|
||||
// which leaves the outer edge fixed), outward grows the outer edge toward the walls (drop
|
||||
// the growth that fell into the original holes), and inward+outward keeps both.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, jt);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline;
|
||||
ExPolygons outline; // the top with its holes filled (same outer edge)
|
||||
outline.reserve(island_top.size());
|
||||
for (const ExPolygon &ex : island_top)
|
||||
outline.emplace_back(ex.contour);
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
@@ -1104,7 +1103,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy) {
|
||||
return chain_points(points);
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
|
||||
@@ -1112,26 +1111,9 @@ std::vector<size_t> chain_points(const Points &points, const Point *start_near)
|
||||
out.reserve(ordered.size());
|
||||
for (auto &segment_and_reversal : ordered)
|
||||
out.emplace_back(segment_and_reversal.first);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near)
|
||||
{
|
||||
std::vector<size_t> path = chain_points(points, start_near);
|
||||
// Alternate 2-opt and crossing removal until convergence.
|
||||
// 2-opt can create new crossings, and crossing removal can create new
|
||||
// opportunities for 2-opt improvement. Break early if neither improves.
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
bool improved = tsp_2opt_improve(path, points);
|
||||
improved |= tsp_remove_crossings(path, points);
|
||||
if (!improved) break;
|
||||
}
|
||||
if (start_near == nullptr)
|
||||
tsp_rotate_minimize_closing(path, points);
|
||||
return path;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define DEBUG_SVG_OUTPUT
|
||||
#endif /* NDEBUG */
|
||||
@@ -2043,13 +2025,12 @@ std::vector<const PrintInstance*> chain_print_object_instances(const std::vector
|
||||
instances.emplace_back(i, j);
|
||||
}
|
||||
}
|
||||
// Order objects using nearest neighbor + post-processing (crossing removal + 2-opt).
|
||||
std::vector<size_t> path = chain_points_with_postprocessing(object_reference_points, start_near);
|
||||
|
||||
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), start_near);
|
||||
std::vector<const PrintInstance*> out;
|
||||
out.reserve(path.size());
|
||||
for (size_t idx : path) {
|
||||
const std::pair<size_t, size_t>& inst = instances[idx];
|
||||
out.reserve(instances.size());
|
||||
for (auto& segment_and_reversal : ordered) {
|
||||
const std::pair<size_t, size_t>& inst = instances[segment_and_reversal.first];
|
||||
out.emplace_back(&print_objects[inst.first]->instances()[inst.second]);
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -15,9 +15,7 @@ namespace Slic3r {
|
||||
using PolyNodes = std::vector<PolyNode*, PointsAllocator<PolyNode*>>;
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, const Point *start_near = nullptr);
|
||||
// Variant with post-processing (crossing removal + 2-opt) for object ordering.
|
||||
std::vector<size_t> chain_points_with_postprocessing(const Points &points, const Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
|
||||
@@ -65,15 +65,6 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
const bool smooth_supports = support_params.support_style != smsGrid;
|
||||
SupportGeneratorLayersPtr &interface_layers = base_and_interface_layers.first;
|
||||
SupportGeneratorLayersPtr &base_interface_layers = base_and_interface_layers.second;
|
||||
// The user-facing interface layer counts include the contact layer. Internally,
|
||||
// contact layers are generated separately, so only the remaining layers are
|
||||
// projected into intermediate interface/base-interface layers here.
|
||||
const size_t num_top_interface_layers = support_params.has_top_contacts ? support_params.num_top_interface_layers - 1 : 0;
|
||||
const size_t num_bottom_interface_layers = support_params.has_bottom_contacts ? support_params.num_bottom_interface_layers - 1 : 0;
|
||||
const size_t num_top_base_interface_layers = std::min(support_params.num_top_base_interface_layers, num_top_interface_layers);
|
||||
const size_t num_bottom_base_interface_layers = std::min(support_params.num_bottom_base_interface_layers, num_bottom_interface_layers);
|
||||
const size_t num_top_interface_layers_only = num_top_interface_layers - num_top_base_interface_layers;
|
||||
const size_t num_bottom_interface_layers_only = num_bottom_interface_layers - num_bottom_base_interface_layers;
|
||||
|
||||
interface_layers.assign(intermediate_layers.size(), nullptr);
|
||||
if (support_params.has_base_interfaces())
|
||||
@@ -133,8 +124,6 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
};
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
|
||||
[&bottom_contacts, &top_contacts, &top_interface_layers, &top_base_interface_layers, &intermediate_layers, &insert_layer, &support_params,
|
||||
num_top_interface_layers, num_bottom_interface_layers, num_top_base_interface_layers, num_bottom_base_interface_layers,
|
||||
num_top_interface_layers_only, num_bottom_interface_layers_only,
|
||||
snug_supports, &interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
|
||||
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
|
||||
// this intermediate layer.
|
||||
@@ -153,16 +142,16 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
Polygons polygons_top_contact_projected_base;
|
||||
Polygons polygons_bottom_contact_projected_interface;
|
||||
Polygons polygons_bottom_contact_projected_base;
|
||||
if (num_top_interface_layers > 0) {
|
||||
if (support_params.num_top_interface_layers > 0) {
|
||||
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_interface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (num_top_base_interface_layers > 0)
|
||||
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers) - 1)]->print_z;
|
||||
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
|
||||
if (support_params.num_top_base_interface_layers > 0)
|
||||
// Some top base interface layers will be generated.
|
||||
top_interface_z = num_top_interface_layers_only == 0 ?
|
||||
top_inteface_z = support_params.num_top_interface_layers_only() == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
- std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(num_top_interface_layers_only) - 1)]->print_z;
|
||||
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + int(support_params.num_top_interface_layers_only()) - 1)]->print_z;
|
||||
// Move idx_top_contact_first up until above the current print_z.
|
||||
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const SupportGeneratorLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -171,22 +160,22 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
|
||||
//FIXME maybe this adds one interface layer in excess?
|
||||
if (top_contact_layer.bottom_z - EPSILON > top_z)
|
||||
break;
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_interface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface,
|
||||
// For snug supports, project the overhang polygons covering the whole overhang, so that they will merge without a gap with support polygons of the other layers.
|
||||
// For grid supports, merging of support regions will be performed by the projection into grid.
|
||||
snug_supports ? *top_contact_layer.overhang_polygons : top_contact_layer.polygons);
|
||||
}
|
||||
}
|
||||
if (num_bottom_interface_layers > 0) {
|
||||
if (support_params.num_bottom_interface_layers > 0) {
|
||||
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers) + 1)]->bottom_z;
|
||||
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
|
||||
if (num_bottom_base_interface_layers > 0)
|
||||
if (support_params.num_bottom_base_interface_layers > 0)
|
||||
// Some bottom base interface layers will be generated.
|
||||
bottom_interface_z = num_bottom_interface_layers_only == 0 ?
|
||||
bottom_interface_z = support_params.num_bottom_interface_layers_only() == 0 ?
|
||||
// Only base interface layers to generate.
|
||||
std::numeric_limits<coordf_t>::max() :
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(num_bottom_interface_layers_only))]->bottom_z;
|
||||
intermediate_layers[std::max(0, idx_intermediate_layer - int(support_params.num_bottom_interface_layers_only()))]->bottom_z;
|
||||
// Move idx_bottom_contact_first up until touching bottom_z.
|
||||
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const SupportGeneratorLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
|
||||
// Collect the top contact areas above this intermediate layer, below top_z.
|
||||
@@ -1574,17 +1563,13 @@ void generate_support_toolpaths(
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_first_layer = filler_first_layer_ptr ? filler_first_layer_ptr.get() : filler_interface.get();
|
||||
// Filler for the 1st layer interface, if different from filler_interface.
|
||||
const bool top_interfaces_enabled = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces_enabled = support_params.num_bottom_interface_layers > 0;
|
||||
const coordf_t base_interface_density = top_interfaces_enabled || !bottom_interfaces_enabled ?
|
||||
support_params.top_interface_density : support_params.bottom_interface_density;
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && !top_interfaces_enabled ?
|
||||
auto filler_raft_contact_ptr = std::unique_ptr<Fill>(range.begin() == n_raft_layers && config.support_interface_top_layers.value == 0 ?
|
||||
Fill::new_from_type(support_params.raft_interface_fill_pattern) : nullptr);
|
||||
// Pointer to the 1st layer interface filler.
|
||||
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
|
||||
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
|
||||
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
|
||||
Fill::new_from_type(base_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
|
||||
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
|
||||
filler_interface->set_bounding_box(bbox_object);
|
||||
if (filler_first_layer_ptr)
|
||||
@@ -1598,7 +1583,10 @@ void generate_support_toolpaths(
|
||||
{
|
||||
SupportLayer &support_layer = *support_layers[support_layer_id];
|
||||
LayerCache &layer_cache = layer_caches[support_layer_id];
|
||||
const float support_interface_angle = support_params.support_interface_angle(support_layer.interface_id());
|
||||
const float support_interface_angle = (config.support_interface_pattern == smipRectilinearInterlaced) ?
|
||||
support_params.raft_interface_angle(support_layer.interface_id()) :
|
||||
((support_params.support_style == smsGrid || config.support_interface_pattern == smipRectilinear) ?
|
||||
support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id()));
|
||||
|
||||
// Find polygons with the same print_z.
|
||||
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
|
||||
@@ -1631,9 +1619,7 @@ void generate_support_toolpaths(
|
||||
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
|
||||
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
|
||||
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = support_params.num_bottom_interface_layers > 0;
|
||||
if (!top_interfaces) {
|
||||
if (config.support_interface_top_layers == 0) {
|
||||
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
|
||||
// Don't merge the raft contact layer though.
|
||||
if (support_params.can_merge_support_regions && ! raft_layer) {
|
||||
@@ -1656,29 +1642,15 @@ void generate_support_toolpaths(
|
||||
if (top_contact_layer.could_merge(interface_layer) && ! raft_layer)
|
||||
top_contact_layer.merge(std::move(interface_layer));
|
||||
}
|
||||
if (!bottom_interfaces && support_params.can_merge_support_regions) {
|
||||
if ((config.support_interface_top_layers == 0 || config.support_interface_bottom_layers == 0) && support_params.can_merge_support_regions) {
|
||||
if (base_layer.could_merge(bottom_contact_layer))
|
||||
base_layer.merge(std::move(bottom_contact_layer));
|
||||
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
|
||||
base_layer = std::move(bottom_contact_layer);
|
||||
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
|
||||
if (top_interfaces && bottom_interfaces) {
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
} else if (bottom_interfaces) {
|
||||
top_contact_layer.set_polygons_to_extrude(
|
||||
diff(top_contact_layer.polygons_to_extrude(), bottom_contact_layer.polygons_to_extrude()));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), top_contact_layer.polygons_to_extrude()));
|
||||
}
|
||||
top_contact_layer.merge(std::move(bottom_contact_layer));
|
||||
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
|
||||
const bool interface_layer_is_bottom = interface_layer.layer->layer_type == SupporLayerType::BottomInterface;
|
||||
if (bottom_interfaces && interface_layer_is_bottom) {
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
} else {
|
||||
bottom_contact_layer.set_polygons_to_extrude(
|
||||
diff(bottom_contact_layer.polygons_to_extrude(), interface_layer.polygons_to_extrude()));
|
||||
}
|
||||
bottom_contact_layer.merge(std::move(interface_layer));
|
||||
}
|
||||
|
||||
// Orca: For organic trees the support-material regions are generated from
|
||||
@@ -1758,12 +1730,12 @@ void generate_support_toolpaths(
|
||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
};
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
|
||||
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
||||
if (!organic_tree)
|
||||
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
||||
const bool interface_layer_enabled = !interface_layer.empty() &&
|
||||
(interface_layer.layer->layer_type == SupporLayerType::BottomInterface ? bottom_interfaces : top_interfaces);
|
||||
extrude_interface(interface_layer, interface_layer_enabled ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
|
||||
// Base interface layers under soluble interfaces
|
||||
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
|
||||
Fill *filler = filler_base_interface.get();
|
||||
@@ -1773,7 +1745,7 @@ void generate_support_toolpaths(
|
||||
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
|
||||
filler->angle = support_interface_angle;
|
||||
filler->spacing = support_params.support_material_interface_flow.spacing();
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / base_interface_density));
|
||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
base_interface_layer.extrusions,
|
||||
@@ -1781,7 +1753,7 @@ void generate_support_toolpaths(
|
||||
// Regions to fill
|
||||
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
|
||||
// Filler and its parameters
|
||||
filler, float(base_interface_density),
|
||||
filler, float(support_params.top_interface_density),
|
||||
// Extrusion parameters
|
||||
ExtrusionRole::erSupportMaterial, interface_flow);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct SupportParameters {
|
||||
|
||||
{
|
||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||
this->num_bottom_interface_layers = std::max(0, number_of_support_interface_bottom_layers(object_config));
|
||||
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
|
||||
this->has_top_contacts = num_top_interface_layers > 0;
|
||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
||||
@@ -46,15 +46,15 @@ struct SupportParameters {
|
||||
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
|
||||
} else {
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_top_base_interface_layers = different_support_interface_filament && num_top_interface_layers > 1 ? 1 : 0;
|
||||
this->num_top_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
|
||||
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
|
||||
} else {
|
||||
// Keep at least one configured layer on the interface filament.
|
||||
this->num_bottom_base_interface_layers = different_support_interface_filament && num_bottom_interface_layers > 1 ? 1 : 0;
|
||||
this->num_bottom_base_interface_layers =
|
||||
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
|
||||
@@ -74,7 +74,7 @@ struct SupportParameters {
|
||||
for (auto layer : object.layers())
|
||||
this->support_layer_height_min = std::min(this->support_layer_height_min, std::max(0.01, layer->height));
|
||||
|
||||
if (this->num_top_interface_layers == 0 && this->num_bottom_interface_layers == 0) {
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->support_material_interface_flow = this->support_material_flow;
|
||||
}
|
||||
@@ -120,8 +120,8 @@ struct SupportParameters {
|
||||
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
|
||||
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
|
||||
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
|
||||
if (this->num_top_interface_layers == 0) {
|
||||
// No top interface layers allowed; keep unused top interface parameters aligned with base support.
|
||||
if (object_config.support_interface_top_layers.value == 0) {
|
||||
// No interface layers allowed, print everything with the base support pattern.
|
||||
this->top_interface_spacing = this->support_spacing;
|
||||
this->top_interface_density = this->support_density;
|
||||
}
|
||||
@@ -133,20 +133,16 @@ struct SupportParameters {
|
||||
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
|
||||
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
|
||||
const coordf_t contact_interface_density = this->num_top_interface_layers > 0 ?
|
||||
this->top_interface_density : this->bottom_interface_density;
|
||||
const bool zero_gap_contact_interface = this->num_top_interface_layers > 0 ?
|
||||
this->zero_gap_interface_top : this->zero_gap_interface_bottom;
|
||||
if (object_config.support_interface_pattern == smipGrid)
|
||||
this->contact_fill_pattern = ipGrid;
|
||||
else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
|
||||
this->contact_fill_pattern = ipRectilinear;
|
||||
else
|
||||
this->contact_fill_pattern =
|
||||
(object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) ||
|
||||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
|
||||
object_config.support_interface_pattern == smipConcentric ?
|
||||
ipConcentric :
|
||||
(contact_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
||||
|
||||
this->raft_angle_1st_layer = 0.f;
|
||||
this->raft_angle_base = 0.f;
|
||||
@@ -192,7 +188,6 @@ struct SupportParameters {
|
||||
std::numeric_limits<double>::max();
|
||||
|
||||
support_style = object_config.support_style;
|
||||
support_interface_pattern = object_config.support_interface_pattern;
|
||||
if (support_style != smsDefault) {
|
||||
if ((support_style == smsSnug || support_style == smsGrid) && is_tree(object_config.support_type)) support_style = smsDefault;
|
||||
if ((support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid || support_style == smsTreeOrganic) &&
|
||||
@@ -216,9 +211,9 @@ struct SupportParameters {
|
||||
bool has_top_contacts;
|
||||
// Is there at least a bottom contact layer extruded below support base?
|
||||
bool has_bottom_contacts;
|
||||
// User-configured number of top interface layers, including the contact layer.
|
||||
// Number of top interface layers without counting the contact layer.
|
||||
size_t num_top_interface_layers;
|
||||
// User-configured number of bottom interface layers, including the contact layer.
|
||||
// Number of bottom interface layers without counting the contact layer.
|
||||
size_t num_bottom_interface_layers;
|
||||
// Number of top base interface layers.
|
||||
size_t num_top_base_interface_layers;
|
||||
@@ -240,7 +235,7 @@ struct SupportParameters {
|
||||
Flow support_material_interface_flow;
|
||||
// Flow at the bottom interfaces and contacts.
|
||||
Flow support_material_bottom_interface_flow;
|
||||
// Flow at raft interface & contact layers.
|
||||
// Flow at raft inteface & contact layers.
|
||||
Flow raft_interface_flow;
|
||||
coordf_t support_extrusion_width;
|
||||
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
|
||||
@@ -267,7 +262,6 @@ struct SupportParameters {
|
||||
// Density of the base support layers.
|
||||
coordf_t support_density;
|
||||
SupportMaterialStyle support_style = smsDefault;
|
||||
SupportMaterialInterfacePattern support_interface_pattern = smipAuto;
|
||||
|
||||
// Pattern of the sparse infill including sparse raft layers.
|
||||
InfillPattern base_fill_pattern;
|
||||
@@ -286,33 +280,9 @@ struct SupportParameters {
|
||||
float raft_angle_base;
|
||||
float raft_angle_interface;
|
||||
|
||||
// Produce a +/-45deg alternating raft interface angle for a given SupportLayer::interface_id().
|
||||
// Produce a raft interface angle for a given SupportLayer::interface_id()
|
||||
float raft_interface_angle(size_t interface_id) const
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI_4) : float(+ M_PI_4)); }
|
||||
|
||||
// Produce support interface angle for a given SupportLayer::interface_id().
|
||||
// Angle will be shifted/rotated based on interface pattern.
|
||||
float support_interface_angle(size_t interface_id) const
|
||||
{
|
||||
float angle;
|
||||
|
||||
switch (this->support_interface_pattern) {
|
||||
case SupportMaterialInterfacePattern::smipRectilinear:
|
||||
angle = support_style == SupportMaterialStyle::smsSnug ? this->interface_angle - float(M_PI_4) : this->interface_angle;
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipRectilinearInterlaced:
|
||||
angle = this->interface_angle + ((interface_id & 1) ? float(M_PI_4) : float(-M_PI_4));
|
||||
break;
|
||||
case SupportMaterialInterfacePattern::smipGrid:
|
||||
angle = this->base_angle;
|
||||
break;
|
||||
default:
|
||||
angle = this->interface_angle;
|
||||
break;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
|
||||
|
||||
bool independent_layer_height = false;
|
||||
const double thresh_big_overhang = Slic3r::sqr(scale_(10));
|
||||
|
||||
@@ -469,7 +469,7 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
||||
});
|
||||
|
||||
// 2) Sum over top / bottom ranges.
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.back();
|
||||
const bool processing_last_mesh = outline_idx == layer_outline_indices.size();
|
||||
tbb::parallel_for(tbb::blocked_range<LayerIndex>(data.begin(), data.end()),
|
||||
[&collision_areas_offsetted, &outlines, &machine_border = m_machine_border, &anti_overhang = m_anti_overhang, radius,
|
||||
xy_distance, z_distance_bottom_layers, z_distance_top_layers, min_resolution = m_min_resolution, &data, processing_last_mesh, &throw_on_cancel]
|
||||
|
||||
@@ -1511,9 +1511,7 @@ void TreeSupport::generate_toolpaths()
|
||||
// ORCA: reset interface Fill state per area group to keep angles deterministic.
|
||||
filler_interface->fixed_angle = false;
|
||||
filler_interface->layer_id = size_t(-1);
|
||||
filler_Roof1stLayer->fixed_angle = false;
|
||||
filler_Roof1stLayer->layer_id = size_t(-1);
|
||||
filler_interface->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
|
||||
if (area_group.type != SupportLayer::BaseType) {
|
||||
// interface
|
||||
if (layer_id == 0) {
|
||||
@@ -1539,10 +1537,8 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
// Note: spacing means the separation between two lines as if they are tightly extruded
|
||||
filler_Roof1stLayer->spacing = interface_flow.spacing();
|
||||
filler_Roof1stLayer->angle = m_support_params.support_interface_angle(area_group.interface_id);
|
||||
filler_Roof1stLayer->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
filler_Roof1stLayer->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
// generate a perimeter first to support interface better
|
||||
@@ -1560,11 +1556,18 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = bottom_interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -1576,11 +1579,17 @@ void TreeSupport::generate_toolpaths()
|
||||
fill_params.density = interface_density;
|
||||
filler_interface->spacing = interface_flow.spacing();
|
||||
|
||||
fill_params.dont_sort = (m_object_config->support_interface_pattern == smipGrid ||
|
||||
m_object_config->support_interface_pattern == smipRectilinearInterlaced);
|
||||
if (m_object_config->support_interface_pattern == smipGrid) {
|
||||
filler_interface->angle = base_support_angle;
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
filler_interface->fixed_angle = (m_object_config->support_interface_pattern == smipRectilinearInterlaced ||
|
||||
m_object_config->support_interface_pattern == smipRectilinear);
|
||||
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
|
||||
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
|
||||
filler_interface->fixed_angle = true;
|
||||
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
|
||||
fill_params.dont_sort = true;
|
||||
}
|
||||
|
||||
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
|
||||
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
|
||||
@@ -2005,9 +2014,6 @@ void TreeSupport::draw_circles()
|
||||
// generate areas
|
||||
const coordf_t layer_height = config.layer_height.value;
|
||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||
const int top_base_interface_layers = std::min<int>(
|
||||
int(m_support_params.num_top_base_interface_layers),
|
||||
top_interface_layers > 0 ? int(top_interface_layers) - 1 : 0);
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
||||
@@ -2048,14 +2054,12 @@ void TreeSupport::draw_circles()
|
||||
|
||||
ExPolygons& base_areas = ts_layer->base_areas;
|
||||
ExPolygons& roof_areas = ts_layer->roof_areas;
|
||||
ExPolygons roof_base_areas;
|
||||
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
||||
ExPolygons& floor_areas = ts_layer->floor_areas;
|
||||
ExPolygons& roof_gap_areas = ts_layer->roof_gap_areas;
|
||||
coordf_t max_layers_above_base = 0;
|
||||
coordf_t max_layers_above_roof = 0;
|
||||
coordf_t max_layers_above_roof1 = 0;
|
||||
size_t first_base_roof_area = 0;
|
||||
bool floor_interface_as_base = false;
|
||||
bool has_circle_node = false;
|
||||
bool need_extra_wall = false;
|
||||
@@ -2090,6 +2094,8 @@ void TreeSupport::draw_circles()
|
||||
break;
|
||||
|
||||
const SupportNode& node = *p_node;
|
||||
// ORCA: Cap top interface height in mm based on per-node support layer height.
|
||||
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
|
||||
ExPolygons area;
|
||||
// Generate directly from overhang polygon if one of the following is true:
|
||||
// 1) node is a normal part of hybrid support
|
||||
@@ -2153,16 +2159,18 @@ void TreeSupport::draw_circles()
|
||||
|
||||
if (obj_layer_nr>0 && node.distance_to_top < 0)
|
||||
append(roof_gap_areas, area);
|
||||
// ORCA: Roof1stLayer must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
|
||||
node.is_sharp_tail == false)
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
|
||||
{
|
||||
append(roof_1st_layer, area);
|
||||
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
|
||||
}
|
||||
// ORCA: Roof layers must also fit inside the mm cap.
|
||||
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
|
||||
node.is_sharp_tail == false)
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
|
||||
{
|
||||
append(node.support_roof_layers_below <= top_base_interface_layers ? roof_base_areas : roof_areas, area);
|
||||
append(roof_areas, area);
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
|
||||
}
|
||||
else
|
||||
@@ -2176,17 +2184,9 @@ void TreeSupport::draw_circles()
|
||||
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
|
||||
|
||||
// join roof segments
|
||||
roof_areas = diff_clipped(closing_ex(roof_areas, line_width_scaled), get_collision(false));
|
||||
roof_areas = diff_clipped(offset2_ex(roof_areas, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
roof_areas = intersection_ex(roof_areas, m_machine_border);
|
||||
roof_base_areas = diff_clipped(closing_ex(roof_base_areas, line_width_scaled), get_collision(false));
|
||||
roof_base_areas = intersection_ex(roof_base_areas, m_machine_border);
|
||||
if (!roof_base_areas.empty() && !roof_areas.empty())
|
||||
roof_base_areas = diff_ex(roof_base_areas,
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas, get_extents(roof_base_areas)));
|
||||
|
||||
first_base_roof_area = roof_areas.size();
|
||||
append(roof_areas, std::move(roof_base_areas));
|
||||
roof_1st_layer = diff_clipped(closing_ex(roof_1st_layer, line_width_scaled), get_collision(false));
|
||||
roof_1st_layer = diff_clipped(offset2_ex(roof_1st_layer, line_width_scaled, -line_width_scaled), get_collision(false));
|
||||
|
||||
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
@@ -2366,11 +2366,9 @@ void TreeSupport::draw_circles()
|
||||
area_groups.back().need_infill = overlaps({ expoly }, area_poly);
|
||||
area_groups.back().need_extra_wall = need_extra_wall && !area_groups.back().need_infill;
|
||||
}
|
||||
for (size_t roof_idx = 0; roof_idx < ts_layer->roof_areas.size(); ++roof_idx) {
|
||||
auto &expoly = ts_layer->roof_areas[roof_idx];
|
||||
for (auto& expoly : ts_layer->roof_areas) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
|
||||
area_groups.back().interface_as_base = roof_idx >= first_base_roof_area;
|
||||
}
|
||||
for (auto &expoly : ts_layer->floor_areas) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
@@ -2380,7 +2378,6 @@ void TreeSupport::draw_circles()
|
||||
for (auto &expoly : ts_layer->roof_1st_layer) {
|
||||
//if (area(expoly) < SQ(scale_(1))) continue;
|
||||
area_groups.emplace_back(&expoly, SupportLayer::Roof1stLayer, max_layers_above_roof1);
|
||||
area_groups.back().interface_as_base = top_base_interface_layers > 0;
|
||||
}
|
||||
|
||||
for (auto &area_group : area_groups) {
|
||||
@@ -2409,6 +2406,7 @@ void TreeSupport::draw_circles()
|
||||
}
|
||||
});
|
||||
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
|
||||
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
|
||||
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
|
||||
int roof_interface_id = 0;
|
||||
int floor_interface_id = 0;
|
||||
@@ -2427,6 +2425,7 @@ void TreeSupport::draw_circles()
|
||||
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
|
||||
if (interlaced)
|
||||
area_group.interface_id = roof_interface_id;
|
||||
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
|
||||
has_roof_interface = true;
|
||||
} else if (area_group.type == SupportLayer::FloorType) {
|
||||
if (interlaced)
|
||||
@@ -2898,7 +2897,7 @@ void TreeSupport::drop_nodes()
|
||||
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
|
||||
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
|
||||
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top >= 0 ? 1 : 0),
|
||||
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, node_parent, print_z_next, height_next);
|
||||
get_max_move_dist(next_node);
|
||||
m_ts_data->m_mutex.lock();
|
||||
@@ -2950,7 +2949,7 @@ void TreeSupport::drop_nodes()
|
||||
for(auto& overhang:overhangs_next) {
|
||||
Point next_pt = overhang.contour.centroid();
|
||||
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top >= 0 ? 1 : 0),
|
||||
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
next_node->max_move_dist = 0;
|
||||
next_node->overhang = std::move(overhang);
|
||||
@@ -3097,7 +3096,7 @@ void TreeSupport::drop_nodes()
|
||||
auto next_collision = get_collision(0, obj_layer_nr_next);
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
||||
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
|
||||
node.support_roof_layers_below - (node.distance_to_top >= 0 ? 1 : 0),
|
||||
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
|
||||
to_buildplate, p_node, print_z_next, height_next);
|
||||
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
||||
to_outside = projection_onto(next_collision, next_node->position);
|
||||
@@ -3377,6 +3376,21 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
|
||||
const int top_layers = m_object->config().support_interface_top_layers.value;
|
||||
if (m_support_params.independent_layer_height && top_layers > 0) {
|
||||
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
|
||||
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
||||
if (contact_nodes[layer_nr].empty()) continue;
|
||||
for (SupportNode *node : contact_nodes[layer_nr]) {
|
||||
if (node->height <= EPSILON) continue;
|
||||
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
|
||||
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
|
||||
node->support_roof_layers_below = std::min(layers_fit, top_layers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log layer_heights
|
||||
for (size_t i = 0; i < layer_heights.size(); i++) {
|
||||
//if (layer_heights[i].height > EPSILON)
|
||||
@@ -3484,7 +3498,7 @@ void TreeSupport::generate_contact_points()
|
||||
if (force_add || !already_inserted.count(hash_pos)) {
|
||||
already_inserted.emplace(hash_pos);
|
||||
bool to_buildplate = true;
|
||||
size_t roof_layers = add_interface ? support_roof_layers : 0;
|
||||
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
|
||||
// add a new node as a virtual node which acts as the invisible gap between support and object
|
||||
// distance_to_top=-1: it's virtual
|
||||
// print_z=object_layer->bottom_z: it directly contacts the bottom
|
||||
|
||||
@@ -706,7 +706,7 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
|
||||
filler->spacing = flow.spacing();
|
||||
filler->angle = roof ?
|
||||
//fixme support_layer.interface_id() instead of layer_idx
|
||||
(support_params.interface_angle + ((layer_idx & 1) ? float(- M_PI_4) : float(+ M_PI_4))) :
|
||||
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
|
||||
support_params.base_angle;
|
||||
|
||||
// ORCA: use top-specific interface density after separating top/bottom settings.
|
||||
|
||||
@@ -62,7 +62,7 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
||||
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
||||
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
this->support_bottom_enable = bottom_interface_layers > 0;
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
|
||||
this->support_bottom_height = this->support_bottom_enable ?
|
||||
bottom_interface_layers * this->layer_height :
|
||||
0;
|
||||
@@ -705,7 +705,7 @@ public:
|
||||
SupportGeneratorLayersPtr& top_contacts_mutable() { return this->top_contacts; }
|
||||
|
||||
public:
|
||||
// Insert the contact layer and some of the interface and base interface layers below.
|
||||
// Insert the contact layer and some of the inteface and base interface layers below.
|
||||
void add_roofs(std::vector<Polygons> &&new_roofs, const size_t insert_layer_idx)
|
||||
{
|
||||
if (! new_roofs.empty()) {
|
||||
|
||||
@@ -42,22 +42,10 @@ struct Calib_Params
|
||||
std::string shaper_type;
|
||||
std::vector<double> accelerations;
|
||||
std::vector<double> speeds;
|
||||
// Resolved layer height for the VFA tower (0 = auto: nozzle_diameter / 2). Each speed block is a
|
||||
// fixed number of layers tall, so this also determines the physical block height / tower height.
|
||||
double vfa_layer_height = 0.0;
|
||||
// Scale the calibration model to the nozzle diameter and set the layer height accordingly (temp tower / VFA).
|
||||
// When false the 0.4 mm / 0.2 mm reference model is printed as-is.
|
||||
bool nozzle_based_resize = true;
|
||||
|
||||
CalibMode mode;
|
||||
};
|
||||
|
||||
// Number of printed layers per speed block in the VFA tower. The base model has 5 mm blocks designed
|
||||
// for a 0.2 mm layer height (0.4 mm nozzle), i.e. 25 layers per block.
|
||||
static constexpr int vfa_layers_per_block = 25;
|
||||
static constexpr double vfa_base_block_height = 5.0;
|
||||
static constexpr double vfa_base_nozzle_diameter = 0.4;
|
||||
|
||||
enum FlowRatioCalibrationType {
|
||||
COMPLETE_CALIBRATION = 0,
|
||||
FINE_CALIBRATION,
|
||||
|
||||
@@ -91,15 +91,12 @@ public:
|
||||
//
|
||||
void toggle_top_layer_only_view_range();
|
||||
//
|
||||
// Dim previous layers (ORCA)
|
||||
// Whether the layers the layer slider is not scrubbed to are rendered darkened while
|
||||
// showing less than the full print, so only the inspected layer(s) are at full brightness.
|
||||
// How bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black.
|
||||
// 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);
|
||||
float get_dim_previous_layers_brightness() const;
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
//
|
||||
// Returns true if the given option is visible.
|
||||
//
|
||||
|
||||
@@ -19,11 +19,9 @@ struct Settings
|
||||
EViewType view_type{ EViewType::FeatureType };
|
||||
ETimeMode time_mode{ ETimeMode::Normal };
|
||||
bool top_layer_only_view_range{ false };
|
||||
// ORCA: when enabled, every layer the layer slider is not scrubbed to is rendered
|
||||
// darkened (keeping its color) while showing less than the full print
|
||||
// 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 };
|
||||
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
|
||||
float dim_previous_layers_brightness{ 0.4f };
|
||||
bool spiral_vase_mode{ false };
|
||||
//
|
||||
// Required update flags
|
||||
|
||||
@@ -82,16 +82,6 @@ void Viewer::set_dim_previous_layers(bool value)
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
}
|
||||
|
||||
float Viewer::get_dim_previous_layers_brightness() const
|
||||
{
|
||||
return m_impl->get_dim_previous_layers_brightness();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers_brightness(float value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers_brightness(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_option_visible(EOptionType type) const
|
||||
{
|
||||
return m_impl->is_option_visible(type);
|
||||
|
||||
@@ -1223,12 +1223,16 @@ static float encode_color(const Color& color) {
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
// ORCA: returns the encoded color scaled towards black by 'brightness', preserving its hue.
|
||||
// 1.0 = no change, 0.0 = black.
|
||||
static float encode_color_dimmed(const Color& color, float brightness) {
|
||||
const int r = static_cast<int>(color[0] * brightness);
|
||||
const int g = static_cast<int>(color[1] * brightness);
|
||||
const int b = static_cast<int>(color[2] * brightness);
|
||||
// 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);
|
||||
}
|
||||
@@ -1244,20 +1248,15 @@ 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 (keeping its color) except the
|
||||
// one(s) the layer slider is being scrubbed to, so that only those are shown at full brightness.
|
||||
// A slider thumb marks a layer as inspected only once it is moved away from
|
||||
// its end of the print: the upper one while it is below the last layer (or while the moves
|
||||
// slider is not at the end of the layer), the lower one while it is above the first layer, so
|
||||
// trimming the print from the bottom lights up the lowest visible layer and using the slider as
|
||||
// a range lights up both ends. When neither thumb is moved the whole print is rendered normally.
|
||||
// Gated on top-layer-only mode, which the greying path below also keys off of, so that the moves
|
||||
// slider still animates normally across all layers when that mode is disabled.
|
||||
const Interval& layers_range = m_layers.get_view_range();
|
||||
const bool inspecting_top_layer = layers_range[1] + 1 < m_layers.count() || color_top_layer_only;
|
||||
const bool inspecting_bottom_layer = layers_range[0] > 0;
|
||||
const bool dim_previous_layers = m_settings.dim_previous_layers && m_settings.top_layer_only_view_range &&
|
||||
!m_layers.empty() && (inspecting_top_layer || inspecting_bottom_layer);
|
||||
// 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 (or darkened, see above). Use either that or the normal color (from the cache).
|
||||
@@ -1266,13 +1265,9 @@ void ViewerImpl::update_colors_texture()
|
||||
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];
|
||||
// ORCA: layers kept at full brightness by the dimming above are excluded from the greying below too
|
||||
const bool inspected_layer = dim_previous_layers &&
|
||||
((inspecting_top_layer && v.layer_id == layers_range[1]) ||
|
||||
(inspecting_bottom_layer && v.layer_id == layers_range[0]));
|
||||
if (dim_previous_layers && !inspected_layer && !keep_spiral_seam)
|
||||
colors[i] = encode_color_dimmed(get_vertex_color(v), m_settings.dim_previous_layers_brightness);
|
||||
else if (!inspected_layer && color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
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];
|
||||
@@ -1384,7 +1379,7 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
|
||||
// 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)
|
||||
@@ -1395,16 +1390,6 @@ void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
// ORCA: set how bright the darkened layers are rendered, 1.0 = unchanged, 0.0 = black
|
||||
void ViewerImpl::set_dim_previous_layers_brightness(float value)
|
||||
{
|
||||
value = std::clamp(value, 0.0f, 1.0f);
|
||||
if (m_settings.dim_previous_layers_brightness == value)
|
||||
return;
|
||||
m_settings.dim_previous_layers_brightness = value;
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
|
||||
{
|
||||
std::vector<ETimeMode> ret;
|
||||
|
||||
@@ -85,14 +85,9 @@ 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 every layer the layer slider is not scrubbed to, so that only the inspected
|
||||
// one(s) - the top thumb's layer, the bottom thumb's layer, or both - stay at full
|
||||
// brightness. dim_previous_layers_brightness sets how dark the rest go, 1.0 = unchanged,
|
||||
// 0.0 = black
|
||||
// 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);
|
||||
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
|
||||
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
|
||||
|
||||
|
||||
+14
-64
@@ -70,9 +70,6 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
|
||||
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
|
||||
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
|
||||
float FULL_BLACK_THRESHOLD = 0.2f;
|
||||
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
|
||||
// shadow/environment samplers when realistic view is disabled.
|
||||
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
|
||||
|
||||
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
|
||||
{
|
||||
@@ -521,37 +518,6 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
// render the outline using depth buffer and discard the pixels that are not on the outline
|
||||
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
|
||||
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
|
||||
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
|
||||
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
|
||||
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
|
||||
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
|
||||
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
|
||||
shader->get_uniform_location("msaa_samples") >= 0;
|
||||
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
|
||||
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
|
||||
// the case with realistic view off - and GL forbids two sampler types referring to the same image
|
||||
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
|
||||
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
|
||||
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
|
||||
int aa_samples = 1;
|
||||
if (use_msaa_outline) {
|
||||
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
|
||||
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
|
||||
if (value == "2" || value == "4" || value == "8" || value == "16")
|
||||
aa_samples = ::atoi(value.c_str());
|
||||
}
|
||||
// Never request more samples than the driver supports for depth textures (a 1-sample texture
|
||||
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
|
||||
GLint max_samples = 1;
|
||||
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
|
||||
if (aa_samples > max_samples)
|
||||
aa_samples = max_samples < 1 ? 1 : max_samples;
|
||||
if (aa_samples < 1)
|
||||
aa_samples = 1;
|
||||
}
|
||||
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
||||
GLuint depth_fbo = 0;
|
||||
GLuint depth_tex = 0;
|
||||
@@ -559,26 +525,21 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glGenFramebuffers(1, &depth_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
|
||||
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
if (use_msaa_outline) {
|
||||
// Multisample textures do not take filter/wrap parameters.
|
||||
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
|
||||
} else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
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(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
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, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
} else {
|
||||
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
|
||||
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
@@ -589,15 +550,12 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
|
||||
// to a sampled unit would be a feedback loop.
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
@@ -607,17 +565,13 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
}
|
||||
shader->set_uniform("is_outline", true);
|
||||
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
||||
shader->set_uniform("msaa_samples", aa_samples);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("depth_tex", depth_tex_unit);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
shader->set_uniform("depth_tex", 0);
|
||||
simple_render(shader, model_objects, colors);
|
||||
|
||||
// Some clean up to do
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
shader->set_uniform("is_outline", false);
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
@@ -1121,10 +1075,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
|
||||
const float support_normal_z = get_selection_support_normal_z();
|
||||
|
||||
// Prime depth_tex on every frame so non-outline draws do not keep the
|
||||
// default sampler unit 0, which can conflict with other sampler types.
|
||||
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
if (type == ERenderType::Transparent) {
|
||||
|
||||
@@ -192,7 +192,7 @@ PingCodeBindDialog::PingCodeBindDialog(Plater* plater /*= nullptr*/)
|
||||
|
||||
|
||||
|
||||
SetSizerAndFit(sizer_main);
|
||||
SetSizer(sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
@@ -670,7 +670,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
m_sizer_main->Add(m_sw_bind_failed_info, 0, wxALIGN_CENTER, 0);
|
||||
m_sizer_main->Add(m_simplebook, 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, ButtonProps::ChoiceButtonGap());
|
||||
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
@@ -992,7 +992,7 @@ UnBindMachineDialog::UnBindMachineDialog(Plater *plater /*= nullptr*/)
|
||||
m_sizer_main->Add(m_sizer_button, 0, wxALIGN_RIGHT | wxRIGHT, ButtonProps::ChoiceButtonGap());
|
||||
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(20));
|
||||
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
|
||||
@@ -632,8 +632,9 @@ EditCalibrationHistoryDialog::EditCalibrationHistoryDialog(wxWindow
|
||||
|
||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
SetSizerAndFit(main_sizer);
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -909,8 +910,9 @@ NewCalibrationHistoryDialog::NewCalibrationHistoryDialog(wxWindow *parent, const
|
||||
|
||||
main_sizer->Add(top_panel, 1, wxEXPAND | wxALL, FromDIP(20));
|
||||
|
||||
SetSizerAndFit(main_sizer);
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
@@ -162,7 +162,7 @@ CalibrationDialog::CalibrationDialog(Plater *plater)
|
||||
body_panel->Layout();
|
||||
|
||||
m_sizer_main->Add(body_panel, 0, wxEXPAND | wxALL, FromDIP(25));
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
|
||||
@@ -112,8 +112,9 @@ CloneDialog::CloneDialog(wxWindow *parent)
|
||||
|
||||
v_sizer->Add(bottom_sizer, 0, wxEXPAND);
|
||||
|
||||
this->SetSizerAndFit(v_sizer);
|
||||
this->SetSizer(v_sizer);
|
||||
this->Layout();
|
||||
v_sizer->Fit(this);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
|
||||
@@ -703,13 +703,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
|
||||
toggle_line("spiral_starting_flow_ratio", has_spiral_vase);
|
||||
toggle_line("spiral_finishing_flow_ratio", has_spiral_vase);
|
||||
bool has_top_shell_layers = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_top_shell = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
bool has_solid_infill = has_top_shell || has_bottom_shell;
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
toggle_field("top_surface_density", has_top_shell);
|
||||
toggle_field("bottom_surface_density", has_bottom_shell);
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
@@ -752,7 +751,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
for (auto el : { "sparse_infill_speed", "bridge_speed", "internal_bridge_speed"})
|
||||
toggle_field(el, have_infill || has_solid_infill, variant_index);
|
||||
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell_layers);
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell);
|
||||
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_shell);
|
||||
|
||||
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
|
||||
@@ -1009,11 +1008,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
||||
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
||||
|
||||
// Orca: the one-wall options act on top/bottom surfaces, which exist only with a shell. An unfilled surface
|
||||
// (0% surface density) is still a surface, so these are gated on the layer counts alone.
|
||||
toggle_line("only_one_wall_first_layer", has_bottom_shell);
|
||||
toggle_line("only_one_wall_top", has_top_shell_layers);
|
||||
toggle_line("min_width_top_surface", (has_top_shell_layers && config->opt_bool("only_one_wall_top")) || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
toggle_line("min_width_top_surface", config->opt_bool("only_one_wall_top") || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
|
||||
for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "hole_to_polyhole_max_edges" })
|
||||
toggle_line(el, config->opt_bool("hole_to_polyhole"));
|
||||
|
||||
@@ -80,8 +80,9 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons
|
||||
|
||||
main_sizer->Add(sizer_top);
|
||||
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->SetSizer(main_sizer);
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
CentreOnParent();
|
||||
|
||||
m_textCtrl_code->Bind(wxEVT_TEXT, &ConnectPrinterDialog::on_input_enter, this);
|
||||
|
||||
@@ -112,8 +112,9 @@ DownloadProgressDialog::DownloadProgressDialog(wxString title)
|
||||
m_simplebook_status->AddPage(m_panel_download_failed, wxEmptyString, false);
|
||||
m_simplebook_status->AddPage(m_panel_install_failed, wxEmptyString, false);
|
||||
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, &DownloadProgressDialog::on_close, this);
|
||||
|
||||
@@ -261,7 +261,7 @@ void ExtrusionCalibration::create()
|
||||
top_sizer->Add(FromDIP(24), 0);
|
||||
top_sizer->Add(sizer_main, 1, wxEXPAND);
|
||||
top_sizer->Add(FromDIP(24), 0);
|
||||
SetSizerAndFit(top_sizer);
|
||||
SetSizer(top_sizer);
|
||||
|
||||
// set default nozzle
|
||||
m_comboBox_nozzle_dia->SetSelection(1);
|
||||
@@ -271,6 +271,7 @@ void ExtrusionCalibration::create()
|
||||
set_step(1);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
m_k_val->GetTextCtrl()->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) {
|
||||
input_value_finish();
|
||||
|
||||
@@ -1334,7 +1334,27 @@ void SpinCtrl::BUILD() {
|
||||
if (!parsed || value < INT_MIN || value > INT_MAX)
|
||||
tmp_value = UNDEF_VALUE;
|
||||
else {
|
||||
tmp_value = (int)value;
|
||||
tmp_value = std::min(std::max((int)value, temp->GetMin()), temp->GetMax());
|
||||
#ifdef __WXOSX__
|
||||
#ifdef UNDEFINED__WXOSX__ // BBS
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
SpinInput* spin = static_cast<SpinInput*>(window);
|
||||
spin->SetValue(tmp_value);
|
||||
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
|
||||
// discard this selection and set insertion point to the end of string
|
||||
// temp->GetText()->SetInsertionPointEnd();
|
||||
#endif
|
||||
#else
|
||||
// update value for the control only if it was changed in respect to the Min/max values
|
||||
if (tmp_value != (int)value) {
|
||||
temp->SetValue(tmp_value);
|
||||
// But after SetValue() cursor ison the first position
|
||||
// so put it to the end of string
|
||||
// int pos = std::to_string(tmp_value).length();
|
||||
// temp->SetSelection(pos, pos);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}), temp->GetTextCtrl()->GetId());
|
||||
|
||||
@@ -1355,10 +1375,6 @@ void SpinCtrl::propagate_value()
|
||||
on_kill_focus();
|
||||
} else {
|
||||
auto ctrl = dynamic_cast<SpinInput *>(window);
|
||||
tmp_value = std::min(std::max(tmp_value, ctrl->GetMin()), ctrl->GetMax());
|
||||
if (ctrl->GetValue() != tmp_value)
|
||||
ctrl->SetValue(tmp_value); // Clamp now when the user is done typing (kill focus / Enter / spin arrows)
|
||||
|
||||
if (m_value.empty()
|
||||
? !ctrl->GetTextCtrl()->GetLabel().IsEmpty()
|
||||
: ctrl->GetValue() != boost::any_cast<int>(m_value))
|
||||
|
||||
@@ -105,8 +105,9 @@ FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fil
|
||||
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
|
||||
container_sizer->Add(dlg_btns, 0, wxEXPAND);
|
||||
|
||||
SetSizerAndFit(container_sizer);
|
||||
SetSizer(container_sizer);
|
||||
Layout();
|
||||
container_sizer->Fit(this);
|
||||
|
||||
// Position the dialog relative to the parent window
|
||||
if (GetParent()) {
|
||||
|
||||
@@ -1134,9 +1134,8 @@ 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 the layers the preview layer slider is not scrubbed to
|
||||
// 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"));
|
||||
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
|
||||
|
||||
// avoid processing if called with the same gcode_result
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
|
||||
|
||||
@@ -333,12 +333,9 @@ public:
|
||||
|
||||
libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); }
|
||||
|
||||
// ORCA: darken the layers not scrubbed to while using the preview layer slider
|
||||
// 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(); }
|
||||
// ORCA: brightness of those darkened layers, 1.0 = unchanged, 0.0 = black
|
||||
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
|
||||
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
|
||||
@@ -6047,10 +6047,10 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "Y"); // ORCA use uppercase to match text on tranform widgets
|
||||
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], _u8L_CONTEXT("Front", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _u8L_CONTEXT("Top", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _u8L_CONTEXT("Bottom", "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], _u8L_CONTEXT("Left", "Camera View").c_str());
|
||||
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
const char* css =
|
||||
"button, button:hover, button:active, button:focus {"
|
||||
"button {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
@@ -589,58 +589,6 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveInputBorder(wxWindow* win)
|
||||
{
|
||||
GtkWidget* widget = win->GetHandle();
|
||||
if (!widget) return;
|
||||
|
||||
#if GTK_CHECK_VERSION(3, 0, 0)
|
||||
// GTK3+: use CSS provider
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
// Target 'entry' and its inner subnodes (like text selection areas)
|
||||
const char* css =
|
||||
"entry, entry text, entry undershoot {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
" padding: 0px;"
|
||||
" margin: 0px;"
|
||||
" min-height: 0px;"
|
||||
" min-width: 0px;"
|
||||
" background: none;"
|
||||
"}";
|
||||
|
||||
#if GTK_CHECK_VERSION(4, 0, 0)
|
||||
// GTK4
|
||||
gtk_css_provider_load_from_data(provider, css, -1);
|
||||
#else
|
||||
// GTK3
|
||||
gtk_css_provider_load_from_data(provider, css, -1, nullptr);
|
||||
#endif
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(widget);
|
||||
gtk_style_context_add_provider(
|
||||
ctx,
|
||||
GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER
|
||||
);
|
||||
g_object_unref(provider);
|
||||
|
||||
#else
|
||||
// GTK2: Target the x/y thickness of the entry widget
|
||||
gtk_rc_parse_string(
|
||||
"style \"no-padding-entry\" {"
|
||||
" xthickness = 0"
|
||||
" ythickness = 0"
|
||||
" GtkEntry::inner-border = { 0, 0, 0, 0 }"
|
||||
" GtkEntry::focus-line-width = 0"
|
||||
"}"
|
||||
"class \"GtkEntry\" style \"no-padding-entry\""
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif // __WXGTK__
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -472,9 +472,8 @@ void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||
void staticbox_remove_margin(wxStaticBox* sb);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK__
|
||||
void RemoveButtonBorder(wxWindow* win); // for wxButton/wxBitmapToggleButton based controls (SwitchButton, CheckBox)
|
||||
void RemoveInputBorder(wxWindow* win); // for TextCtrl based controls (TextInput, ComboBox, SpinInput..)
|
||||
#ifdef __WXGTK3__
|
||||
void RemoveButtonBorder(wxWindow* win);
|
||||
#endif
|
||||
|
||||
#if defined(__WXOSX__) || defined(__linux__)
|
||||
|
||||
@@ -3502,7 +3502,7 @@ static void check_objects_after_cut(const ModelObjectPtrs& objects)
|
||||
names += ", " + from_u8(err_objects_names[i]);
|
||||
WarningDialog(wxGetApp().plater(), format_wxstr(_L("Objects(%1%) have duplicated connectors. "
|
||||
"Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."), names)).ShowModal();
|
||||
}
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ void ArrangeJob::process(Ctl &ctl)
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this, &ctl](unsigned num_finished, std::string str = "") {
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging ") + str);
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging") + str);
|
||||
};
|
||||
|
||||
{
|
||||
|
||||
@@ -2654,14 +2654,14 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame,
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//view_menu->AppendSeparator();
|
||||
//TRN To be shown in the main menu View->Top
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Top", "Camera View") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Top") + "\t" + ctrl + "1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
//TRN To be shown in the main menu View->Bottom
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Bottom", "Camera View") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Bottom") + "\t" + ctrl + "2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Front", "Camera View") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Front") + "\t" + ctrl + "3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Rear", "Camera View") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
append_menu_item(view_menu, wxID_ANY, _L("Rear") + "\t" + ctrl + "4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
append_menu_item(view_menu, wxID_ANY, _L_CONTEXT("Left", "Camera View") + "\t" + ctrl + "5", _L("Left View"),[mainFrame](wxCommandEvent &) {mainFrame->select_view("left"); },
|
||||
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
|
||||
|
||||
@@ -65,7 +65,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
||||
main_sizer->Add(btn_sizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND | wxTOP, FromDIP(10));
|
||||
|
||||
apply_style(style);
|
||||
SetSizer(main_sizer);
|
||||
SetSizerAndFit(main_sizer);
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,6 @@ void MsgDialog::apply_style(long style)
|
||||
|
||||
void MsgDialog::finalize()
|
||||
{
|
||||
GetSizer()->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
@@ -548,7 +547,7 @@ DeleteConfirmDialog::DeleteConfirmDialog(wxWindow *parent, const wxString &title
|
||||
m_del_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_OK); });
|
||||
m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { EndModal(wxID_CANCEL); });
|
||||
|
||||
SetSizerAndFit(m_main_sizer);
|
||||
SetSizer(m_main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -583,7 +582,7 @@ Newer3mfVersionDialog::Newer3mfVersionDialog(wxWindow *parent, const Semver *fil
|
||||
main_sizer->Add(content_sizer, 0, wxEXPAND | wxALL, FromDIP(5));
|
||||
main_sizer->Add(get_btn_sizer(), 0, wxEXPAND | wxALL, FromDIP(5));
|
||||
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->SetSizer(main_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
@@ -746,8 +745,9 @@ NetworkErrorDialog::NetworkErrorDialog(wxWindow* parent)
|
||||
sizer_main->Add(sizer_button, 1, wxEXPAND | wxLEFT | wxRIGHT, 15);
|
||||
sizer_main->Add(0, 0, 0, wxTOP, 18);
|
||||
|
||||
SetSizerAndFit(sizer_main);
|
||||
SetSizer(sizer_main);
|
||||
Layout();
|
||||
sizer_main->Fit(this);
|
||||
Centre(wxBOTH);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ NetworkPluginDownloadDialog::NetworkPluginDownloadDialog(wxWindow* parent, Mode
|
||||
} else {
|
||||
create_missing_plugin_ui();
|
||||
}
|
||||
main_sizer->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
|
||||
@@ -47,7 +47,7 @@ NetworkTestDialog::NetworkTestDialog(wxWindow* parent, wxWindowID id, const wxSt
|
||||
|
||||
init_bind();
|
||||
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->SetSizer(main_sizer);
|
||||
this->Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
@@ -270,7 +270,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_simplebook->AddPage(m_book_third_panel, _("dialog page"), false);
|
||||
m_sizer->Add(m_simplebook, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
SetSizerAndFit(m_sizer);
|
||||
SetSizer(m_sizer);
|
||||
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
|
||||
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
|
||||
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
|
||||
@@ -281,6 +281,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
|
||||
|
||||
Layout();
|
||||
Fit();
|
||||
CentreOnParent();
|
||||
}
|
||||
|
||||
|
||||
+12
-52
@@ -14183,7 +14183,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
}
|
||||
}
|
||||
|
||||
if (params.nozzle_based_resize && std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
if (std::abs(nozzle_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(nozzle_scale, nozzle_scale, nozzle_scale);
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
@@ -14191,9 +14191,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature_initial_layer", (int) start_temp);
|
||||
set_config_values<int, ConfigOptionInts>(filament_config, "nozzle_temperature", (int) start_temp);
|
||||
// When resizing is disabled the 0.4 mm / 0.2 mm reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize)
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(5.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14204,8 +14202,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
||||
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
if (params.nozzle_based_resize)
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(nozzle_diameter/2));
|
||||
|
||||
|
||||
changed_objects({ 0 });
|
||||
@@ -14369,42 +14366,6 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config;
|
||||
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
|
||||
const ConfigOptionFloats* nozzle_diameter_config = printer_config->option<ConfigOptionFloats>("nozzle_diameter");
|
||||
size_t nozzle_id = static_cast<size_t>(std::max(params.extruder_id, 0));
|
||||
double nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
if (nozzle_diameter_config && !nozzle_diameter_config->values.empty()) {
|
||||
nozzle_id = std::min(nozzle_id, nozzle_diameter_config->values.size() - 1);
|
||||
nozzle_diameter = nozzle_diameter_config->values[nozzle_id];
|
||||
}
|
||||
if (nozzle_diameter <= 0.0)
|
||||
nozzle_diameter = vfa_base_nozzle_diameter;
|
||||
|
||||
// Resolved layer height: use the (possibly auto-adjusted) value from the dialog, else default to nozzle/2.
|
||||
double layer_height = params.vfa_layer_height > 0.0 ? params.vfa_layer_height : nozzle_diameter / 2.0;
|
||||
|
||||
// cut upper (on the unscaled model, using the base block height); the scaling below keeps the physical
|
||||
// block height (vfa_layers_per_block * layer_height) in sync with the speed stepping in GCode::process_layer.
|
||||
// Subtract EPSILON (as the temperature tower does) so the cut lands just below the flat block surface instead
|
||||
// of exactly on it, which would otherwise add a degenerate extra layer.
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = vfa_base_block_height * ((params.end - params.start) / params.step + 1) - EPSILON;
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
// When resizing is enabled, XY scales with the nozzle (footprint / line width) and Z scales so each base
|
||||
// block becomes vfa_layers_per_block layers of the resolved layer height. When disabled the 0.4 mm / 0.2 mm
|
||||
// reference model is printed as-is (preset layer height kept).
|
||||
if (params.nozzle_based_resize) {
|
||||
const double xy_scale = nozzle_diameter / vfa_base_nozzle_diameter;
|
||||
const double z_scale = (vfa_layers_per_block * layer_height) / vfa_base_block_height;
|
||||
if (std::abs(xy_scale - 1.0) > EPSILON || std::abs(z_scale - 1.0) > EPSILON)
|
||||
model().objects[0]->scale(xy_scale, xy_scale, z_scale);
|
||||
}
|
||||
|
||||
model().objects[0]->ensure_on_bed();
|
||||
|
||||
printer_config->set_key_value("resonance_avoidance", new ConfigOptionBool{false});
|
||||
filament_config->set_key_value("slow_down_layer_time", new ConfigOptionFloats { 0.0 });
|
||||
set_config_values<bool, ConfigOptionBoolsNullable>(print_config, "enable_overhang_speed", false);
|
||||
@@ -14418,10 +14379,6 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
print_config->set_key_value("spiral_mode", new ConfigOptionBool(true));
|
||||
print_config->set_key_value("enable_wrapping_detection", new ConfigOptionBool(false));
|
||||
print_config->set_key_value("precise_z_height", new ConfigOptionBool(false));
|
||||
if (params.nozzle_based_resize) {
|
||||
print_config->set_key_value("initial_layer_print_height", new ConfigOptionFloat(layer_height));
|
||||
model().objects[0]->config.set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||
}
|
||||
model().objects[0]->config.set_key_value("brim_type", new ConfigOptionEnum<BrimType>(btOuterOnly));
|
||||
model().objects[0]->config.set_key_value("brim_width", new ConfigOptionFloat(3.0));
|
||||
model().objects[0]->config.set_key_value("brim_object_gap", new ConfigOptionFloat(0.0));
|
||||
@@ -14432,11 +14389,14 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->update_ui_from_settings();
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->update_ui_from_settings();
|
||||
|
||||
// Pass the resolved layer height on (only meaningful when resized). GCode's VFA stepping is layer-based, so
|
||||
// it does not require it, but keep it consistent with the geometry.
|
||||
Calib_Params calib_params = params;
|
||||
calib_params.vfa_layer_height = params.nozzle_based_resize ? layer_height : 0.0;
|
||||
p->background_process.fff_print()->set_calib_params(calib_params);
|
||||
// cut upper
|
||||
auto obj_bb = model().objects[0]->bounding_box_exact();
|
||||
auto height = 5 * ((params.end - params.start) / params.step + 1);
|
||||
if (height < obj_bb.size().z()) {
|
||||
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
|
||||
}
|
||||
|
||||
p->background_process.fff_print()->set_calib_params(params);
|
||||
}
|
||||
|
||||
void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
||||
@@ -15134,7 +15094,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string &filename)
|
||||
|
||||
m_sizer_main->Add(dlg_btns, 0, wxEXPAND);
|
||||
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Slic3r
|
||||
// IMPORTANT: ordinal order is the Plugins dialog Source sort priority.
|
||||
Mine,
|
||||
Subscribed,
|
||||
Orphaned,
|
||||
Local
|
||||
};
|
||||
|
||||
@@ -21,7 +20,6 @@ namespace Slic3r
|
||||
{
|
||||
case PluginSource::Mine: return "mine";
|
||||
case PluginSource::Subscribed: return "subscribed";
|
||||
case PluginSource::Orphaned: return "orphaned";
|
||||
case PluginSource::Local: return "local";
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@ struct PluginDialogItem
|
||||
bool loading = false;
|
||||
|
||||
bool is_cloud_plugin = false;
|
||||
bool orphaned = false;
|
||||
bool has_local_package = false;
|
||||
bool unauthorized = false;
|
||||
bool has_script_capability = false;
|
||||
@@ -247,7 +246,6 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
|
||||
payload_item["sharing_token"] = dialog_item.sharing_token;
|
||||
payload_item["thumbnail_url"] = dialog_item.thumbnail_url;
|
||||
payload_item["installed"] = dialog_item.has_local_package;
|
||||
payload_item["orphaned"] = dialog_item.orphaned;
|
||||
payload_item["installed_version"] = dialog_item.installed_version;
|
||||
payload_item["latest_version"] = dialog_item.latest_version;
|
||||
return payload_item;
|
||||
@@ -270,8 +268,7 @@ PluginSource derive_plugin_source(const PluginDescriptor& descriptor)
|
||||
const bool is_cloud = descriptor.is_cloud_plugin();
|
||||
const bool is_mine = is_cloud && has_cloud_meta && descriptor.cloud->is_mine;
|
||||
|
||||
if (is_cloud && has_cloud_meta && descriptor.cloud->orphaned)
|
||||
return PluginSource::Orphaned;
|
||||
// Source is ownership/locality only; issue states never replace this badge.
|
||||
if (is_mine)
|
||||
return PluginSource::Mine;
|
||||
if (is_cloud)
|
||||
@@ -284,12 +281,11 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
PluginAvailableActions available_actions;
|
||||
const bool is_loading = item.status == PluginStatus::Loading;
|
||||
const bool is_cloud = item.is_cloud_plugin;
|
||||
const bool is_orphaned = item.orphaned;
|
||||
const bool is_mine = item.source == PluginSource::Mine;
|
||||
const bool has_local = item.has_local_package;
|
||||
const bool authorized_for_install = !item.unauthorized;
|
||||
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !is_orphaned && !has_local && authorized_for_install;
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !has_local && authorized_for_install;
|
||||
available_actions.can_toggle = !is_loading && (has_local || available_actions.toggle_installs_cloud_plugin);
|
||||
|
||||
auto add_action = [&available_actions](const char* id, const char* label, bool enabled = true, bool danger = false) {
|
||||
@@ -298,7 +294,7 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
|
||||
// Owned cloud plugins fall through to the local delete: it removes the installed package only.
|
||||
// Deleting a plugin from the cloud is a plugin hub operation and is never offered here.
|
||||
if (is_cloud && !is_orphaned && !is_mine) {
|
||||
if (is_cloud && !is_mine) {
|
||||
add_action("unsubscribe_plugin", "Unsubscribe", true, true);
|
||||
} else if (has_local) {
|
||||
add_action("delete_plugin", "Delete", true, true);
|
||||
@@ -306,13 +302,11 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
|
||||
add_action("open_folder", "Show in folder", has_local);
|
||||
|
||||
if (!is_orphaned) {
|
||||
if (is_cloud) {
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
} else {
|
||||
add_action("reload_plugin", "Reload");
|
||||
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||
}
|
||||
if (is_cloud) {
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
} else {
|
||||
add_action("reload_plugin", "Reload");
|
||||
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||
}
|
||||
|
||||
return available_actions;
|
||||
@@ -366,7 +360,6 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
|
||||
item.error_text = descriptor.normalized_error();
|
||||
item.has_error = descriptor.has_error();
|
||||
item.is_cloud_plugin = descriptor.is_cloud_plugin();
|
||||
item.orphaned = descriptor.cloud.has_value() && descriptor.cloud->orphaned;
|
||||
item.has_local_package = descriptor.has_local_package();
|
||||
item.unauthorized = descriptor.is_unauthorized();
|
||||
item.is_loaded = manager.is_plugin_loaded(descriptor.plugin_key);
|
||||
@@ -599,35 +592,7 @@ bool PluginsDialog::get_descriptor(const std::string& plugin_key, PluginDescript
|
||||
|
||||
void PluginsDialog::refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud)
|
||||
{
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() {
|
||||
prompt_for_missing_plugins();
|
||||
send_plugins();
|
||||
}, title, message);
|
||||
}
|
||||
|
||||
void PluginsDialog::prompt_for_missing_plugins()
|
||||
{
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
const std::vector<PluginDescriptor> missing = manager.get_missing_plugin_descriptors();
|
||||
if (missing.empty())
|
||||
return;
|
||||
|
||||
wxString names;
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(missing.size());
|
||||
for (const PluginDescriptor& plugin : missing) {
|
||||
keys.push_back(plugin.plugin_key);
|
||||
names += "\n- ";
|
||||
names += plugin_display_name(plugin.plugin_key);
|
||||
}
|
||||
|
||||
const int result = wxMessageBox(
|
||||
wxString::Format(_L("The following installed plugins were not found on disk:\n%s\n\nRemove them from OrcaSlicer?"), names),
|
||||
_L("Missing Plugins"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
restore_z_order();
|
||||
|
||||
if (result == wxYES)
|
||||
manager.remove_missing_plugins(keys);
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message);
|
||||
}
|
||||
|
||||
void PluginsDialog::refresh_plugins()
|
||||
|
||||
@@ -68,7 +68,6 @@ private:
|
||||
bool get_descriptor(const std::string& plugin_key, Slic3r::PluginDescriptor& descriptor) const;
|
||||
|
||||
void refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud);
|
||||
void prompt_for_missing_plugins();
|
||||
void refresh_plugins();
|
||||
void toggle_plugin(const std::string& plugin_key, bool enabled);
|
||||
void toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, bool enabled);
|
||||
|
||||
@@ -700,12 +700,6 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit
|
||||
auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param)));
|
||||
input->SetToolTip(tip);
|
||||
|
||||
// ORCA: this one is only meaningful while the dimming it controls is enabled
|
||||
if (param == "preview_dim_previous_layers_brightness") {
|
||||
m_dim_previous_layers_brightness_input = input;
|
||||
input->Enable(app_config->get_bool("preview_dim_previous_layers"));
|
||||
}
|
||||
|
||||
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
if(!title2.empty()){
|
||||
@@ -1056,10 +1050,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview (ported from preFlight)
|
||||
else if (param == "preview_dim_previous_layers") {
|
||||
if (m_dim_previous_layers_brightness_input)
|
||||
m_dim_previous_layers_brightness_input->Enable(app_config->get_bool(param));
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers(app_config->get_bool(param));
|
||||
@@ -1922,28 +1914,6 @@ void PreferencesDialog::create_items()
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers);
|
||||
|
||||
auto item_dim_previous_layers_brightness = create_item_spinctrl(
|
||||
_L("Dimmed layer brightness"),
|
||||
"",
|
||||
_L("%"),
|
||||
_L("How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."),
|
||||
"preview_dim_previous_layers_brightness",
|
||||
0,
|
||||
99,
|
||||
// ORCA: apply the new brightness immediately to the currently loaded preview
|
||||
[](int value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers_brightness(0.01f * value);
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers_brightness);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/SpinInput.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
@@ -72,7 +71,6 @@ public:
|
||||
::CheckBox * m_sync_user_preset_checkbox = {nullptr};
|
||||
::CheckBox * m_bambu_cloud_checkbox = {nullptr};
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::SpinInput *m_dim_previous_layers_brightness_input = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
std::vector<NetworkLibraryVersionInfo> m_available_versions;
|
||||
|
||||
|
||||
@@ -865,9 +865,6 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
clr_picker = new wxBitmapButton(parent, wxID_ANY, {}, wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), wxBU_EXACTFIT | wxBU_AUTODRAW | wxBORDER_NONE);
|
||||
clr_picker->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
clr_picker->SetToolTip(_L("Click to select filament color"));
|
||||
#ifdef __WXGTK__
|
||||
RemoveButtonBorder(clr_picker);
|
||||
#endif
|
||||
clr_picker->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
// Check if it's an official filament
|
||||
auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data());
|
||||
|
||||
@@ -28,7 +28,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
||||
{
|
||||
this->SetDoubleBuffered(true);
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
// SetMinSize(FromDIP(wxSize{wxDefaultCoord,520}));
|
||||
SetSize(FromDIP(480),FromDIP(520));
|
||||
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
@@ -50,8 +50,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
||||
m_scrollwindow->FitInside();
|
||||
|
||||
this->Layout();
|
||||
mainSizer->SetMinSize(wxDefaultCoord, FromDIP(520));
|
||||
mainSizer->Fit(this);
|
||||
// mainSizer->Fit(this);
|
||||
//this->Fit();
|
||||
|
||||
m_cb_ai_monitoring->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) {
|
||||
@@ -1671,9 +1670,12 @@ PrinterPartsDialog::PrinterPartsDialog(wxWindow* parent)
|
||||
/*inset data*/
|
||||
sizer->Add(single_panel, 0, wxEXPAND, 0);
|
||||
sizer->Add(multiple_panel, 0, wxEXPAND, 0);
|
||||
single_panel->Hide();
|
||||
SetSizerAndFit(sizer);
|
||||
SetSizer(sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
single_panel->Hide();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
@@ -1750,7 +1752,6 @@ bool PrinterPartsDialog::Show(bool show)
|
||||
}
|
||||
}
|
||||
|
||||
GetSizer()->SetSizeHints(this);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ PublishDialog::PublishDialog(Plater *plater)
|
||||
top_sizer->Add(m_main_sizer, 1, wxALL | wxEXPAND, 0);
|
||||
top_sizer->Add(FromDIP(30), 0, 0, wxEXPAND, 0);
|
||||
|
||||
this->SetSizerAndFit(top_sizer);
|
||||
this->SetSizer(top_sizer);
|
||||
this->Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
@@ -310,9 +310,10 @@ StepMeshDialog::StepMeshDialog(wxWindow* parent, Slic3r::Step& file, double line
|
||||
|
||||
bSizer->Add(bSizer_button, 1, wxEXPAND);
|
||||
|
||||
this->SetSizerAndFit(bSizer);
|
||||
this->SetSizer(bSizer);
|
||||
update_mesh_number_text();
|
||||
this->Layout();
|
||||
bSizer->Fit(this);
|
||||
|
||||
this->Bind(wxEVT_LEFT_DOWN, [this](auto& e) {
|
||||
SetFocusIgnoringChildren();
|
||||
|
||||
@@ -354,7 +354,7 @@ TroubleshootDialog::TroubleshootDialog()
|
||||
m_sizer->AddSpacer(FromDIP(20));
|
||||
m_sizer->Add(right_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, FromDIP(15));
|
||||
|
||||
SetSizerAndFit(m_sizer);
|
||||
SetSizer(m_sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
@@ -168,9 +168,6 @@ private:
|
||||
}
|
||||
wxClientDC dc(this);
|
||||
int cWidth = GetClientSize().GetWidth();
|
||||
// Don't compute/commit a size based on a not-yet-laid-out width
|
||||
// Mirrors the guard in OnPaint() so both use the same wrap results
|
||||
if (cWidth < 50) return;
|
||||
|
||||
int y = 0;
|
||||
for (size_t i = 0; i < m_lines.size(); ++i) {
|
||||
|
||||
@@ -773,7 +773,7 @@ std::vector<std::string> DiffViewCtrl::selected_options()
|
||||
|
||||
static std::string none{"none"};
|
||||
#define UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE wxSize(FromDIP(490), FromDIP(374))
|
||||
#define UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE wxSize(FromDIP(490), -1)
|
||||
#define UNSAVE_CHANGE_DIALOG_ACTION_LINE_SIZE wxSize(FromDIP(490), FromDIP(60))
|
||||
#define UNSAVE_CHANGE_DIALOG_FIRST_VALUE_WIDTH FromDIP(190)
|
||||
#define UNSAVE_CHANGE_DIALOG_VALUE_WIDTH FromDIP(150)
|
||||
#define UNSAVE_CHANGE_DIALOG_ITEM_HEIGHT FromDIP(24)
|
||||
@@ -1075,6 +1075,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
||||
m_sizer_main->Add(m_sizer_button, 0, wxEXPAND | wxTOP, 6);
|
||||
m_sizer_main->Add(0, 0, 1, wxTOP, 18);
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
Centre(wxBOTH);
|
||||
|
||||
if (params) {
|
||||
if (params->left_to_right)
|
||||
update_tree(type, params->config, params->from, params->to);
|
||||
@@ -1090,11 +1095,6 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
||||
//topSizer->SetSizeHints(this);
|
||||
|
||||
show_info_line(Action::Undef);
|
||||
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
// Centre(wxBOTH);
|
||||
}
|
||||
|
||||
void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name)
|
||||
@@ -1499,7 +1499,6 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent
|
||||
}
|
||||
|
||||
m_action_line->SetLabel(action_msg);
|
||||
m_action_line->Wrap(UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE.x);
|
||||
|
||||
update_tree(type, presets);
|
||||
update_list();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user