mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6140110f2 | ||
|
|
a6dbf2502d | ||
|
|
8a6377f087 | ||
|
|
c86e33db6d | ||
|
|
7b0e2f3ce5 | ||
|
|
d80c69341c | ||
|
|
4a63a7d916 | ||
|
|
de1dfd0611 | ||
|
|
11a5971cef | ||
|
|
82f37ddb3a | ||
|
|
fb03d1a1cb | ||
|
|
2b4bdead73 | ||
|
|
b1f0d6c6f6 | ||
|
|
14751a8b06 | ||
|
|
9a86d79038 | ||
|
|
d905f1a39b | ||
|
|
84ec518f26 | ||
|
|
1fc153308f | ||
|
|
3167c3665a | ||
|
|
6d34d83e78 |
@@ -30,8 +30,9 @@ how you read the wx docs:
|
|||||||
Look things up in the source the app is built from — it beats memory, and 3.3 changed real behaviour:
|
Look things up in the source the app is built from — it beats memory, and 3.3 changed real behaviour:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
WX=$(find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' | head -1)
|
WX=$(find -L deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets' 2>/dev/null | head -1)
|
||||||
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux: deps/build/dep_wxWidgets-prefix/...
|
# macOS: deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets Linux, Windows: deps/<tree>/dep_wxWidgets-prefix/...
|
||||||
|
# -L follows a worktree's deps/<tree> symlinked to the main checkout. Not a glob: zsh aborts on one that matches nothing.
|
||||||
# If deps are not built: git clone --depth 1 -b v3.3.2 https://github.com/SoftFever/Orca-deps-wxWidgets
|
# If deps are not built: git clone --depth 1 -b v3.3.2 https://github.com/SoftFever/Orca-deps-wxWidgets
|
||||||
grep -n "CaptureMouse" -A 30 $WX/interface/wx/window.h # documented contract (doxygen source)
|
grep -n "CaptureMouse" -A 30 $WX/interface/wx/window.h # documented contract (doxygen source)
|
||||||
grep -rn "@onlyfor\|not implemented" $WX/interface/wx/popupwin.h # documented platform limits
|
grep -rn "@onlyfor\|not implemented" $WX/interface/wx/popupwin.h # documented platform limits
|
||||||
@@ -40,6 +41,13 @@ grep -n "IsDark" $WX/docs/changes.txt # what changed in
|
|||||||
grep -n "NotifyCaptureLost" -r $WX/src/osx $WX/src/gtk $WX/src/msw # what each port actually does
|
grep -n "NotifyCaptureLost" -r $WX/src/osx $WX/src/gtk $WX/src/msw # what each port actually does
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On Windows these lookups are bash: run them from Git Bash. PowerShell has no `grep`, and its `find` is
|
||||||
|
Windows' text-search `find.exe`. To locate the wx tree from PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$WX = Resolve-Path deps\*\dep_wxWidgets-prefix\src\dep_wxWidgets, deps\*\*\dep_wxWidgets-prefix\src\dep_wxWidgets -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Path
|
||||||
|
```
|
||||||
|
|
||||||
`interface/wx/<class>.h` is the documentation; `src/common` holds shared behaviour and
|
`interface/wx/<class>.h` is the documentation; `src/common` holds shared behaviour and
|
||||||
`src/{msw,osx,gtk,unix,generic}` the per-port implementation. When the docs and the source disagree,
|
`src/{msw,osx,gtk,unix,generic}` the per-port implementation. When the docs and the source disagree,
|
||||||
the source is what runs — the references mark such facts **[source]**. Orca-side design docs live in
|
the source is what runs — the references mark such facts **[source]**. Orca-side design docs live in
|
||||||
|
|||||||
@@ -331,6 +331,10 @@ OrcaSlicer: integer spinners are `::SpinInput` ([below](#spininput)); progress b
|
|||||||
Contract:
|
Contract:
|
||||||
- A page must be created with the book as its parent and added once; the book owns and deletes it
|
- A page must be created with the book as its parent and added once; the book owns and deletes it
|
||||||
(`interface/wx/bookctrl.h:253-254, 273`). `RemovePage` detaches without deleting, and you then own it (`:324-330`).
|
(`interface/wx/bookctrl.h:253-254, 273`). `RemovePage` detaches without deleting, and you then own it (`:324-330`).
|
||||||
|
- Removing the selected page selects the page before it (the new first page if it was first) through `SetSelection`,
|
||||||
|
so that page is shown and PAGE_CHANGING/CHANGED are sent; removing a page before the selection only shifts the
|
||||||
|
index. This is `wxBookCtrlBase::DoSetSelectionAfterRemoval` (`src/common/bookctrl.cpp:477-495` **[source]**),
|
||||||
|
called from `DoRemovePage` by `wxSimplebook`, `wxChoicebook`, `wxListbook`, `wxToolbook` and Orca's `Notebook`.
|
||||||
- `GetSelection()` inside a `PAGE_CHANGED` handler may return the old or the new page depending on the platform; use
|
- `GetSelection()` inside a `PAGE_CHANGED` handler may return the old or the new page depending on the platform; use
|
||||||
`event.GetSelection()` (`interface/wx/bookctrl.h:160-166`).
|
`event.GetSelection()` (`interface/wx/bookctrl.h:160-166`).
|
||||||
- `wxSimplebook` has no UI; switch with `ChangeSelection()`. `SetSelection()` sends PAGE_CHANGING/CHANGED
|
- `wxSimplebook` has no UI; switch with `ChangeSelection()`. `SetSelection()` sends PAGE_CHANGING/CHANGED
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ wx asserts are compiled out in Orca (`wxDEBUG_LEVEL=0`), so every misuse below t
|
|||||||
an assert fails silently. "GTK" means wxGTK3, Orca's Linux default (X11 and Wayland); GTK2 is only
|
an assert fails silently. "GTK" means wxGTK3, Orca's Linux default (X11 and Wayland); GTK2 is only
|
||||||
an opt-out build (`-DDEP_WX_GTK3=OFF`), noted where it differs. Paths starting `interface/`,
|
an opt-out build (`-DDEP_WX_GTK3=OFF`), noted where it differs. Paths starting `interface/`,
|
||||||
`include/`, `src/`, `docs/` are in the wx tree
|
`include/`, `src/`, `docs/` are in the wx tree
|
||||||
(`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`); Orca paths are
|
(located as in `SKILL.md` §Ground truth); Orca paths are
|
||||||
relative to `src/slic3r/GUI/`.
|
relative to `src/slic3r/GUI/`.
|
||||||
|
|
||||||
Contents: [Rules](#rules) · [Mouse capture](#mouse-capture) · [Mouse events](#mouse-events) ·
|
Contents: [Rules](#rules) · [Mouse capture](#mouse-capture) · [Mouse events](#mouse-events) ·
|
||||||
|
|||||||
@@ -332,7 +332,9 @@ indices: pages come and go per printer and per feature flag.
|
|||||||
pages depending on the printer and on `use_printer_agents`; a removed page stays registered but is
|
pages depending on the printer and on `use_printer_agents`; a removed page stays registered but is
|
||||||
not prebuilt (its `LazyPage::in_book()` is false).
|
not prebuilt (its `LazyPage::in_book()` is false).
|
||||||
- Plugin pages are appended by `PluginPages::initialize` (`plugin/host/PluginPages.hpp`) with
|
- Plugin pages are appended by `PluginPages::initialize` (`plugin/host/PluginPages.hpp`) with
|
||||||
namespaced ids (`plugin.<plugin_key>.<name>`) that cannot collide with `TAB_ID_*`.
|
namespaced ids (`plugin.<plugin_key>.<name>`) that cannot collide with `TAB_ID_*`. Each is a
|
||||||
|
`LazyPage<PluginPage>` with order −1, destroyed when its capability goes away.
|
||||||
|
→ [Deferred construction](#deferred-construction-lazy-lazypage-stagedbuild-idlescheduler)
|
||||||
|
|
||||||
### Preset tabs
|
### Preset tabs
|
||||||
|
|
||||||
@@ -529,6 +531,32 @@ the main frame does nothing to a panel after creating it.
|
|||||||
m_idle.add(m_diff_dialog);
|
m_idle.add(m_diff_dialog);
|
||||||
```
|
```
|
||||||
Cite: `IdleScheduler::tick`, `docs/HLSD/deferred-page-construction.md`.
|
Cite: `IdleScheduler::tick`, `docs/HLSD/deferred-page-construction.md`.
|
||||||
|
- **Rule:** A lazy page that can be destroyed while the main frame lives takes a negative order and
|
||||||
|
stays out of `m_lazy_pages`.
|
||||||
|
**Why:** `m_lazy_pages` and `PrebuildQueue` hold raw `LazyBase*` and nothing removes one
|
||||||
|
(`PrebuildQueue` has only `add` and `clear`). The queue calls `pending()` on every task each slice,
|
||||||
|
and `prebuild_pages_when_idle` reads every entry of `m_lazy_pages`, so a page destroyed while still
|
||||||
|
listed can be read after it is freed. A page only taken out of the book is fine: it stays registered
|
||||||
|
and its `pending()` is false (`MainFrame::show_device`).
|
||||||
|
```cpp
|
||||||
|
// Right (PluginPages::create_page): order -1, and no m_lazy_pages.push_back
|
||||||
|
auto* page = new GUI::LazyPage<PluginPage>(m_parent, name, -1, [capability](wxWindow* parent) {
|
||||||
|
return new PluginPage(parent, capability);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
Cite: `PluginPages::create_page`, `PluginPages::remove_page`.
|
||||||
|
- **Rule:** Remove several lazy pages from a book left to right.
|
||||||
|
**Why:** removing the selected page selects and shows the page before it
|
||||||
|
(`references/controls-dataview.md` §Book controls), and showing an unbuilt `LazyPage` while the frame
|
||||||
|
is shown builds it. In any other order the page before the selected one can be one removed next,
|
||||||
|
built only to be destroyed; left to right it is one that stays (unless the selected page is the
|
||||||
|
book's first).
|
||||||
|
```cpp
|
||||||
|
// Right (PluginPages::shutdown): m_order is the tabs' left-to-right order
|
||||||
|
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||||
|
remove_page(id);
|
||||||
|
```
|
||||||
|
Cite: `PluginPages::shutdown`, `PluginPages::relayout`, `PluginPages::on_plugin_deregister`.
|
||||||
|
|
||||||
## Plater and Sidebar
|
## Plater and Sidebar
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ to write a custom control on the wx side and how to author an Orca widget on the
|
|||||||
`StaticBox`/`StateHandler` foundation. Read it before writing or reviewing any `wxEVT_PAINT`
|
`StaticBox`/`StateHandler` foundation. Read it before writing or reviewing any `wxEVT_PAINT`
|
||||||
handler, `render`/`doRender` method, `messureSize`, or a new class under `src/slic3r/GUI/Widgets/`.
|
handler, `render`/`doRender` method, `messureSize`, or a new class under `src/slic3r/GUI/Widgets/`.
|
||||||
|
|
||||||
wx cites are relative to the pinned wx 3.3.2 tree (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every
|
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every
|
||||||
wx assert below is compiled out and `wxCHECK*` returns silently, so paint misuse shows up only as
|
wx assert below is compiled out and `wxCHECK*` returns silently, so paint misuse shows up only as
|
||||||
wrong, missing or stale pixels, never as an assert dialog.
|
wrong, missing or stale pixels, never as an assert dialog.
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ Contents: [Rules](#rules) · [The wx build Orca uses](#the-wx-build-orca-uses)
|
|||||||
`references/colours-dark-mode.md`.
|
`references/colours-dark-mode.md`.
|
||||||
- The checked-out source is the tree that every wx citation in this skill refers to:
|
- The checked-out source is the tree that every wx citation in this skill refers to:
|
||||||
`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets` on macOS and
|
`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets` on macOS and
|
||||||
`deps/build/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux. Find it with
|
`deps/<tree>/dep_wxWidgets-prefix/src/dep_wxWidgets` on Linux and Windows (`deps/build` for a
|
||||||
`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`. On macOS its
|
release build; `build_win.bat` names the others). Locate it with the bash or PowerShell lookup in
|
||||||
|
`SKILL.md` §Ground truth. On macOS its
|
||||||
`src/osx/cocoa/colour.mm` already has the patch applied.
|
`src/osx/cocoa/colour.mm` already has the patch applied.
|
||||||
- **Flatpak builds wx separately.** `deps/CMakeLists.txt` leaves `dep_wxWidgets` out of the deps
|
- **Flatpak builds wx separately.** `deps/CMakeLists.txt` leaves `dep_wxWidgets` out of the deps
|
||||||
target when `FLATPAK` is set. Instead, `scripts/flatpak/com.orcaslicer.OrcaSlicer.yml` has its own
|
target when `FLATPAK` is set. Instead, `scripts/flatpak/com.orcaslicer.OrcaSlicer.yml` has its own
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ How wx 3.3.2 popups and menus behave on each port, and the Orca wrappers built o
|
|||||||
`append_menu_item`, `Plater::PopupMenu` and the macOS menubar versus `BBLTopbar`. Read it before you add
|
`append_menu_item`, `Plater::PopupMenu` and the macOS menubar versus `BBLTopbar`. Read it before you add
|
||||||
or change anything that opens over other UI and must close by itself, or any context menu or menubar item.
|
or change anything that opens over other UI and must close by itself, or any context menu or menubar item.
|
||||||
|
|
||||||
wx cites are relative to the wx tree root (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
wx cites are relative to the wx tree root (located as in `SKILL.md` §Ground truth).
|
||||||
**[source]** marks behaviour derived from the implementation that the wx docs do not state or contradict.
|
**[source]** marks behaviour derived from the implementation that the wx docs do not state or contradict.
|
||||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0`, so every "asserts" below means "fails silently in Orca".
|
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0`, so every "asserts" below means "fails silently in Orca".
|
||||||
"GTK" means wxGTK3 (X11 and Wayland), the default Linux build; GTK2 is only an opt-out (`-DDEP_WX_GTK3=OFF`).
|
"GTK" means wxGTK3 (X11 and Wayland), the default Linux build; GTK2 is only an opt-out (`-DDEP_WX_GTK3=OFF`).
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ yields and nested event loops, progress dialogs, startup, shutdown and exception
|
|||||||
screen, and the access rules for `wxGetApp()` and `app_config`. Read it whenever code runs off the
|
screen, and the access rules for `wxGetApp()` and `app_config`. Read it whenever code runs off the
|
||||||
main thread, defers work, starts a timer, yields, shows progress, or runs during startup or shutdown.
|
main thread, defers work, starts a timer, yields, shows progress, or runs during startup or shutdown.
|
||||||
|
|
||||||
wx cites are relative to the pinned wx 3.3.2 tree (`deps/build/<arch>/dep_wxWidgets-prefix/src/dep_wxWidgets`).
|
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth).
|
||||||
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`, so `wxASSERT`
|
Orca builds wx with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`, so `wxASSERT`
|
||||||
is compiled out and `wxCHECK*` returns silently: a timer started off the main thread never fires on macOS,
|
is compiled out and `wxCHECK*` returns silently: a timer started off the main thread never fires on macOS,
|
||||||
`Exit()` on a loop that is not the active one is ignored, `Start(0)` on macOS fails — all without
|
`Exit()` on a loop that is not the active one is ignored, `Start(0)` on macOS fails — all without
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ port; what Orca's `DPIDialog`/`DPIFrame` add; the Orca dialog recipe; and the `M
|
|||||||
Read it before writing or reviewing any dialog, frame, close handler, `Destroy()`/`delete`, or code
|
Read it before writing or reviewing any dialog, frame, close handler, `Destroy()`/`delete`, or code
|
||||||
that keeps a pointer to a window across an event, a `CallAfter` or a modal loop.
|
that keeps a pointer to a window across an event, a `CallAfter` or a modal loop.
|
||||||
|
|
||||||
wx cites are relative to the pinned wx 3.3.2 tree (`find deps -maxdepth 5 -type d -path
|
wx cites are relative to the pinned wx 3.3.2 tree (located as in `SKILL.md` §Ground truth). wx is
|
||||||
'*dep_wxWidgets-prefix/src/dep_wxWidgets'`). wx is built with `wxBUILD_DEBUG_LEVEL=0` and
|
built with `wxBUILD_DEBUG_LEVEL=0` and `libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert
|
||||||
`libslic3r_gui` with `wxDEBUG_LEVEL=0`: every wx assert quoted below is compiled out, so misuse
|
quoted below is compiled out, so misuse fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
|
||||||
fails silently (dropped call, stuck loop, freed memory), never with an assert dialog. "GTK" below
|
|
||||||
means wxGTK as Orca builds it on Linux: GTK3 by default (X11 or Wayland); GTK2 is only an opt-out.
|
means wxGTK as Orca builds it on Linux: GTK3 by default (X11 or Wayland); GTK2 is only an opt-out.
|
||||||
|
|
||||||
Contents: [Rules](#rules) · [1 Creating and parenting](#1-creating-and-parenting-windows) ·
|
Contents: [Rules](#rules) · [1 Creating and parenting](#1-creating-and-parenting-windows) ·
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Contents: [Rules](#rules) · [1 Reading the change logs](#1-reading-the-change-l
|
|||||||
[8 Migration done in Orca](#8-migration-already-done-in-orca)
|
[8 Migration done in Orca](#8-migration-already-done-in-orca)
|
||||||
|
|
||||||
All `docs/`, `interface/`, `include/`, `src/`, `build/` cites are relative to the pinned wx tree
|
All `docs/`, `interface/`, `include/`, `src/`, `build/` cites are relative to the pinned wx tree
|
||||||
(`find deps -maxdepth 5 -type d -path '*dep_wxWidgets-prefix/src/dep_wxWidgets'`), except paths
|
(located as in `SKILL.md` §Ground truth), except paths
|
||||||
explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca file + symbol cites.
|
explicitly called Orca's (`deps/…`, Orca's `src/CMakeLists.txt`) and bare Orca file + symbol cites.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|||||||
@@ -62,6 +62,23 @@ Clipper2 behaves differently by default, the wrapper adjusts it.
|
|||||||
clip do not leave slivers.
|
clip do not leave slivers.
|
||||||
- Open polylines are clipped with the non-zero rule and keep their direction.
|
- Open polylines are clipped with the non-zero rule and keep their direction.
|
||||||
|
|
||||||
|
### Tiled booleans
|
||||||
|
|
||||||
|
The sweep slows down with the number of edges crossing a scan line, so a layer
|
||||||
|
cut into thousands of pieces makes every whole-layer boolean expensive.
|
||||||
|
`diff_ex_by_piece()` and `intersection_ex_by_piece()` take a subject of
|
||||||
|
non-overlapping `ExPolygons`, group them into tiles with
|
||||||
|
`ClipperUtils::tile_expolygons()`, and run each tile in parallel against only
|
||||||
|
the clip polygons near it, cut to the tile's box. Below 128 pieces there is a
|
||||||
|
single tile, and they are the plain `diff_ex()` / `intersection_ex()`.
|
||||||
|
|
||||||
|
The result covers the same area as the plain call. Without the safety offset
|
||||||
|
the rings are the same. With it, each tile unites only the clip polygons near
|
||||||
|
it, so a clip edge that the whole-layer union splits where it crosses a distant
|
||||||
|
clip polygon stays whole, and a crossing with the subject can round 1 unit
|
||||||
|
differently. The tiles' results are concatenated in tile order, so the order of
|
||||||
|
the output `ExPolygons` differs from the plain call.
|
||||||
|
|
||||||
### Offsets
|
### Offsets
|
||||||
|
|
||||||
- Before offsetting, input vertices closer than
|
- Before offsetting, input vertices closer than
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
@@ -800,6 +802,72 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
|
|||||||
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
|
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
|
||||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||||
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||||
|
|
||||||
|
namespace ClipperUtils {
|
||||||
|
std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
|
||||||
|
{
|
||||||
|
BoundingBox extent;
|
||||||
|
std::vector<BoundingBox> bboxes;
|
||||||
|
bboxes.reserve(expolygons.size());
|
||||||
|
for (const ExPolygon &expoly : expolygons) {
|
||||||
|
bboxes.emplace_back(get_extents(expoly));
|
||||||
|
extent.merge(bboxes.back());
|
||||||
|
}
|
||||||
|
if (! extent.defined)
|
||||||
|
return {};
|
||||||
|
const int tiles = std::clamp(int(std::sqrt(double(expolygons.size()) / double(std::max<size_t>(per_tile, 1)))), 1, 32);
|
||||||
|
const Point size = extent.size();
|
||||||
|
const coord_t tile_w = std::max<coord_t>(1, size.x() / tiles + 1), tile_h = std::max<coord_t>(1, size.y() / tiles + 1);
|
||||||
|
std::vector<ExPolygonsTile> out(size_t(tiles * tiles));
|
||||||
|
for (size_t i = 0; i < expolygons.size(); ++ i) {
|
||||||
|
const Point c = bboxes[i].center();
|
||||||
|
ExPolygonsTile &tile = out[size_t(std::clamp(int((c.y() - extent.min.y()) / tile_h), 0, tiles - 1) * tiles +
|
||||||
|
std::clamp(int((c.x() - extent.min.x()) / tile_w), 0, tiles - 1))];
|
||||||
|
tile.members.emplace_back(i);
|
||||||
|
tile.bbox.merge(bboxes[i]);
|
||||||
|
}
|
||||||
|
out.erase(std::remove_if(out.begin(), out.end(), [](const ExPolygonsTile &tile) { return tile.members.empty(); }), out.end());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slic3r::ExPolygons clipper_ex_by_piece(ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||||
|
{
|
||||||
|
// A few dozen subject ExPolygons to a tile, each tile one Clipper call with the clip cut to the tile's box.
|
||||||
|
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(subject, 32);
|
||||||
|
// One tile is the plain call: cutting the clip would only cost time.
|
||||||
|
if (tiles.size() <= 1)
|
||||||
|
return _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset);
|
||||||
|
std::vector<BoundingBox> clip_bboxes;
|
||||||
|
clip_bboxes.reserve(clip.size());
|
||||||
|
for (const Polygon &polygon : clip)
|
||||||
|
clip_bboxes.emplace_back(get_extents(polygon));
|
||||||
|
|
||||||
|
std::vector<Slic3r::ExPolygons> out_tiles(tiles.size());
|
||||||
|
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
|
||||||
|
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
|
||||||
|
Slic3r::ExPolygons local_subject;
|
||||||
|
local_subject.reserve(tile.members.size());
|
||||||
|
for (size_t i : tile.members)
|
||||||
|
local_subject.emplace_back(subject[i]);
|
||||||
|
// Grown so that the cut edges of the clip stay clear of the subject, also after the safety offset.
|
||||||
|
const BoundingBox bbox = tile.bbox.inflated(SCALED_EPSILON);
|
||||||
|
Polygons local_clip;
|
||||||
|
for (size_t i = 0; i < clip.size(); ++i)
|
||||||
|
if (clip_bboxes[i].overlap(bbox))
|
||||||
|
if (Polygon clipped = ClipperUtils::clip_clipper_polygon_with_subject_bbox(clip[i], bbox); ! clipped.empty())
|
||||||
|
local_clip.emplace_back(std::move(clipped));
|
||||||
|
out_tiles[tile_idx] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset);
|
||||||
|
});
|
||||||
|
Slic3r::ExPolygons out;
|
||||||
|
for (Slic3r::ExPolygons &out_tile : out_tiles)
|
||||||
|
append(out, std::move(out_tile));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||||
|
{ return clipper_ex_by_piece(ctDifference, subject, clip, do_safety_offset); }
|
||||||
|
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||||
|
{ return clipper_ex_by_piece(ctIntersection, subject, clip, do_safety_offset); }
|
||||||
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
||||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type)
|
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type)
|
||||||
{ return _clipper_ex(ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
|
{ return _clipper_ex(ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Polyline.hpp"
|
#include "Polyline.hpp"
|
||||||
#include "Line.hpp"
|
#include "Line.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "Surface.hpp"
|
#include "Surface.hpp"
|
||||||
@@ -337,6 +338,15 @@ namespace ClipperUtils {
|
|||||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||||
|
|
||||||
|
// Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run Clipper on a
|
||||||
|
// layer of many pieces tile by tile. Returns the non-empty tiles, each with the indices of its ExPolygons and their box.
|
||||||
|
struct ExPolygonsTile
|
||||||
|
{
|
||||||
|
BoundingBox bbox;
|
||||||
|
std::vector<size_t> members;
|
||||||
|
};
|
||||||
|
[[nodiscard]] std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// offset Polygons
|
// offset Polygons
|
||||||
@@ -532,6 +542,11 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
|
|||||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
|
// diff_ex() / intersection_ex() of the subject split into tiles, each against only the part of the clip near it, the tiles in
|
||||||
|
// parallel. The same area as the operation on the whole subject when its ExPolygons do not overlap, and much faster for a
|
||||||
|
// subject of thousands of pieces spread over a layer: Clipper slows down with the number of edges crossing a scan line.
|
||||||
|
Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
|
Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
||||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||||
|
|||||||
+31
-12
@@ -22,6 +22,8 @@
|
|||||||
#include "../PrintConfig.hpp"
|
#include "../PrintConfig.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
#include "AABBTreeLines.hpp"
|
#include "AABBTreeLines.hpp"
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
@@ -652,24 +654,28 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
|||||||
if (!line_based_pattern) {
|
if (!line_based_pattern) {
|
||||||
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing);
|
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing);
|
||||||
|
|
||||||
for (const ExPolygon &expolygon : fill.expolygons) {
|
// Each expolygon is split on its own, so they run in parallel and are collected in their original order.
|
||||||
|
std::vector<std::pair<ExPolygons, ExPolygons>> split_parts(fill.expolygons.size()); // normal, narrow
|
||||||
|
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t idx) {
|
||||||
|
const ExPolygon &expolygon = fill.expolygons[idx];
|
||||||
Polygons filled_area = to_polygons(expolygon);
|
Polygons filled_area = to_polygons(expolygon);
|
||||||
|
|
||||||
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon.
|
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon.
|
||||||
Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
|
Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
|
||||||
|
|
||||||
if (inner_area.empty()) {
|
if (inner_area.empty()) {
|
||||||
narrow_infill.emplace_back(expolygon);
|
split_parts[idx].second.emplace_back(expolygon);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons inner_ex = union_ex(inner_area);
|
ExPolygons inner_ex = union_ex(inner_area);
|
||||||
ExPolygons expolys{expolygon};
|
ExPolygons expolys{expolygon};
|
||||||
ExPolygons narrow_ex = diff_ex(expolys, inner_ex);
|
split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area
|
||||||
ExPolygons normal_ex = intersection_ex(expolys, inner_ex);
|
split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area
|
||||||
|
});
|
||||||
append(normal_infill, normal_ex); // normal infill area
|
for (auto &[normal_ex, narrow_ex] : split_parts) {
|
||||||
append(narrow_infill, narrow_ex); // narrow infill area
|
append(normal_infill, std::move(normal_ex));
|
||||||
|
append(narrow_infill, std::move(narrow_ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -691,7 +697,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
|||||||
}
|
}
|
||||||
const double aligning_angle = -base_angle + PI;
|
const double aligning_angle = -base_angle + PI;
|
||||||
|
|
||||||
for (const ExPolygon &expolygon : fill.expolygons) {
|
// Each expolygon is reconstructed on its own, so they run in parallel and are collected in their original order.
|
||||||
|
std::vector<Polygons> split_reconstructed(fill.expolygons.size());
|
||||||
|
tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t expolygon_idx) {
|
||||||
|
const ExPolygon &expolygon = fill.expolygons[expolygon_idx];
|
||||||
Polygons filled_area = to_polygons(expolygon);
|
Polygons filled_area = to_polygons(expolygon);
|
||||||
polygons_rotate(filled_area, aligning_angle);
|
polygons_rotate(filled_area, aligning_angle);
|
||||||
BoundingBox bb = get_extents(filled_area);
|
BoundingBox bb = get_extents(filled_area);
|
||||||
@@ -822,8 +831,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
polygons_append(normal_fill_areas, reconstructed_area);
|
split_reconstructed[expolygon_idx] = std::move(reconstructed_area);
|
||||||
}
|
});
|
||||||
|
for (Polygons &reconstructed_area : split_reconstructed)
|
||||||
|
polygons_append(normal_fill_areas, std::move(reconstructed_area));
|
||||||
|
|
||||||
polygons_rotate(normal_fill_areas, -aligning_angle);
|
polygons_rotate(normal_fill_areas, -aligning_angle);
|
||||||
|
|
||||||
@@ -1431,7 +1442,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||||||
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
|
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
|
||||||
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
|
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
|
||||||
|
|
||||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
// Only the part of the layer-wide no-overlap area under this expolygon matters, so clip it to the
|
||||||
|
// expolygon's box first (padded past the safety offset, which grows the clip side). The result is
|
||||||
|
// identical; the cost is not: a layer split into many small fills, e.g. by colour painting,
|
||||||
|
// otherwise intersects every one of them with the whole layer.
|
||||||
|
BoundingBox no_overlap_bbox = get_extents(expoly);
|
||||||
|
no_overlap_bbox.offset(SCALED_EPSILON);
|
||||||
|
f->no_overlap_expolygons = intersection_ex(
|
||||||
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(surface_fill.no_overlap_expolygons, no_overlap_bbox),
|
||||||
|
ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||||
if (params.symmetric_infill_y_axis) {
|
if (params.symmetric_infill_y_axis) {
|
||||||
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
|
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
|
||||||
expoly.symmetric_y(params.symmetric_y_axis);
|
expoly.symmetric_y(params.symmetric_y_axis);
|
||||||
|
|||||||
@@ -139,15 +139,87 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
|||||||
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For many islands, the same scan with the edges binned in a uniform grid over their boxes, so each edge is only tested against the edges sharing a
|
||||||
|
// cell with it - two edges whose boxes overlap always do. It returns the same crossing as the all-pairs scan
|
||||||
|
// (smallest i, then smallest j), so the result is unchanged. The all-pairs scan is quadratic in the edge count and
|
||||||
|
// runs again after every reversal; rebuilding the grid costs more than it saves below the threshold.
|
||||||
|
constexpr size_t grid_min_size = 500;
|
||||||
|
BoundingBox extent;
|
||||||
|
for (size_t idx : path)
|
||||||
|
extent.merge(centers[idx]);
|
||||||
|
const int grid_n = std::clamp(int(std::sqrt(double(pn))), 1, 256);
|
||||||
|
const coord_t cell_w = std::max<coord_t>(1, (extent.max.x() - extent.min.x()) / grid_n + 1);
|
||||||
|
const coord_t cell_h = std::max<coord_t>(1, (extent.max.y() - extent.min.y()) / grid_n + 1);
|
||||||
|
const auto for_cells = [&](const Point& a, const Point& b, auto&& fn) {
|
||||||
|
const int x0 = int((std::min(a.x(), b.x()) - extent.min.x()) / cell_w), x1 = int((std::max(a.x(), b.x()) - extent.min.x()) / cell_w);
|
||||||
|
const int y0 = int((std::min(a.y(), b.y()) - extent.min.y()) / cell_h), y1 = int((std::max(a.y(), b.y()) - extent.min.y()) / cell_h);
|
||||||
|
for (int y = y0; y <= y1; ++y)
|
||||||
|
for (int x = x0; x <= x1; ++x)
|
||||||
|
fn(y * grid_n + x);
|
||||||
|
};
|
||||||
|
std::vector<std::vector<size_t>> edge_cells(size_t(grid_n) * grid_n);
|
||||||
|
|
||||||
|
auto find_crossing_grid = [&]() -> std::pair<size_t, size_t> {
|
||||||
|
for (std::vector<size_t>& cell : edge_cells)
|
||||||
|
cell.clear();
|
||||||
|
for (size_t j = 0; j < n_edges; ++j)
|
||||||
|
for_cells(centers[path[j]], centers[path[(j + 1) % pn]], [&](int cell) { edge_cells[cell].emplace_back(j); });
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n_edges; ++i) {
|
||||||
|
const Point& ai = centers[path[i]];
|
||||||
|
const Point& bi = centers[path[(i + 1) % pn]];
|
||||||
|
|
||||||
|
size_t first_j = std::numeric_limits<size_t>::max();
|
||||||
|
for_cells(ai, bi, [&](int cell) {
|
||||||
|
for (size_t j : edge_cells[cell]) {
|
||||||
|
if (j < i + 2 || j >= first_j) continue;
|
||||||
|
// 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))
|
||||||
|
first_j = j;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (first_j != std::numeric_limits<size_t>::max())
|
||||||
|
return {i, first_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.
|
// Process crossings one at a time: find first, reverse it, restart scan.
|
||||||
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
|
||||||
int max_iters = static_cast<int>(pn * pn);
|
const int max_iters = static_cast<int>(pn * pn);
|
||||||
bool improved = false;
|
bool improved = false;
|
||||||
while (max_iters-- > 0) {
|
// Reversing between two segments that only touch or overlap along a line need not remove the intersection, so on
|
||||||
auto [ci, cj] = find_crossing();
|
// islands laid out on a regular grid (a tiled texture, an array of parts) the loop can cycle through the same
|
||||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
// orderings until the pn * pn cap. Once an ordering repeats the rest of the loop is periodic, so only the steps
|
||||||
improved = true;
|
// to the ordering the capped loop would have stopped on are taken.
|
||||||
|
std::unordered_map<uint64_t, int> seen_paths; // path hash -> reversals done when it was reached
|
||||||
|
const auto path_hash = [&path]() {
|
||||||
|
uint64_t h = 1469598103934665603ull; // FNV-1a
|
||||||
|
for (size_t idx : path)
|
||||||
|
h = (h ^ uint64_t(idx)) * 1099511628211ull;
|
||||||
|
return h;
|
||||||
|
};
|
||||||
|
const auto reverse_first_crossing = [&]() {
|
||||||
|
auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing();
|
||||||
|
if (ci == std::numeric_limits<size_t>::max())
|
||||||
|
return false;
|
||||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
seen_paths.emplace(path_hash(), 0);
|
||||||
|
for (int iter = 1; iter <= max_iters && reverse_first_crossing(); ++iter) {
|
||||||
|
improved = true;
|
||||||
|
if (auto [it, inserted] = seen_paths.emplace(path_hash(), iter); !inserted) {
|
||||||
|
for (int steps = (max_iters - iter) % (iter - it->second); steps > 0; --steps)
|
||||||
|
reverse_first_crossing();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return improved;
|
return improved;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -1201,21 +1202,21 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|
|||||||
const size_t layer_idx, const float max_distance,
|
const size_t layer_idx, const float max_distance,
|
||||||
const SeamPlacerImpl::SeamComparator &comparator) const {
|
const SeamPlacerImpl::SeamComparator &comparator) const {
|
||||||
using namespace SeamPlacerImpl;
|
using namespace SeamPlacerImpl;
|
||||||
std::vector<size_t> nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position,
|
// Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within
|
||||||
max_distance);
|
// the radius, so they are looked at as the search finds them rather than collected into a vector first.
|
||||||
|
constexpr size_t none = std::numeric_limits<size_t>::max();
|
||||||
if (nearby_points_indices.empty()) {
|
size_t best_nearby_point_index = none;
|
||||||
return {};
|
size_t nearest_point_index = none;
|
||||||
|
visit_nearby_points(*layers[layer_idx].points_tree, projected_position, max_distance,
|
||||||
|
[&layers, &comparator, &projected_position, layer_idx, &best_nearby_point_index, &nearest_point_index]
|
||||||
|
(size_t nearby_point_index) {
|
||||||
|
if (best_nearby_point_index == none) {
|
||||||
|
// The first point found starts both, as the first of the collected ones did.
|
||||||
|
best_nearby_point_index = nearest_point_index = nearby_point_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t best_nearby_point_index = nearby_points_indices[0];
|
|
||||||
size_t nearest_point_index = nearby_points_indices[0];
|
|
||||||
|
|
||||||
// Now find best nearby point, nearest point, and corresponding indices
|
|
||||||
for (const size_t &nearby_point_index : nearby_points_indices) {
|
|
||||||
const SeamCandidate &point = layers[layer_idx].points[nearby_point_index];
|
const SeamCandidate &point = layers[layer_idx].points[nearby_point_index];
|
||||||
if (point.perimeter.finalized) {
|
if (point.perimeter.finalized) {
|
||||||
continue; // skip over finalized perimeters, try to find some that is not finalized
|
return; // skip over finalized perimeters, try to find some that is not finalized
|
||||||
}
|
}
|
||||||
if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index],
|
if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index],
|
||||||
projected_position.head<2>())
|
projected_position.head<2>())
|
||||||
@@ -1227,6 +1228,10 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|
|||||||
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
|
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
|
||||||
nearest_point_index = nearby_point_index;
|
nearest_point_index = nearby_point_index;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (best_nearby_point_index == none) {
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
|
const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
|
||||||
|
|||||||
@@ -318,6 +318,36 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
|
|||||||
return visitor.result;
|
return visitor.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visits the points within max_distance of center, in the order find_nearby_points() would collect them, and hands
|
||||||
|
// each of them to `visitor_fn` instead of returning them all: a search over a dense set spends more on collecting the
|
||||||
|
// points into a vector than on the search itself, and its caller usually keeps only a few of them.
|
||||||
|
template<typename KDTreeIndirectType, typename PointType, typename VisitorFn>
|
||||||
|
void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||||
|
const typename KDTreeIndirectType::CoordType &max_distance, VisitorFn visitor_fn)
|
||||||
|
{
|
||||||
|
using CoordType = typename KDTreeIndirectType::CoordType;
|
||||||
|
|
||||||
|
struct Visitor {
|
||||||
|
const KDTreeIndirectType &kdtree;
|
||||||
|
const PointType center;
|
||||||
|
const CoordType max_distance_squared;
|
||||||
|
VisitorFn visitor_fn;
|
||||||
|
|
||||||
|
unsigned int operator()(size_t idx, size_t dimension) {
|
||||||
|
auto dist = CoordType(0);
|
||||||
|
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
|
||||||
|
CoordType d = center[i] - kdtree.coordinate(idx, i);
|
||||||
|
dist += d * d;
|
||||||
|
}
|
||||||
|
if (dist < max_distance_squared)
|
||||||
|
visitor_fn(idx);
|
||||||
|
return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
|
||||||
|
}
|
||||||
|
} visitor { kdtree, center, max_distance * max_distance, visitor_fn };
|
||||||
|
|
||||||
|
kdtree.visit(visitor);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename KDTreeIndirectType, typename PointType>
|
template<typename KDTreeIndirectType, typename PointType>
|
||||||
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
|
||||||
const typename KDTreeIndirectType::CoordType& max_distance)
|
const typename KDTreeIndirectType::CoordType& max_distance)
|
||||||
|
|||||||
@@ -96,10 +96,11 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||||||
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
|
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
|
||||||
// Trim surfaces by the fill_boundaries.
|
// Trim surfaces by the fill_boundaries.
|
||||||
this->fill_surfaces.surfaces.clear();
|
this->fill_surfaces.surfaces.clear();
|
||||||
|
const Polygons fill_boundaries = to_polygons(this->fill_expolygons);
|
||||||
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
|
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
|
||||||
const SurfacesPtr &this_surfaces = by_surface[surface_type];
|
const SurfacesPtr &this_surfaces = by_surface[surface_type];
|
||||||
if (! this_surfaces.empty())
|
if (! this_surfaces.empty())
|
||||||
this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type));
|
this->fill_surfaces.append(intersection_ex_by_piece(to_expolygons(this_surfaces), fill_boundaries), SurfaceType(surface_type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <list>
|
#include <list>
|
||||||
@@ -1353,10 +1354,15 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
}
|
}
|
||||||
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM
|
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||||
|
|
||||||
// When the upper surface of an object is occluded, it should no longer be considered the upper surface
|
// When the upper surface of an object is occluded, it should no longer be considered the upper surface.
|
||||||
|
// Every (colour, layer) pair is trimmed on its own, so they all run at once: the painted faces of a finely
|
||||||
|
// textured part project hundreds of thousands of triangles onto one layer, which used to be trimmed serially.
|
||||||
{
|
{
|
||||||
for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
|
const size_t occluded_pairs = num_facets_states * layers.size();
|
||||||
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, occluded_pairs), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t pair_idx = range.begin(); pair_idx < range.end(); ++pair_idx) {
|
||||||
|
const size_t extruder_idx = pair_idx / layers.size();
|
||||||
|
const size_t layer_idx = pair_idx % layers.size();
|
||||||
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
|
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
|
||||||
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
|
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
|
||||||
}
|
}
|
||||||
@@ -1364,7 +1370,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
|
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
|
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
|
||||||
@@ -1422,11 +1428,58 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in
|
||||||
|
// `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left.
|
||||||
|
// Only the slices within the deepest offset of `ex` (three times that with the miter joins) decide the result, so the
|
||||||
|
// work is done per tile of `ex`'s ExPolygons on the slices cut to the tile's box grown by that much: the same result, but
|
||||||
|
// each Clipper call stays the size of a tile rather than of a layer cut through a fine relief, and the tiles run in
|
||||||
|
// parallel.
|
||||||
|
const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector<size_t> &shell_layers,
|
||||||
|
const LayerColorStat &stat, ShellProjections &dst) {
|
||||||
|
std::vector<float> offsets(shell_layers.size());
|
||||||
|
float offset = 0.f;
|
||||||
|
for (size_t i = 0; i < shell_layers.size(); ++i) {
|
||||||
|
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
||||||
|
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
||||||
|
offsets[i] = offset;
|
||||||
|
}
|
||||||
|
if (offsets.empty())
|
||||||
|
return;
|
||||||
|
const coord_t reach = coord_t(std::ceil(DefaultMiterLimit * std::abs(offsets.back()))) + 10 * SCALED_EPSILON;
|
||||||
|
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(ex, 16);
|
||||||
|
// [shell layer][tile]
|
||||||
|
std::vector<std::vector<ExPolygons>> shells(shell_layers.size(), std::vector<ExPolygons>(tiles.size()));
|
||||||
|
tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
|
||||||
|
const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
|
||||||
|
const BoundingBox bbox = tile.bbox.inflated(reach);
|
||||||
|
ExPolygons tile_ex;
|
||||||
|
tile_ex.reserve(tile.members.size());
|
||||||
|
for (size_t i : tile.members)
|
||||||
|
tile_ex.emplace_back(ex[i]);
|
||||||
|
Polygons layer_slices_trimmed = ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[layer_idx], bbox);
|
||||||
|
for (size_t i = 0; i < shell_layers.size() && ! layer_slices_trimmed.empty(); ++i) {
|
||||||
|
const ExPolygons trimmed = intersection_ex(layer_slices_trimmed, ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[shell_layers[i]], bbox));
|
||||||
|
shells[i][tile_idx] = opening_ex(intersection_ex(tile_ex, offset_ex(trimmed, offsets[i])), stat.small_region_threshold);
|
||||||
|
layer_slices_trimmed = to_polygons(trimmed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < shell_layers.size(); ++i) {
|
||||||
|
ExPolygons shell;
|
||||||
|
for (ExPolygons &tile_shell : shells[i])
|
||||||
|
append(shell, std::move(tile_shell));
|
||||||
|
if (shell.empty())
|
||||||
|
break;
|
||||||
|
dst.emplace_back(shell_layers[i], std::move(shell));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
||||||
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
&throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells,
|
||||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
|
// Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face
|
||||||
|
// projects onto a single layer, which otherwise did all of its colours on one thread.
|
||||||
|
tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) {
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
||||||
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
||||||
@@ -1435,18 +1488,10 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
||||||
if (! top_ex.empty()) {
|
if (! top_ex.empty()) {
|
||||||
append(triangles_by_color_top[color_idx][layer_idx], top_ex);
|
append(triangles_by_color_top[color_idx][layer_idx], top_ex);
|
||||||
float offset = 0.f;
|
std::vector<size_t> shell_layers;
|
||||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx)
|
||||||
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
|
shell_layers.emplace_back(size_t(last_idx));
|
||||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx][layer_idx]);
|
||||||
//offset -= stat.extrusion_width ;
|
|
||||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
|
||||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
|
||||||
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
|
||||||
if (last.empty())
|
|
||||||
break;
|
|
||||||
shell_triangles_by_color_top[color_idx][layer_idx].emplace_back(size_t(last_idx), std::move(last));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
||||||
@@ -1455,21 +1500,13 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
||||||
if (! bottom_ex.empty()) {
|
if (! bottom_ex.empty()) {
|
||||||
append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex);
|
append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex);
|
||||||
float offset = 0.f;
|
std::vector<size_t> shell_layers;
|
||||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx)
|
||||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
|
shell_layers.emplace_back(last_idx);
|
||||||
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx][layer_idx]);
|
||||||
//offset -= stat.extrusion_width;
|
|
||||||
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
|
||||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
|
||||||
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
|
||||||
if (last.empty())
|
|
||||||
break;
|
|
||||||
shell_triangles_by_color_bottom[color_idx][layer_idx].emplace_back(last_idx, std::move(last));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1490,20 +1527,23 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
&shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range<size_t> &range) {
|
&shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
ExPolygons painted_exploys;
|
// The per-colour unions below are independent of each other, so they run in parallel (a painted top or
|
||||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
// bottom face puts all of its colours on one layer); whatever combines the colours stays in colour order.
|
||||||
|
const auto merge_colour_union = [&](size_t color_idx) {
|
||||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
||||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
||||||
self = union_ex(self);
|
self = union_ex(self);
|
||||||
|
};
|
||||||
|
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), merge_colour_union);
|
||||||
|
|
||||||
append(painted_exploys, self);
|
ExPolygons painted_exploys;
|
||||||
}
|
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx)
|
||||||
|
append(painted_exploys, triangles_by_color_merged[color_idx][layer_idx]);
|
||||||
painted_exploys = union_ex(painted_exploys);
|
painted_exploys = union_ex(painted_exploys);
|
||||||
|
|
||||||
//BBS: merge the top and bottom shell layers
|
//BBS: merge the top and bottom shell layers
|
||||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), [&](size_t color_idx) {
|
||||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||||
|
|
||||||
auto top_area = diff_ex(union_ex(shell_top_by_layer[color_idx][layer_idx]), painted_exploys);
|
auto top_area = diff_ex(union_ex(shell_top_by_layer[color_idx][layer_idx]), painted_exploys);
|
||||||
@@ -1512,7 +1552,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
append(self, top_area);
|
append(self, top_area);
|
||||||
append(self, bottom_area);
|
append(self, bottom_area);
|
||||||
self = union_ex(self);
|
self = union_ex(self);
|
||||||
}
|
});
|
||||||
// Trim one region by the other if some of the regions overlap.
|
// Trim one region by the other if some of the regions overlap.
|
||||||
ExPolygons painted_regions;
|
ExPolygons painted_regions;
|
||||||
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||||
@@ -1879,7 +1919,69 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
|
||||||
|
// Finds the islands (layer ExPolygons) a region piece overlaps. A top or bottom region is projected from the neighbouring
|
||||||
|
// layers and may reach past the island it belongs to, or over several islands.
|
||||||
|
class IslandLocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit IslandLocator(const ExPolygons &islands) : m_islands(islands)
|
||||||
|
{
|
||||||
|
m_bboxes.reserve(islands.size());
|
||||||
|
for (const ExPolygon &island : islands) {
|
||||||
|
m_bboxes.emplace_back(get_extents(island));
|
||||||
|
m_extent.merge(m_bboxes.back());
|
||||||
|
}
|
||||||
|
if (!m_extent.defined)
|
||||||
|
return;
|
||||||
|
const Point size = m_extent.size();
|
||||||
|
m_cell_w = std::max<coord_t>(1, size.x() / GRID + 1);
|
||||||
|
m_cell_h = std::max<coord_t>(1, size.y() / GRID + 1);
|
||||||
|
m_grid.assign(GRID * GRID, {});
|
||||||
|
for (size_t i = 0; i < m_bboxes.size(); ++i)
|
||||||
|
for_cells(m_bboxes[i], [&](int cell) { m_grid[cell].emplace_back(i); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void find(const ExPolygon &piece, std::vector<size_t> &out) const
|
||||||
|
{
|
||||||
|
out.clear();
|
||||||
|
const BoundingBox bbox = get_extents(piece);
|
||||||
|
if (!m_extent.defined || !m_extent.overlap(bbox))
|
||||||
|
return;
|
||||||
|
for_cells(bbox, [&](int cell) {
|
||||||
|
for (size_t i : m_grid[cell])
|
||||||
|
if (m_bboxes[i].overlap(bbox))
|
||||||
|
out.emplace_back(i);
|
||||||
|
});
|
||||||
|
sort_remove_duplicates(out);
|
||||||
|
if (out.size() > 1)
|
||||||
|
out.erase(std::remove_if(out.begin(), out.end(), [&](size_t i) {
|
||||||
|
const BoundingBox common(m_bboxes[i].min.cwiseMax(bbox.min), m_bboxes[i].max.cwiseMin(bbox.max));
|
||||||
|
return intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(piece, common.inflated(SCALED_EPSILON)),
|
||||||
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(m_islands[i], common.inflated(SCALED_EPSILON))).empty();
|
||||||
|
}), out.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int GRID = 64;
|
||||||
|
template<typename Fn> void for_cells(const BoundingBox &bb, Fn &&fn) const
|
||||||
|
{
|
||||||
|
const int x0 = std::clamp(int((bb.min.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1), x1 = std::clamp(int((bb.max.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1);
|
||||||
|
const int y0 = std::clamp(int((bb.min.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1), y1 = std::clamp(int((bb.max.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1);
|
||||||
|
for (int y = y0; y <= y1; ++y)
|
||||||
|
for (int x = x0; x <= x1; ++x)
|
||||||
|
fn(y * GRID + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExPolygons &m_islands;
|
||||||
|
std::vector<BoundingBox> m_bboxes;
|
||||||
|
BoundingBox m_extent;
|
||||||
|
coord_t m_cell_w = 1, m_cell_h = 1;
|
||||||
|
std::vector<std::vector<size_t>> m_grid;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
|
||||||
|
const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
||||||
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
||||||
const size_t num_facets_states,
|
const size_t num_facets_states,
|
||||||
const std::function<void()> &throw_on_cancel_callback)
|
const std::function<void()> &throw_on_cancel_callback)
|
||||||
@@ -1890,33 +1992,91 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
|||||||
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
// Every region of a layer is merged together with the regions of the islands it overlaps, and the islands are further
|
||||||
|
// apart than the dimple removal below reaches, so this gives the same result as merging the layer at once. On a layer
|
||||||
|
// cut through a fine relief every region shares thousands of hole contours with every other, and Clipper, splitting
|
||||||
|
// and re-linking one huge polygon over and over, took anything up to half an hour for a layer; per island each operation
|
||||||
|
// stays the size of the island, and the islands run in parallel.
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||||
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
||||||
// Zero is skipped because it is the default color of the volume
|
|
||||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
|
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
// Group the islands joined by a region overlapping several of them; the last group takes the regions lying
|
||||||
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
// outside every island.
|
||||||
if (!top_and_bottom_layers.empty()) {
|
const ExPolygons &islands = input_expolygons[layer_idx];
|
||||||
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) {
|
const IslandLocator locator(islands);
|
||||||
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
|
std::vector<size_t> parent(islands.size() + 1);
|
||||||
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
|
std::iota(parent.begin(), parent.end(), 0);
|
||||||
}
|
const auto root = [&parent](size_t i) {
|
||||||
}
|
while (parent[i] != i)
|
||||||
|
i = parent[i] = parent[parent[i]];
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
// Islands of every piece: side regions of colours 1.., then top/bottom regions of colours 0..
|
||||||
|
std::vector<const ExPolygon *> pieces;
|
||||||
|
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||||
|
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
|
||||||
|
pieces.emplace_back(&piece);
|
||||||
|
if (!top_and_bottom_layers.empty())
|
||||||
|
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
|
||||||
|
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
|
||||||
|
pieces.emplace_back(&piece);
|
||||||
|
std::vector<std::vector<size_t>> overlapped(pieces.size());
|
||||||
|
tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); });
|
||||||
|
std::vector<size_t> piece_island(pieces.size());
|
||||||
|
for (size_t i = 0; i < pieces.size(); ++i) {
|
||||||
|
piece_island[i] = overlapped[i].empty() ? islands.size() : overlapped[i].front();
|
||||||
|
for (size_t island : overlapped[i])
|
||||||
|
parent[root(island)] = root(piece_island[i]);
|
||||||
}
|
}
|
||||||
|
std::vector<size_t> bucket_of(parent.size(), size_t(-1));
|
||||||
|
size_t num_buckets = 0;
|
||||||
|
for (size_t i = 0; i < parent.size(); ++i)
|
||||||
|
if (size_t &b = bucket_of[root(i)]; b == size_t(-1))
|
||||||
|
b = num_buckets++;
|
||||||
|
|
||||||
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
|
// [bucket][colour]
|
||||||
}
|
std::vector<std::vector<ExPolygons>> sides(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||||
|
std::vector<std::vector<ExPolygons>> tops(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||||
|
size_t piece_idx = 0;
|
||||||
|
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||||
|
for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
|
||||||
|
sides[bucket_of[root(piece_island[piece_idx++])]][extruder_id].emplace_back(piece);
|
||||||
|
if (!top_and_bottom_layers.empty())
|
||||||
|
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
|
||||||
|
for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
|
||||||
|
tops[bucket_of[root(piece_island[piece_idx++])]][color_idx].emplace_back(piece);
|
||||||
|
|
||||||
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
// Side regions minus the top/bottom regions of every colour.
|
||||||
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
|
std::vector<std::vector<ExPolygons>> merged(num_buckets, std::vector<ExPolygons>(num_facets_states));
|
||||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
|
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
|
||||||
|
Polygons tops_all;
|
||||||
|
for (const ExPolygons &t : tops[bucket])
|
||||||
|
polygons_append(tops_all, t);
|
||||||
|
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
|
||||||
|
if (!sides[bucket][extruder_id].empty())
|
||||||
|
merged[bucket][extruder_id] = tops_all.empty() ? std::move(sides[bucket][extruder_id]) :
|
||||||
|
diff_ex_by_piece(sides[bucket][extruder_id], tops_all);
|
||||||
|
});
|
||||||
|
|
||||||
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
// Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left.
|
||||||
if (!was_top_and_bottom_empty)
|
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
|
||||||
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
if (top_and_bottom_layers.empty() || top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
||||||
|
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
|
||||||
|
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
bool was_top_and_bottom_empty = true;
|
||||||
|
for (size_t bucket = 0; bucket < num_buckets && was_top_and_bottom_empty; ++bucket)
|
||||||
|
was_top_and_bottom_empty = merged[bucket][extruder_id].empty();
|
||||||
|
tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
|
||||||
|
ExPolygons ®ion = merged[bucket][extruder_id];
|
||||||
|
append(region, tops[bucket][extruder_id]);
|
||||||
|
if (!was_top_and_bottom_empty && !region.empty())
|
||||||
|
region = offset2_ex(union_ex(region), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||||
|
});
|
||||||
|
for (size_t bucket = 0; bucket < num_buckets; ++bucket)
|
||||||
|
append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}); // end of parallel_for
|
}); // end of parallel_for
|
||||||
@@ -2203,16 +2363,56 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
|
|||||||
|
|
||||||
assert(!color_poly.empty());
|
assert(!color_poly.empty());
|
||||||
assert(!color_poly.front().empty());
|
assert(!color_poly.front().empty());
|
||||||
if (has_layer_only_one_color(color_poly)) {
|
// Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to
|
||||||
// If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
|
// that island's contours than to any other island's - the way out crosses its own boundary first - so its
|
||||||
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
|
// Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief
|
||||||
} else {
|
// has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions
|
||||||
MMU_Graph graph = build_graph(layer_idx, color_poly);
|
// that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in
|
||||||
remove_multiple_edges_in_vertices(graph, color_poly);
|
// parallel; an island in a single colour needs no diagram at all.
|
||||||
graph.remove_nodes_with_one_arc();
|
const ExPolygons &islands = input_expolygons[layer_idx];
|
||||||
segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states);
|
std::vector<std::pair<size_t, size_t>> island_contours(islands.size()); // [first, last) into color_poly
|
||||||
//segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx);
|
{
|
||||||
|
// The same order EdgeGrid::Grid::create() lists the contours in, and so colorize_contours().
|
||||||
|
size_t idx = 0;
|
||||||
|
for (size_t island_idx = 0; island_idx < islands.size(); ++island_idx) {
|
||||||
|
const size_t first = idx;
|
||||||
|
if (!islands[island_idx].contour.empty())
|
||||||
|
++idx;
|
||||||
|
for (const Polygon &hole : islands[island_idx].holes)
|
||||||
|
if (!hole.empty())
|
||||||
|
++idx;
|
||||||
|
island_contours[island_idx] = {first, idx};
|
||||||
}
|
}
|
||||||
|
assert(idx == color_poly.size());
|
||||||
|
}
|
||||||
|
std::vector<std::vector<ExPolygons>> island_regions(islands.size());
|
||||||
|
tbb::parallel_for(size_t(0), islands.size(), [&](size_t island_idx) {
|
||||||
|
const auto [first, last] = island_contours[island_idx];
|
||||||
|
if (first == last)
|
||||||
|
return;
|
||||||
|
const std::vector<ColoredLines> island_poly(color_poly.begin() + first, color_poly.begin() + last);
|
||||||
|
std::vector<ExPolygons> ®ions = island_regions[island_idx];
|
||||||
|
if (has_layer_only_one_color(island_poly)) {
|
||||||
|
regions.assign(num_facets_states, ExPolygons());
|
||||||
|
regions[size_t(island_poly.front().front().color)].emplace_back(islands[island_idx]);
|
||||||
|
} else {
|
||||||
|
MMU_Graph graph = build_graph(layer_idx, island_poly);
|
||||||
|
remove_multiple_edges_in_vertices(graph, island_poly);
|
||||||
|
graph.remove_nodes_with_one_arc();
|
||||||
|
regions = extract_colored_segments(graph, num_facets_states);
|
||||||
|
// The faces of one colour tile it without overlapping; merged here, where an island is small,
|
||||||
|
// every later boolean gets a few regions instead of thousands of faces sharing their edges. An
|
||||||
|
// island with many holes keeps its faces: merged, each colour would be one region with thousands
|
||||||
|
// of holes, and subtracting from that is far slower than from the faces one at a time.
|
||||||
|
if (island_poly.size() <= 64)
|
||||||
|
for (ExPolygons &faces : regions)
|
||||||
|
if (faces.size() > 1)
|
||||||
|
faces = union_ex(faces);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (std::vector<ExPolygons> ®ions : island_regions)
|
||||||
|
for (size_t color_idx = 0; color_idx < regions.size(); ++color_idx)
|
||||||
|
append(segmented_regions[layer_idx][color_idx], std::move(regions[color_idx]));
|
||||||
|
|
||||||
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
||||||
export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]);
|
export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]);
|
||||||
@@ -2235,7 +2435,7 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
|
|||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
|
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(input_expolygons, segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
|
|
||||||
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
|
||||||
|
|||||||
@@ -23,11 +23,15 @@ public:
|
|||||||
|
|
||||||
MultiPoint() {}
|
MultiPoint() {}
|
||||||
MultiPoint(const MultiPoint &other) : points(other.points) {}
|
MultiPoint(const MultiPoint &other) : points(other.points) {}
|
||||||
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
|
MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {}
|
||||||
MultiPoint(std::initializer_list<Point> list) : points(list) {}
|
MultiPoint(std::initializer_list<Point> list) : points(list) {}
|
||||||
explicit MultiPoint(const Points &_points) : points(_points) {}
|
explicit MultiPoint(const Points &_points) : points(_points) {}
|
||||||
|
// Without it, the derived classes' move constructors passing std::move(points) here copied them, which
|
||||||
|
// also means a moved-from Polygon or Polyline is now really empty where it used to silently keep its
|
||||||
|
// points: a use-after-move anywhere in the tree that happened to work before now sees nothing.
|
||||||
|
explicit MultiPoint(Points &&_points) noexcept : points(std::move(_points)) {}
|
||||||
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
|
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
|
||||||
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
|
MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; }
|
||||||
virtual ~MultiPoint() = default;
|
virtual ~MultiPoint() = default;
|
||||||
void scale(double factor);
|
void scale(double factor);
|
||||||
void scale(double factor_x, double factor_y);
|
void scale(double factor_x, double factor_y);
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <tbb/blocked_range.h>
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -2491,7 +2493,22 @@ void PerimeterGenerator::process_arachne()
|
|||||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_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
|
// we need to process each island separately because we might have different
|
||||||
// extra perimeters for each one
|
// extra perimeters for each one
|
||||||
for (const Surface& surface : all_surfaces) {
|
// Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands
|
||||||
|
// of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the
|
||||||
|
// original island order, which is what the extra overhang perimeters (applied to the last island's loops and to
|
||||||
|
// all fill surfaces so far) depend on.
|
||||||
|
struct ArachneSurfaceResult
|
||||||
|
{
|
||||||
|
ExtrusionEntityCollection loops;
|
||||||
|
bool has_loops = false;
|
||||||
|
ExPolygons infill;
|
||||||
|
ExPolygons no_overlap;
|
||||||
|
};
|
||||||
|
std::vector<ArachneSurfaceResult> results(all_surfaces.size());
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, all_surfaces.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
|
||||||
|
const Surface &surface = all_surfaces[surface_idx];
|
||||||
|
ArachneSurfaceResult &result = results[surface_idx];
|
||||||
coord_t bead_width_0 = ext_perimeter_spacing;
|
coord_t bead_width_0 = ext_perimeter_spacing;
|
||||||
// detect how many perimeters must be generated for this island
|
// detect how many perimeters must be generated for this island
|
||||||
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
|
int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
|
||||||
@@ -2755,10 +2772,10 @@ void PerimeterGenerator::process_arachne()
|
|||||||
// Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
|
// Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
|
||||||
// rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
|
// rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
|
||||||
coord_t threshold_external = (apply_precise_outer_wall)
|
coord_t threshold_external = (apply_precise_outer_wall)
|
||||||
// Precise outer wall ⇒ use “full external spacing”
|
// Precise outer wall: use the full external spacing
|
||||||
? ( this->ext_perimeter_flow.scaled_spacing()
|
? ( this->ext_perimeter_flow.scaled_spacing()
|
||||||
+ this->perimeter_flow.scaled_spacing()/2.0 )
|
+ this->perimeter_flow.scaled_spacing()/2.0 )
|
||||||
// Normal ⇒ half ext spacing + half int spacing
|
// Normal: half ext spacing plus half int spacing
|
||||||
: ( this->ext_perimeter_flow.scaled_spacing()/2.0
|
: ( this->ext_perimeter_flow.scaled_spacing()/2.0
|
||||||
+ this->perimeter_flow.scaled_spacing()/2.0 );
|
+ this->perimeter_flow.scaled_spacing()/2.0 );
|
||||||
|
|
||||||
@@ -2848,7 +2865,8 @@ void PerimeterGenerator::process_arachne()
|
|||||||
this->config->overhang_reverse_internal_only);
|
this->config->overhang_reverse_internal_only);
|
||||||
}
|
}
|
||||||
defer_unsupported_loops(*this, extrusion_coll);
|
defer_unsupported_loops(*this, extrusion_coll);
|
||||||
this->loops->append(extrusion_coll);
|
result.loops = std::move(extrusion_coll);
|
||||||
|
result.has_loops = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||||
@@ -2891,9 +2909,7 @@ void PerimeterGenerator::process_arachne()
|
|||||||
if (!top_expolygons.empty()) {
|
if (!top_expolygons.empty()) {
|
||||||
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
|
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
|
||||||
}
|
}
|
||||||
this->fill_surfaces->append(infill_exp, stInternal);
|
result.infill = std::move(infill_exp);
|
||||||
|
|
||||||
apply_extra_perimeters(infill_exp);
|
|
||||||
|
|
||||||
// BBS: get the no-overlap infill expolygons
|
// BBS: get the no-overlap infill expolygons
|
||||||
{
|
{
|
||||||
@@ -2904,9 +2920,18 @@ void PerimeterGenerator::process_arachne()
|
|||||||
float(+min_perimeter_infill_spacing / 2.));
|
float(+min_perimeter_infill_spacing / 2.));
|
||||||
if (!top_expolygons.empty())
|
if (!top_expolygons.empty())
|
||||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
|
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
|
||||||
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
result.no_overlap = std::move(polyWithoutOverlap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
for (ArachneSurfaceResult &result : results) {
|
||||||
|
if (result.has_loops)
|
||||||
|
// Moved, not copied: append(const ExtrusionEntity &) clones the whole wall tree of the island.
|
||||||
|
this->loops->append(std::move(result.loops));
|
||||||
|
this->fill_surfaces->append(result.infill, stInternal);
|
||||||
|
apply_extra_perimeters(result.infill);
|
||||||
|
append(*this->fill_no_overlap, std::move(result.no_overlap));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PerimeterGeneratorLoop::is_internal_contour() const
|
bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public:
|
|||||||
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
explicit Polygon(const Points &points) : MultiPoint(points) {}
|
||||||
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
|
||||||
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
Polygon(const Polygon &other) : MultiPoint(other.points) {}
|
||||||
Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
|
Polygon(Polygon &&other) noexcept : MultiPoint(std::move(other.points)) {}
|
||||||
static Polygon new_scale(const std::vector<Vec2d> &points) {
|
static Polygon new_scale(const std::vector<Vec2d> &points) {
|
||||||
Polygon pgn;
|
Polygon pgn;
|
||||||
pgn.points.reserve(points.size());
|
pgn.points.reserve(points.size());
|
||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
return pgn;
|
return pgn;
|
||||||
}
|
}
|
||||||
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
|
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
|
||||||
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
|
Polygon& operator=(Polygon &&other) noexcept { points = std::move(other.points); return *this; }
|
||||||
|
|
||||||
Point& operator[](Points::size_type idx) { return this->points[idx]; }
|
Point& operator[](Points::size_type idx) { return this->points[idx]; }
|
||||||
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Polyline : public MultiPoint {
|
|||||||
public:
|
public:
|
||||||
Polyline() {};
|
Polyline() {};
|
||||||
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
|
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
|
||||||
Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
|
Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
|
||||||
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
|
Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
|
||||||
fitting_result.clear();
|
fitting_result.clear();
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ public:
|
|||||||
fitting_result = other.fitting_result;
|
fitting_result = other.fitting_result;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
Polyline& operator=(Polyline&& other) {
|
Polyline& operator=(Polyline&& other) noexcept {
|
||||||
points = std::move(other.points);
|
points = std::move(other.points);
|
||||||
fitting_result = std::move(other.fitting_result);
|
fitting_result = std::move(other.fitting_result);
|
||||||
return *this;
|
return *this;
|
||||||
|
|||||||
+338
-168
@@ -47,6 +47,7 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
#include <array>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
|
#include <tbb/parallel_invoke.h>
|
||||||
#include <tbb/spin_mutex.h>
|
#include <tbb/spin_mutex.h>
|
||||||
#include <tbb/concurrent_unordered_set.h>
|
#include <tbb/concurrent_unordered_set.h>
|
||||||
|
|
||||||
@@ -1695,7 +1697,9 @@ void PrintObject::detect_surfaces_type()
|
|||||||
bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
|
bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
|
||||||
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
|
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
|
||||||
|
|
||||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
|
||||||
|
// others, so the regions run next to each other instead of one after another, each still over all layers.
|
||||||
|
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
|
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
for (Layer *layer : m_layers)
|
for (Layer *layer : m_layers)
|
||||||
@@ -1753,7 +1757,7 @@ void PrintObject::detect_surfaces_type()
|
|||||||
if (upper_layer) {
|
if (upper_layer) {
|
||||||
ExPolygons upper_slices = interface_shells ?
|
ExPolygons upper_slices = interface_shells ?
|
||||||
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
|
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
|
||||||
diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
|
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(upper_layer->lslices), ApplySafetyOffset::Yes);
|
||||||
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
|
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
|
||||||
} else {
|
} else {
|
||||||
// if no upper layer, all surfaces of this one are solid
|
// if no upper layer, all surfaces of this one are solid
|
||||||
@@ -1779,7 +1783,7 @@ void PrintObject::detect_surfaces_type()
|
|||||||
surfaces_append(
|
surfaces_append(
|
||||||
bottom,
|
bottom,
|
||||||
opening_ex(
|
opening_ex(
|
||||||
diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
|
diff_ex_by_piece(layerm_slices_surfaces, to_polygons(lower_layer->lslices), ApplySafetyOffset::Yes),
|
||||||
offset),
|
offset),
|
||||||
surface_type_bottom_other);
|
surface_type_bottom_other);
|
||||||
// if user requested internal shells, we need to identify surfaces
|
// if user requested internal shells, we need to identify surfaces
|
||||||
@@ -1810,34 +1814,44 @@ void PrintObject::detect_surfaces_type()
|
|||||||
// and top surfaces; let's do an intersection to discover them and consider them
|
// and top surfaces; let's do an intersection to discover them and consider them
|
||||||
// as bottom surfaces (to allow for bridge detection)
|
// as bottom surfaces (to allow for bridge detection)
|
||||||
if (! top.empty() && ! bottom.empty()) {
|
if (! top.empty() && ! bottom.empty()) {
|
||||||
const auto cracks = intersection_ex(top, bottom);
|
const auto cracks = intersection_ex_by_piece(to_expolygons(top), to_polygons(bottom));
|
||||||
if (!cracks.empty()) {
|
if (!cracks.empty()) {
|
||||||
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom
|
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom
|
||||||
const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5;
|
const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5;
|
||||||
|
|
||||||
|
// Only the bottom surfaces near a crack can take part: one that contains it must contain its box,
|
||||||
|
// and one whose box misses the grown crack is left unchanged by removing it. A layer cut through
|
||||||
|
// a fine relief has thousands of both, which made this loop quadratic.
|
||||||
for (const auto& crack : cracks) {
|
for (const auto& crack : cracks) {
|
||||||
if (offset_ex(crack, small_crack_threshold).empty()) {
|
if (offset_ex(crack, small_crack_threshold).empty()) {
|
||||||
// For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well
|
// For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well
|
||||||
if (std::any_of(bottom.begin(), bottom.end(), [&crack, small_crack_threshold](const Surface& s) {
|
const BoundingBox crack_bbox = get_extents(crack);
|
||||||
|
if (std::any_of(bottom.begin(), bottom.end(), [&crack, &crack_bbox, small_crack_threshold](const Surface& s) {
|
||||||
const auto& se = s.expolygon;
|
const auto& se = s.expolygon;
|
||||||
return diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
|
return get_extents(se).inflated(SCALED_EPSILON).contains(crack_bbox)
|
||||||
|
&& diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
|
||||||
&& se.area() > crack.area() * 2
|
&& se.area() > crack.area() * 2
|
||||||
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
|
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
|
||||||
})) continue;
|
})) continue;
|
||||||
|
|
||||||
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces
|
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces
|
||||||
|
const ExPolygons grown_crack = offset_ex(crack, -small_crack_threshold);
|
||||||
|
const BoundingBox grown_bbox = get_extents(grown_crack);
|
||||||
Surfaces bot_tmp;
|
Surfaces bot_tmp;
|
||||||
for (auto& b : bottom) {
|
for (auto& b : bottom) {
|
||||||
surfaces_append(bot_tmp, diff_ex(b.expolygon, offset_ex(crack, -small_crack_threshold)), b.surface_type);
|
if (get_extents(b.expolygon).overlap(grown_bbox))
|
||||||
|
surfaces_append(bot_tmp, diff_ex(b.expolygon, grown_crack), b.surface_type);
|
||||||
|
else
|
||||||
|
bot_tmp.emplace_back(std::move(b));
|
||||||
}
|
}
|
||||||
bottom = std::move(bot_tmp);
|
bottom = std::move(bot_tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Polygons top_polygons = to_polygons(std::move(top));
|
ExPolygons top_expolygons = to_expolygons(std::move(top));
|
||||||
top.clear();
|
top.clear();
|
||||||
surfaces_append(top, diff_ex(top_polygons, bottom), stTop);
|
surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1928,7 +1942,7 @@ void PrintObject::detect_surfaces_type()
|
|||||||
{
|
{
|
||||||
Polygons topbottom = to_polygons(top);
|
Polygons topbottom = to_polygons(top);
|
||||||
polygons_append(topbottom, to_polygons(bottom));
|
polygons_append(topbottom, to_polygons(bottom));
|
||||||
surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal);
|
surfaces_append(surfaces_out, diff_ex_by_piece(surfaces_prev_expolys, topbottom), stInternal);
|
||||||
}
|
}
|
||||||
|
|
||||||
surfaces_append(surfaces_out, std::move(top));
|
surfaces_append(surfaces_out, std::move(top));
|
||||||
@@ -2105,29 +2119,31 @@ void PrintObject::detect_surfaces_type()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// ==============================================================================================================
|
|
||||||
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
|
|
||||||
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
|
|
||||||
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
|
|
||||||
// ==============================================================================================================
|
|
||||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
|
||||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &range) {
|
|
||||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
|
|
||||||
Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
|
|
||||||
for (Surface &s : surfs) {
|
|
||||||
if (s.surface_type == stInternalAfterExternalBridge) {
|
|
||||||
s.surface_type = stBottomBridge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
// === ORCA: End of second external bridge layer changes =======================================================
|
// === ORCA: End of second external bridge layer changes =======================================================
|
||||||
// ==============================================================================================================
|
// ==============================================================================================================
|
||||||
|
|
||||||
|
}); // for each this->print->region_count
|
||||||
|
|
||||||
|
// ==============================================================================================================
|
||||||
|
// === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
|
||||||
|
// === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
|
||||||
|
// === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
|
||||||
|
// ==============================================================================================================
|
||||||
|
// Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below.
|
||||||
|
if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) {
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &range) {
|
||||||
|
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer)
|
||||||
|
for (LayerRegion *layerm : m_layers[idx_layer]->regions())
|
||||||
|
for (Surface &s : layerm->slices.surfaces)
|
||||||
|
if (s.surface_type == stInternalAfterExternalBridge)
|
||||||
|
s.surface_type = stBottomBridge;
|
||||||
|
});
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
|
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
|
||||||
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
|
||||||
tbb::parallel_for(
|
tbb::parallel_for(
|
||||||
@@ -2144,7 +2160,7 @@ void PrintObject::detect_surfaces_type()
|
|||||||
});
|
});
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
|
||||||
} // for each this->print->region_count
|
});
|
||||||
|
|
||||||
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
|
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
|
||||||
m_typed_slices = true;
|
m_typed_slices = true;
|
||||||
@@ -2211,8 +2227,10 @@ void PrintObject::process_external_surfaces()
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
|
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start";
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
|
// The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
|
||||||
|
// others, so the regions run next to each other instead of one after another, each still over all layers.
|
||||||
|
tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) {
|
||||||
tbb::parallel_for(
|
tbb::parallel_for(
|
||||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||||
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
|
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
|
||||||
@@ -2227,9 +2245,9 @@ void PrintObject::process_external_surfaces()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
|
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintObject::discover_vertical_shells()
|
void PrintObject::discover_vertical_shells()
|
||||||
@@ -2268,10 +2286,10 @@ void PrintObject::discover_vertical_shells()
|
|||||||
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
|
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
|
||||||
return;
|
return;
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
|
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
|
||||||
//FIXME Improve the heuristics for a grain size.
|
// One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a
|
||||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
// few such layers next to each other must not end up in one task.
|
||||||
tbb::parallel_for(
|
tbb::parallel_for(
|
||||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
tbb::blocked_range<size_t>(0, num_layers, 1),
|
||||||
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||||
const size_t num_regions = this->num_printing_regions();
|
const size_t num_regions = this->num_printing_regions();
|
||||||
@@ -2279,26 +2297,38 @@ void PrintObject::discover_vertical_shells()
|
|||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
const Layer &layer = *m_layers[idx_layer];
|
const Layer &layer = *m_layers[idx_layer];
|
||||||
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
|
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
|
||||||
// Simulate single set of perimeters over all merged regions.
|
const auto top_bottom_expansion = [&layer](size_t region_id) {
|
||||||
float perimeter_offset = 0.f;
|
return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||||
float perimeter_min_spacing = FLT_MAX;
|
};
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
static size_t debug_idx = 0;
|
static size_t debug_idx = 0;
|
||||||
++ debug_idx;
|
++ debug_idx;
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
|
// The top surfaces, the bottom surfaces and the holes are independent of each other.
|
||||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
tbb::parallel_invoke(
|
||||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
[&]() {
|
||||||
// Top surfaces.
|
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
|
||||||
append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion));
|
append(cache.top_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_type(stTop), top_bottom_expansion(region_id)));
|
||||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||||
// Bottom surfaces.
|
// Save some computing time by reducing the number of polygons.
|
||||||
append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
cache.top_surfaces = union_(cache.top_surfaces);
|
||||||
|
},
|
||||||
|
[&]() {
|
||||||
|
for (size_t region_id = 0; region_id < num_regions; ++ region_id)
|
||||||
|
append(cache.bottom_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_types(surfaces_bottom), top_bottom_expansion(region_id)));
|
||||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||||
|
cache.bottom_surfaces = union_(cache.bottom_surfaces);
|
||||||
|
},
|
||||||
|
[&]() {
|
||||||
|
// Simulate single set of perimeters over all merged regions.
|
||||||
|
float perimeter_offset = 0.f;
|
||||||
|
float perimeter_min_spacing = FLT_MAX;
|
||||||
|
for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
|
||||||
|
const LayerRegion &layerm = *layer.m_regions[region_id];
|
||||||
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
|
// Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
|
||||||
// First find the maxium number of perimeters per region slice.
|
// First find the maxium number of perimeters per region slice.
|
||||||
unsigned int perimeters = 0;
|
unsigned int perimeters = 0;
|
||||||
for (Surface &s : layerm.slices.surfaces)
|
for (const Surface &s : layerm.slices.surfaces)
|
||||||
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
|
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
|
||||||
perimeters += layerm.region().config().wall_loops.value;
|
perimeters += layerm.region().config().wall_loops.value;
|
||||||
// Then calculate the infill offset.
|
// Then calculate the infill offset.
|
||||||
@@ -2311,9 +2341,6 @@ void PrintObject::discover_vertical_shells()
|
|||||||
}
|
}
|
||||||
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
|
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
|
||||||
}
|
}
|
||||||
// Save some computing time by reducing the number of polygons.
|
|
||||||
cache.top_surfaces = union_(cache.top_surfaces);
|
|
||||||
cache.bottom_surfaces = union_(cache.bottom_surfaces);
|
|
||||||
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
|
// For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
|
||||||
if (perimeter_offset > 0.) {
|
if (perimeter_offset > 0.) {
|
||||||
// The layer.lslices are forced to merge by expanding them first.
|
// The layer.lslices are forced to merge by expanding them first.
|
||||||
@@ -2329,106 +2356,32 @@ void PrintObject::discover_vertical_shells()
|
|||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
}
|
}
|
||||||
cache.holes = union_(cache.holes);
|
cache.holes = union_(cache.holes);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
|
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
// With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing
|
||||||
const PrintRegion ®ion = this->printing_region(region_id);
|
// region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
|
||||||
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
|
// one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
|
||||||
// This region will be handled by discover_horizontal_shells().
|
// surfaces, and a multi-material print has a region per filament.
|
||||||
continue;
|
using AccumulationKey = std::array<double, 5>;
|
||||||
|
struct ShellAccumulation
|
||||||
//FIXME Improve the heuristics for a grain size.
|
{
|
||||||
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
AccumulationKey key;
|
||||||
|
|
||||||
if (! top_bottom_surfaces_all_regions) {
|
|
||||||
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
|
|
||||||
// is calculated over a single material.
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
|
|
||||||
tbb::parallel_for(
|
|
||||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
|
||||||
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
|
||||||
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
|
||||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
|
||||||
m_print->throw_if_canceled();
|
|
||||||
Layer &layer = *m_layers[idx_layer];
|
|
||||||
LayerRegion &layerm = *layer.m_regions[region_id];
|
|
||||||
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
|
||||||
// Top surfaces.
|
|
||||||
auto &cache = cache_top_botom_regions[idx_layer];
|
|
||||||
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
|
|
||||||
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
|
||||||
// Bottom surfaces.
|
|
||||||
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
|
|
||||||
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
|
||||||
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
|
|
||||||
if (cache.holes.empty()) {
|
|
||||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
|
|
||||||
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
m_print->throw_if_canceled();
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
|
|
||||||
grain_size = 1;
|
|
||||||
tbb::parallel_for(
|
|
||||||
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
|
||||||
[this, region_id, &cache_top_botom_regions]
|
|
||||||
(const tbb::blocked_range<size_t>& range) {
|
|
||||||
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
|
|
||||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
|
||||||
m_print->throw_if_canceled();
|
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
|
||||||
static size_t debug_idx = 0;
|
|
||||||
++ debug_idx;
|
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
|
||||||
|
|
||||||
Layer *layer = m_layers[idx_layer];
|
|
||||||
LayerRegion *layerm = layer->m_regions[region_id];
|
|
||||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
|
||||||
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
|
|
||||||
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
|
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
|
||||||
|
|
||||||
Flow solid_infill_flow = layerm->flow(frSolidInfill);
|
|
||||||
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
|
||||||
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
|
|
||||||
Polygons shell;
|
Polygons shell;
|
||||||
Polygons holes;
|
Polygons holes;
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
};
|
||||||
ExPolygons shell_ex;
|
const auto accumulation_key = [](const PrintRegionConfig ®ion_config, const LayerRegion *layerm) {
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
|
||||||
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
|
||||||
#if 0
|
double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
|
||||||
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
};
|
||||||
{
|
const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig ®ion_config,
|
||||||
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
|
const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
|
||||||
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
|
const Layer *layer = m_layers[idx_layer];
|
||||||
if (n < 0 || n >= (int)m_layers.size())
|
|
||||||
continue;
|
|
||||||
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
|
|
||||||
for (size_t i = 0; i < expolys.size(); ++ i) {
|
|
||||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
|
|
||||||
svg.draw(expolys[i]);
|
|
||||||
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
|
||||||
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
|
||||||
svg.Close();
|
|
||||||
|
|
||||||
svg_cummulative.draw(expolys[i]);
|
|
||||||
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
|
||||||
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
|
||||||
polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
|
polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
|
||||||
auto combine_holes = [&holes](const Polygons &holes2) {
|
auto combine_holes = [&holes](const Polygons &holes2) {
|
||||||
if (holes.empty() || holes2.empty())
|
if (holes.empty() || holes2.empty())
|
||||||
@@ -2503,6 +2456,141 @@ void PrintObject::discover_vertical_shells()
|
|||||||
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
|
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
|
||||||
combine_holes(cache_top_botom_regions[i].holes);
|
combine_holes(cache_top_botom_regions[i].holes);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
std::vector<std::vector<ShellAccumulation>> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0);
|
||||||
|
if (! shell_accumulations.empty()) {
|
||||||
|
// Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared
|
||||||
|
// between them and they can run next to each other.
|
||||||
|
std::vector<std::array<size_t, 3>> todo; // layer, its slot, a region holding the key
|
||||||
|
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
|
||||||
|
std::vector<ShellAccumulation> &accumulations = shell_accumulations[idx_layer];
|
||||||
|
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
|
||||||
|
if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll)
|
||||||
|
continue;
|
||||||
|
const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
|
||||||
|
const AccumulationKey key = accumulation_key(layerm->region().config(), layerm);
|
||||||
|
if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) {
|
||||||
|
todo.push_back({ idx_layer, accumulations.size(), region_id });
|
||||||
|
accumulations.push_back({ key, {}, {} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) {
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]];
|
||||||
|
ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]];
|
||||||
|
accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes);
|
||||||
|
});
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto process_region = [&](size_t region_id) {
|
||||||
|
const PrintRegion ®ion = this->printing_region(region_id);
|
||||||
|
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
|
||||||
|
// This region will be handled by discover_horizontal_shells().
|
||||||
|
return;
|
||||||
|
|
||||||
|
//FIXME Improve the heuristics for a grain size.
|
||||||
|
size_t grain_size = std::max(num_layers / 16, size_t(1));
|
||||||
|
|
||||||
|
if (! top_bottom_surfaces_all_regions) {
|
||||||
|
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
|
||||||
|
// is calculated over a single material.
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
|
||||||
|
tbb::parallel_for(
|
||||||
|
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||||
|
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||||
|
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
|
||||||
|
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
Layer &layer = *m_layers[idx_layer];
|
||||||
|
LayerRegion &layerm = *layer.m_regions[region_id];
|
||||||
|
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
|
||||||
|
// Top surfaces.
|
||||||
|
auto &cache = cache_top_botom_regions[idx_layer];
|
||||||
|
cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion);
|
||||||
|
// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
|
||||||
|
// Bottom surfaces.
|
||||||
|
cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion);
|
||||||
|
// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
|
||||||
|
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
|
||||||
|
if (cache.holes.empty()) {
|
||||||
|
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
|
||||||
|
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
|
||||||
|
grain_size = 1;
|
||||||
|
tbb::parallel_for(
|
||||||
|
tbb::blocked_range<size_t>(0, num_layers, grain_size),
|
||||||
|
[this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell]
|
||||||
|
(const tbb::blocked_range<size_t>& range) {
|
||||||
|
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
|
||||||
|
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||||
|
m_print->throw_if_canceled();
|
||||||
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
|
static size_t debug_idx = 0;
|
||||||
|
++ debug_idx;
|
||||||
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
|
||||||
|
Layer *layer = m_layers[idx_layer];
|
||||||
|
LayerRegion *layerm = layer->m_regions[region_id];
|
||||||
|
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||||
|
|
||||||
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
|
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
|
||||||
|
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
|
||||||
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
|
||||||
|
Flow solid_infill_flow = layerm->flow(frSolidInfill);
|
||||||
|
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
|
||||||
|
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
|
||||||
|
Polygons shell;
|
||||||
|
Polygons holes;
|
||||||
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
|
ExPolygons shell_ex;
|
||||||
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
|
||||||
|
#if 0
|
||||||
|
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
|
{
|
||||||
|
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
|
||||||
|
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
|
||||||
|
if (n < 0 || n >= (int)m_layers.size())
|
||||||
|
continue;
|
||||||
|
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
|
||||||
|
for (size_t i = 0; i < expolys.size(); ++ i) {
|
||||||
|
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
|
||||||
|
svg.draw(expolys[i]);
|
||||||
|
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||||
|
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||||
|
svg.Close();
|
||||||
|
|
||||||
|
svg_cummulative.draw(expolys[i]);
|
||||||
|
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
|
||||||
|
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
const AccumulationKey key = accumulation_key(region_config, layerm);
|
||||||
|
const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr :
|
||||||
|
[&]() -> const ShellAccumulation * {
|
||||||
|
for (const ShellAccumulation &a : shell_accumulations[idx_layer])
|
||||||
|
if (a.key == key)
|
||||||
|
return &a;
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
if (reused != nullptr) {
|
||||||
|
shell = reused->shell;
|
||||||
|
holes = reused->holes;
|
||||||
|
} else
|
||||||
|
accumulate_shell(idx_layer, region_config, layerm, shell, holes);
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
{
|
{
|
||||||
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
|
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
|
||||||
@@ -2596,11 +2684,8 @@ void PrintObject::discover_vertical_shells()
|
|||||||
Polygons object_volume;
|
Polygons object_volume;
|
||||||
Polygons internal_volume;
|
Polygons internal_volume;
|
||||||
{
|
{
|
||||||
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{};
|
if (idx_layer > 0 && idx_layer + 1 < m_layers.size())
|
||||||
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ?
|
object_volume = to_polygons(intersection_ex_by_piece(m_layers[idx_layer - 1]->lslices, to_polygons(m_layers[idx_layer + 1]->lslices)));
|
||||||
to_polygons(m_layers[idx_layer + 1]->lslices) :
|
|
||||||
Polygons{};
|
|
||||||
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
|
|
||||||
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
|
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2611,15 +2696,34 @@ void PrintObject::discover_vertical_shells()
|
|||||||
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
|
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
|
||||||
// 2. the area does not fully cover an internal polygon
|
// 2. the area does not fully cover an internal polygon
|
||||||
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
|
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
|
||||||
|
// Both tests below compare a small piece against the whole layer. Done literally, that is
|
||||||
|
// quadratic in the number of pieces, which is what a layer split up by colour painting has,
|
||||||
|
// so each is restricted to the part of the layer near the piece with an identical result:
|
||||||
|
// object_volume is clipped to the piece's box, and only the internal polygons whose box meets
|
||||||
|
// the expanded piece take part in the count, since the others pass through the difference
|
||||||
|
// unchanged and add the same number to both sides of it.
|
||||||
|
std::vector<BoundingBox> internal_bboxes;
|
||||||
|
internal_bboxes.reserve(internal_volume.size());
|
||||||
|
for (const Polygon &poly : internal_volume)
|
||||||
|
internal_bboxes.emplace_back(get_extents(poly));
|
||||||
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
|
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
|
||||||
[&internal_volume, &min_perimeter_infill_spacing,
|
[&internal_volume, &internal_bboxes, &min_perimeter_infill_spacing,
|
||||||
&object_volume](const ExPolygon &p) {
|
&object_volume](const ExPolygon &p) {
|
||||||
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
const bool small = p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
|
||||||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
|
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
|
||||||
diff(to_polygons(p), object_volume).empty())) &&
|
diff(to_polygons(p),
|
||||||
diff(internal_volume,
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||||
expand(to_polygons(p), min_perimeter_infill_spacing))
|
object_volume, get_extents(p).inflated(SCALED_EPSILON)))
|
||||||
.size() >= internal_volume.size();
|
.empty());
|
||||||
|
if (!small)
|
||||||
|
return false;
|
||||||
|
const Polygons expanded = expand(to_polygons(p), min_perimeter_infill_spacing);
|
||||||
|
const BoundingBox bbox = get_extents(expanded);
|
||||||
|
Polygons nearby;
|
||||||
|
for (size_t i = 0; i < internal_volume.size(); ++i)
|
||||||
|
if (internal_bboxes[i].overlap(bbox))
|
||||||
|
nearby.emplace_back(internal_volume[i]);
|
||||||
|
return diff(nearby, expanded).size() >= nearby.size();
|
||||||
}),
|
}),
|
||||||
regularized_shell.end());
|
regularized_shell.end());
|
||||||
}
|
}
|
||||||
@@ -2641,8 +2745,9 @@ void PrintObject::discover_vertical_shells()
|
|||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
|
|
||||||
// Trim the internal & internalvoid by the shell.
|
// Trim the internal & internalvoid by the shell.
|
||||||
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell);
|
const Polygons regularized_shell_polygons = to_polygons(regularized_shell);
|
||||||
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell);
|
Slic3r::ExPolygons new_internal = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternal)), regularized_shell_polygons);
|
||||||
|
Slic3r::ExPolygons new_internal_void = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), regularized_shell_polygons);
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||||
{
|
{
|
||||||
@@ -2669,7 +2774,15 @@ void PrintObject::discover_vertical_shells()
|
|||||||
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
|
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
|
||||||
}
|
}
|
||||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||||
} // for each region
|
}; // for each region
|
||||||
|
if (top_bottom_surfaces_all_regions)
|
||||||
|
// Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others,
|
||||||
|
// so they run next to each other instead of one after another.
|
||||||
|
tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region);
|
||||||
|
else
|
||||||
|
// Here every region fills the one top/bottom cache with its own surfaces first.
|
||||||
|
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id)
|
||||||
|
process_region(region_id);
|
||||||
} // void PrintObject::discover_vertical_shells()
|
} // void PrintObject::discover_vertical_shells()
|
||||||
|
|
||||||
// #define DEBUG_BRIDGE_OVER_INFILL
|
// #define DEBUG_BRIDGE_OVER_INFILL
|
||||||
@@ -3190,6 +3303,16 @@ void PrintObject::bridge_over_infill()
|
|||||||
vertical_lines[i].b = Point{x, y_max};
|
vertical_lines[i].b = Point{x, y_max};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The vertical lines only span the bridged area's x range, so anchors entirely outside it can never be
|
||||||
|
// hit. Leaving them out gives the same intersections without building a tree over the whole layer's
|
||||||
|
// boundary for every bridge.
|
||||||
|
const coord_t scan_x_min = bb_x.min.x();
|
||||||
|
const coord_t scan_x_max = bb_x.min.x() + coord_t(n_vlines) * scan_spacing;
|
||||||
|
anchors.erase(std::remove_if(anchors.begin(), anchors.end(),
|
||||||
|
[scan_x_min, scan_x_max](const Line &l) {
|
||||||
|
return std::max(l.a.x(), l.b.x()) < scan_x_min || std::min(l.a.x(), l.b.x()) > scan_x_max;
|
||||||
|
}),
|
||||||
|
anchors.end());
|
||||||
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
|
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
|
||||||
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
|
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
|
||||||
|
|
||||||
@@ -3434,28 +3557,62 @@ void PrintObject::bridge_over_infill()
|
|||||||
|
|
||||||
std::vector<CandidateSurface> expanded_surfaces;
|
std::vector<CandidateSurface> expanded_surfaces;
|
||||||
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
|
expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
|
||||||
|
// The expanded fill boundary depends only on the bridging flow, and total_fill_area is not
|
||||||
|
// modified below, so build it once per spacing rather than once per candidate. A layer split
|
||||||
|
// into many candidates (e.g. by colour painting) otherwise repeats a layer-wide offset for each.
|
||||||
|
std::map<coord_t, Polylines> boundary_by_spacing;
|
||||||
|
// expansion_area is a clean, non-overlapping set, so uniting it with a bridge or cutting a bridge
|
||||||
|
// out of it only changes the polygons near that bridge. The rest are passed through untouched
|
||||||
|
// instead of being fed to Clipper with the whole layer again for every candidate.
|
||||||
|
// Not `near`/`far`: the Windows headers still define those as macros, and they expand to
|
||||||
|
// nothing, which turns the declaration below into an empty one.
|
||||||
|
const auto split_near = [](const Polygons &polys, const BoundingBox &bbox, Polygons &rest) {
|
||||||
|
Polygons nearby;
|
||||||
|
for (const Polygon &p : polys)
|
||||||
|
(get_extents(p).overlap(bbox) ? nearby : rest).emplace_back(p);
|
||||||
|
return nearby;
|
||||||
|
};
|
||||||
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
|
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
|
||||||
const auto ®ion_config = candidate.region->region().config();
|
const auto ®ion_config = candidate.region->region().config();
|
||||||
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
|
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
|
||||||
region_config.sparse_infill_pattern == ipOctagramSpiral;
|
region_config.sparse_infill_pattern == ipOctagramSpiral;
|
||||||
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
|
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
|
||||||
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
|
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
|
||||||
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
|
// deep_infill_area and internal_unsupported_area cover the whole layer; only their part under
|
||||||
|
// this candidate can change the results, so they are clipped to its box first.
|
||||||
|
if (!area_to_be_bridge.empty())
|
||||||
|
area_to_be_bridge = intersection(area_to_be_bridge,
|
||||||
|
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||||
|
deep_infill_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON)));
|
||||||
|
|
||||||
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
|
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
|
||||||
[internal_unsupported_area](const Polygon &p) {
|
[&internal_unsupported_area](const Polygon &p) {
|
||||||
return intersection({p}, internal_unsupported_area).empty();
|
return intersection({p}, ClipperUtils::clip_clipper_polygons_with_subject_bbox(
|
||||||
|
internal_unsupported_area,
|
||||||
|
get_extents(p).inflated(SCALED_EPSILON)))
|
||||||
|
.empty();
|
||||||
}),
|
}),
|
||||||
area_to_be_bridge.end());
|
area_to_be_bridge.end());
|
||||||
|
|
||||||
Polygons limiting_area = union_(area_to_be_bridge, expansion_area);
|
|
||||||
|
|
||||||
if (area_to_be_bridge.empty())
|
if (area_to_be_bridge.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing()));
|
Polygons limiting_area;
|
||||||
|
const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON),
|
||||||
|
limiting_area);
|
||||||
|
const size_t num_far = limiting_area.size();
|
||||||
|
append(limiting_area, union_(area_to_be_bridge, near_expansion));
|
||||||
|
|
||||||
|
auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing());
|
||||||
|
if (boundary_it == boundary_by_spacing.end())
|
||||||
|
boundary_it = boundary_by_spacing
|
||||||
|
.emplace(flow.scaled_spacing(), to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())))
|
||||||
|
.first;
|
||||||
|
Polylines boundary_plines = boundary_it->second;
|
||||||
{
|
{
|
||||||
Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing()));
|
// The sub-unit offset (spacing is in mm) still re-unites touching polygons by the bridge, which the anchors depend on.
|
||||||
|
Polylines limiting_plines = to_polylines(Polygons(limiting_area.begin(), limiting_area.begin() + num_far));
|
||||||
|
append(limiting_plines, to_polylines(expand(Polygons(limiting_area.begin() + num_far, limiting_area.end()), 0.3 * flow.spacing())));
|
||||||
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
|
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3530,8 +3687,11 @@ void PrintObject::bridge_over_infill()
|
|||||||
{
|
{
|
||||||
bool reconstruct = false;
|
bool reconstruct = false;
|
||||||
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
|
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
|
||||||
|
const BoundingBox tmp_expanded_bbox = get_extents(tmp_expanded_area);
|
||||||
for (const CandidateSurface &s : expanded_surfaces) {
|
for (const CandidateSurface &s : expanded_surfaces) {
|
||||||
if (!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
// Surfaces whose boxes miss each other cannot intersect, which is most pairs on a busy layer.
|
||||||
|
if (get_extents(s.new_polys).overlap(tmp_expanded_bbox) &&
|
||||||
|
!intersection(s.new_polys, tmp_expanded_area).empty()) {
|
||||||
bridging_angle = s.bridge_angle;
|
bridging_angle = s.bridge_angle;
|
||||||
reconstruct = true;
|
reconstruct = true;
|
||||||
break;
|
break;
|
||||||
@@ -3555,10 +3715,20 @@ void PrintObject::bridge_over_infill()
|
|||||||
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
|
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
|
||||||
bridging_angle, scan_spacing, true));
|
bridging_angle, scan_spacing, true));
|
||||||
}
|
}
|
||||||
bridging_area = intersection(bridging_area, limiting_area);
|
// Each of these meets one bridge with the whole layer, so the layer side is first cut down to the
|
||||||
bridging_area = intersection(bridging_area, total_fill_area);
|
// bridge's box (and expansion_area split as above); the result is the same.
|
||||||
bridging_area = diff(bridging_area, total_top_area);
|
if (!bridging_area.empty()) {
|
||||||
expansion_area = diff(expansion_area, bridging_area);
|
const BoundingBox bridging_bbox = get_extents(bridging_area).inflated(SCALED_EPSILON);
|
||||||
|
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(limiting_area, bridging_bbox));
|
||||||
|
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_fill_area, bridging_bbox));
|
||||||
|
bridging_area = diff(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_top_area, bridging_bbox));
|
||||||
|
}
|
||||||
|
if (!bridging_area.empty()) {
|
||||||
|
Polygons kept;
|
||||||
|
const Polygons cut = split_near(expansion_area, get_extents(bridging_area).inflated(SCALED_EPSILON), kept);
|
||||||
|
append(kept, diff(cut, bridging_area));
|
||||||
|
expansion_area = std::move(kept);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
#ifdef DEBUG_BRIDGE_OVER_INFILL
|
||||||
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r),
|
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r),
|
||||||
|
|||||||
@@ -955,9 +955,9 @@ public:
|
|||||||
::fread(&y, sizeof(coord_t), 1, file);
|
::fread(&y, sizeof(coord_t), 1, file);
|
||||||
poly.points.emplace_back(Point(x * scale, y * scale));
|
poly.points.emplace_back(Point(x * scale, y * scale));
|
||||||
}
|
}
|
||||||
|
printf("Polygon %d, area: %lf\n", i, area(poly.points));
|
||||||
if (which == -1 || which == i)
|
if (which == -1 || which == i)
|
||||||
m_support_polygons_deserialized.emplace_back(std::move(poly));
|
m_support_polygons_deserialized.emplace_back(std::move(poly));
|
||||||
printf("Polygon %d, area: %lf\n", i, area(poly.points));
|
|
||||||
}
|
}
|
||||||
::fread(&n_polygons, 4, 1, file);
|
::fread(&n_polygons, 4, 1, file);
|
||||||
m_trimming_polygons_deserialized.reserve(n_polygons);
|
m_trimming_polygons_deserialized.reserve(n_polygons);
|
||||||
|
|||||||
@@ -891,11 +891,41 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
|
|||||||
if (is_auto(stype) && config_detect_sharp_tails)
|
if (is_auto(stype) && config_detect_sharp_tails)
|
||||||
{
|
{
|
||||||
// BBS detect sharp tail
|
// BBS detect sharp tail
|
||||||
|
// Each island is tested only against the lower islands whose box meets its own; overlaps() tries
|
||||||
|
// every pair, which is quadratic in the island counts of the two layers.
|
||||||
|
std::vector<BoundingBox> lower_bboxes;
|
||||||
|
lower_bboxes.reserve(lower_polys.size());
|
||||||
|
for (const ExPolygon &lower : lower_polys)
|
||||||
|
lower_bboxes.emplace_back(get_extents(lower));
|
||||||
for (const ExPolygon& expoly : curr_polys) {
|
for (const ExPolygon& expoly : curr_polys) {
|
||||||
bool is_sharp_tail = false;
|
bool is_sharp_tail = false;
|
||||||
// 1. nothing below
|
// 1. nothing below
|
||||||
// this is a sharp tail region if it's floating and non-ignorable
|
// this is a sharp tail region if it's floating and non-ignorable
|
||||||
if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) {
|
const ExPolygons expanded = offset_ex(expoly, 0.1 * extrusion_width_scaled);
|
||||||
|
const BoundingBox bbox = get_extents(expanded);
|
||||||
|
ExPolygons lower_nearby;
|
||||||
|
for (size_t i = 0; i < lower_polys.size(); ++i)
|
||||||
|
if (lower_bboxes[i].overlap(bbox))
|
||||||
|
lower_nearby.emplace_back(lower_polys[i]);
|
||||||
|
// As overlaps(expanded, lower_nearby), with each lower island cut to the island's box first:
|
||||||
|
// below a fine relief the lower layer is a few islands with thousands of holes, and the whole
|
||||||
|
// of that boundary would otherwise be intersected once per island above.
|
||||||
|
const auto overlaps_nearby = [&]() {
|
||||||
|
for (const ExPolygon &a : expanded) {
|
||||||
|
if (a.empty())
|
||||||
|
continue;
|
||||||
|
const BoundingBox a_bbox = get_extents(a);
|
||||||
|
for (const ExPolygon &b : lower_nearby) {
|
||||||
|
if (b.empty() || !get_extents(b).overlap(a_bbox))
|
||||||
|
continue;
|
||||||
|
const Polygons b_near = ClipperUtils::clip_clipper_polygons_with_subject_bbox(b, a_bbox.inflated(SCALED_EPSILON));
|
||||||
|
if (!intersection_pl(to_polylines(b_near), a).empty() || b.contains(a.contour.points.front()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (!overlaps_nearby()) {
|
||||||
is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
|
is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public:
|
|||||||
thickness(other.thickness), thickness_layers(other.thickness_layers),
|
thickness(other.thickness), thickness_layers(other.thickness_layers),
|
||||||
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
|
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
|
||||||
{};
|
{};
|
||||||
Surface(Surface &&rhs)
|
Surface(Surface &&rhs) noexcept
|
||||||
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
|
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
|
||||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
|
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
|
||||||
@@ -95,7 +95,7 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface& operator=(Surface &&rhs)
|
Surface& operator=(Surface &&rhs) noexcept
|
||||||
{
|
{
|
||||||
surface_type = rhs.surface_type;
|
surface_type = rhs.surface_type;
|
||||||
expolygon = std::move(rhs.expolygon);
|
expolygon = std::move(rhs.expolygon);
|
||||||
|
|||||||
@@ -168,10 +168,10 @@ inline void append(std::vector<T, Alloc> &dest, std::vector<T, Alloc> &&src)
|
|||||||
{
|
{
|
||||||
if (dest.empty())
|
if (dest.empty())
|
||||||
dest = std::move(src);
|
dest = std::move(src);
|
||||||
else {
|
else
|
||||||
dest.reserve(dest.size() + src.size());
|
// insert() grows the capacity geometrically; reserving exactly the new size reallocated on every call, which
|
||||||
std::move(std::begin(src), std::end(src), std::back_inserter(dest));
|
// made appending piece by piece quadratic.
|
||||||
}
|
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
|
||||||
src.clear();
|
src.clear();
|
||||||
src.shrink_to_fit();
|
src.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,8 @@ class MainFrame : public DPIFrame
|
|||||||
std::string m_name{ "prepare_layout" };
|
std::string m_name{ "prepare_layout" };
|
||||||
wxSize m_laid_out_size;
|
wxSize m_laid_out_size;
|
||||||
} m_prepare_layout_prebuild{ *this };
|
} m_prepare_layout_prebuild{ *this };
|
||||||
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
// Every built-in LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
||||||
|
// Plugin pages stay out: PluginPages destroys them at runtime.
|
||||||
std::vector<LazyBase*> m_lazy_pages;
|
std::vector<LazyBase*> m_lazy_pages;
|
||||||
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
|
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
|
||||||
wxString m_printer_url;
|
wxString m_printer_url;
|
||||||
|
|||||||
@@ -119,33 +119,13 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
|
|||||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
// Create the webview
|
// Create the webview
|
||||||
m_browser = WebView::CreateWebView(this, "");
|
create_browser();
|
||||||
if (m_browser == nullptr) {
|
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||||
wxLogError("Could not init m_browser");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
inject_vue_resize_workaround(m_browser);
|
|
||||||
|
|
||||||
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
|
|
||||||
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
|
|
||||||
auto wv_ctx = webkit_web_view_get_context(wv);
|
|
||||||
auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx);
|
|
||||||
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
|
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
|
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
|
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
|
|
||||||
|
|
||||||
SetSizer(topsizer);
|
SetSizer(topsizer);
|
||||||
|
|
||||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
|
|
||||||
update_mode();
|
|
||||||
|
|
||||||
// Log backend information
|
// Log backend information
|
||||||
/* m_browser->GetUserAgent() may lead crash
|
/* m_browser->GetUserAgent() may lead crash
|
||||||
if (wxGetApp().get_mode() == comDevelop) {
|
if (wxGetApp().get_mode() == comDevelop) {
|
||||||
@@ -177,12 +157,44 @@ PrinterWebView::~PrinterWebView()
|
|||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrinterWebView::create_browser()
|
||||||
|
{
|
||||||
|
m_browser = WebView::CreateWebView(this, "");
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
inject_vue_resize_workaround(m_browser);
|
||||||
|
|
||||||
|
auto cookiesPath = boost::filesystem::path(data_dir() + "/cache/cookies.db");
|
||||||
|
auto wv = static_cast<WebKitWebView*>(m_browser->GetNativeBackend());
|
||||||
|
auto wv_ctx = webkit_web_view_get_context(wv);
|
||||||
|
auto cookieManager = webkit_web_context_get_cookie_manager(wv_ctx);
|
||||||
|
webkit_cookie_manager_set_persistent_storage(cookieManager, cookiesPath.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
|
||||||
|
update_mode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrinterWebView::reset_browser()
|
||||||
|
{
|
||||||
|
m_browser->Destroy(); // also removes it from the sizer
|
||||||
|
create_browser();
|
||||||
|
GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
|
Layout();
|
||||||
|
|
||||||
|
// OnLoaded may have cleared m_url_deferred already, so requeue the last url for Show().
|
||||||
|
m_apikey_sent = false;
|
||||||
|
m_url_deferred = m_url;
|
||||||
|
}
|
||||||
|
|
||||||
void PrinterWebView::load_url(wxString& url, wxString apikey)
|
void PrinterWebView::load_url(wxString& url, wxString apikey)
|
||||||
{
|
{
|
||||||
// this->Show();
|
// this->Show();
|
||||||
// this->Raise();
|
// this->Raise();
|
||||||
if (m_browser == nullptr)
|
m_url = url;
|
||||||
return;
|
|
||||||
m_apikey = apikey;
|
m_apikey = apikey;
|
||||||
m_apikey_sent = false;
|
m_apikey_sent = false;
|
||||||
m_handler = create_printer_webview_handler(*this);
|
m_handler = create_printer_webview_handler(*this);
|
||||||
@@ -200,6 +212,8 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
|
|||||||
|
|
||||||
bool PrinterWebView::Show(bool show)
|
bool PrinterWebView::Show(bool show)
|
||||||
{
|
{
|
||||||
|
if (show && std::exchange(m_reset_on_show, false))
|
||||||
|
reset_browser();
|
||||||
if (show && !m_url_deferred.empty()) {
|
if (show && !m_url_deferred.empty()) {
|
||||||
m_browser->LoadURL(m_url_deferred);
|
m_browser->LoadURL(m_url_deferred);
|
||||||
//ORCA: m_url_deferred will be cleared on load success
|
//ORCA: m_url_deferred will be cleared on load success
|
||||||
|
|||||||
@@ -58,13 +58,18 @@ private:
|
|||||||
friend class PrinterWebViewHandler;
|
friend class PrinterWebViewHandler;
|
||||||
|
|
||||||
void SendAPIKey();
|
void SendAPIKey();
|
||||||
|
void create_browser();
|
||||||
|
void reset_browser();
|
||||||
|
|
||||||
wxWebView* m_browser;
|
wxWebView* m_browser;
|
||||||
long m_zoomFactor;
|
long m_zoomFactor;
|
||||||
wxString m_apikey;
|
wxString m_apikey;
|
||||||
bool m_apikey_sent;
|
bool m_apikey_sent;
|
||||||
|
// Last url passed to load_url(), reloaded after reset_browser().
|
||||||
|
wxString m_url;
|
||||||
wxString m_url_deferred;
|
wxString m_url_deferred;
|
||||||
std::unique_ptr<PrinterWebViewHandler> m_handler;
|
std::unique_ptr<PrinterWebViewHandler> m_handler;
|
||||||
|
bool m_reset_on_show{false};
|
||||||
|
|
||||||
// DECLARE_EVENT_TABLE()
|
// DECLARE_EVENT_TABLE()
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-11
@@ -80,16 +80,10 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
|||||||
|
|
||||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
m_browser = WebView::CreateWebView(this, m_project_home_url);
|
create_browser();
|
||||||
if (m_browser == nullptr) {
|
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("load web view of project page failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//m_browser->Hide();
|
//m_browser->Hide();
|
||||||
main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
main_sizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
|
|
||||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
|
|
||||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this, m_browser->GetId());
|
|
||||||
|
|
||||||
Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this);
|
Bind(EVT_PROJECT_RELOAD, &ProjectPanel::on_reload, this);
|
||||||
|
|
||||||
@@ -117,6 +111,23 @@ void ProjectPanel::shutdown()
|
|||||||
m_reload_task.reset();
|
m_reload_task.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectPanel::create_browser()
|
||||||
|
{
|
||||||
|
m_browser = WebView::CreateWebView(this, m_project_home_url);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATED, &ProjectPanel::on_navigated, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ProjectPanel::OnScriptMessage, this, m_browser->GetId());
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_NAVIGATING, &ProjectPanel::onWebNavigating, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectPanel::reset_browser()
|
||||||
|
{
|
||||||
|
m_browser->Destroy(); // also removes it from the sizer
|
||||||
|
create_browser();
|
||||||
|
GetSizer()->Insert(0, m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
|
Layout();
|
||||||
|
m_web_init_completed.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to convert newlines to <br>
|
// Helper to convert newlines to <br>
|
||||||
static std::string convert_newlines_to_br(const std::string& text) {
|
static std::string convert_newlines_to_br(const std::string& text) {
|
||||||
std::string result = text;
|
std::string result = text;
|
||||||
@@ -281,12 +292,13 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
|||||||
|
|
||||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||||
|
|
||||||
if (m_web_init_completed.load(std::memory_order_acquire) &&
|
if (!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
|
||||||
!cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
|
|
||||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
return;
|
return;
|
||||||
RunScript(strJS.ToStdString());
|
m_info_script = strJS.ToStdString();
|
||||||
|
if (m_web_init_completed.load(std::memory_order_acquire))
|
||||||
|
RunScript(m_info_script);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -327,6 +339,11 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
|||||||
}
|
}
|
||||||
else if (strCmd == "request_3mf_info") {
|
else if (strCmd == "request_3mf_info") {
|
||||||
m_web_init_completed.store(true, std::memory_order_release);
|
m_web_init_completed.store(true, std::memory_order_release);
|
||||||
|
// Replay the stored info after each page load.
|
||||||
|
CallAfter([this] {
|
||||||
|
if (!m_info_script.empty())
|
||||||
|
RunScript(m_info_script);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (strCmd == "edit_project_info") {
|
else if (strCmd == "edit_project_info") {
|
||||||
show_info_editor(true);
|
show_info_editor(true);
|
||||||
@@ -383,6 +400,8 @@ void ProjectPanel::clear_model_info()
|
|||||||
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
wxGetApp().CallAfter([this, cancel_token, strJS] {
|
||||||
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
return;
|
return;
|
||||||
|
// Runs after any store queued by an earlier reload pass, so stale info is never replayed.
|
||||||
|
m_info_script.clear();
|
||||||
RunScript(strJS.ToStdString());
|
RunScript(strJS.ToStdString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -525,6 +544,8 @@ void ProjectPanel::RunScript(std::string content)
|
|||||||
|
|
||||||
bool ProjectPanel::Show(bool show)
|
bool ProjectPanel::Show(bool show)
|
||||||
{
|
{
|
||||||
|
if (show && std::exchange(m_reset_on_show, false))
|
||||||
|
reset_browser();
|
||||||
if (show) update_model_data();
|
if (show) update_model_data();
|
||||||
return wxPanel::Show(show);
|
return wxPanel::Show(show);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,9 +80,14 @@ private:
|
|||||||
AuxiliaryPanel* m_auxiliary{nullptr};
|
AuxiliaryPanel* m_auxiliary{nullptr};
|
||||||
wxString m_project_home_url;
|
wxString m_project_home_url;
|
||||||
wxString m_root_dir;
|
wxString m_root_dir;
|
||||||
|
// Last show_3mf_info script, also sent whenever the page asks for it.
|
||||||
|
std::string m_info_script;
|
||||||
|
bool m_reset_on_show{false};
|
||||||
static inline std::atomic<int> m_sequence_id{8000};
|
static inline std::atomic<int> m_sequence_id{8000};
|
||||||
|
|
||||||
void show_info_editor(bool show);
|
void show_info_editor(bool show);
|
||||||
|
void create_browser();
|
||||||
|
void reset_browser();
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ namespace GUI {
|
|||||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||||
{
|
{
|
||||||
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
m_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||||
wxString strlang = wxGetApp().current_language_code_safe();
|
wxString strlang = wxGetApp().current_language_code_safe();
|
||||||
if (strlang != "")
|
if (strlang != "")
|
||||||
url += "?lang=" + strlang;
|
m_home_url += "?lang=" + strlang;
|
||||||
|
|
||||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
@@ -109,12 +109,8 @@ WebViewPanel::WebViewPanel(wxWindow *parent)
|
|||||||
m_info = new wxInfoBar(this);
|
m_info = new wxInfoBar(this);
|
||||||
topsizer->Add(m_info, wxSizerFlags().Expand());
|
topsizer->Add(m_info, wxSizerFlags().Expand());
|
||||||
// Create the webview
|
// Create the webview
|
||||||
m_browser = WebView::CreateWebView(this, url);
|
create_browser();
|
||||||
if (m_browser == nullptr) {
|
m_reset_on_show = WebView::NeedsRecreateOnShow();
|
||||||
wxLogError("Could not init m_browser");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_browser->Hide();
|
|
||||||
SetSizer(topsizer);
|
SetSizer(topsizer);
|
||||||
|
|
||||||
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
@@ -264,6 +260,27 @@ WebViewPanel::~WebViewPanel()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WebViewPanel::create_browser()
|
||||||
|
{
|
||||||
|
m_browser = WebView::CreateWebView(this, m_home_url);
|
||||||
|
m_browser->Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebViewPanel::reset_browser()
|
||||||
|
{
|
||||||
|
m_browser->Destroy(); // also removes it from the sizer
|
||||||
|
create_browser();
|
||||||
|
GetSizer()->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
|
Layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebViewPanel::Show(bool show)
|
||||||
|
{
|
||||||
|
if (show && std::exchange(m_reset_on_show, false))
|
||||||
|
reset_browser();
|
||||||
|
return wxPanel::Show(show);
|
||||||
|
}
|
||||||
|
|
||||||
void WebViewPanel::load_url(wxString& url)
|
void WebViewPanel::load_url(wxString& url)
|
||||||
{
|
{
|
||||||
this->Show();
|
this->Show();
|
||||||
|
|||||||
@@ -113,9 +113,15 @@ public:
|
|||||||
int get_model_mall_detail_url(std::string *url, std::string id);
|
int get_model_mall_detail_url(std::string *url, std::string id);
|
||||||
|
|
||||||
void update_mode();
|
void update_mode();
|
||||||
|
|
||||||
|
bool Show(bool show = true) override;
|
||||||
private:
|
private:
|
||||||
|
void create_browser();
|
||||||
|
void reset_browser();
|
||||||
|
|
||||||
wxWebView* m_browser;
|
wxWebView* m_browser;
|
||||||
|
wxString m_home_url;
|
||||||
|
bool m_reset_on_show{false};
|
||||||
wxButton * m_button_stop;
|
wxButton * m_button_stop;
|
||||||
wxTextCtrl *m_url;
|
wxTextCtrl *m_url;
|
||||||
#if !BBL_RELEASE_TO_PUBLIC
|
#if !BBL_RELEASE_TO_PUBLIC
|
||||||
|
|||||||
@@ -381,6 +381,17 @@ void WebView::MarkScriptMessageHandlerAdded(wxWebView * webView)
|
|||||||
if (WebViewRef *ref = webview_ref(webView))
|
if (WebViewRef *ref = webview_ref(webView))
|
||||||
ref->m_script_handler_added = true;
|
ref->m_script_handler_added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebView::NeedsRecreateOnShow()
|
||||||
|
{
|
||||||
|
const bool recreating = Slic3r::GUI::wxGetApp().is_recreating_gui();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": is_recreating_gui = " << recreating;
|
||||||
|
#ifdef __WIN32__
|
||||||
|
return recreating;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#if wxUSE_WEBVIEW_EDGE
|
#if wxUSE_WEBVIEW_EDGE
|
||||||
bool WebView::CheckWebViewRuntime()
|
bool WebView::CheckWebViewRuntime()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ public:
|
|||||||
// Marks "wx" as registered so CreateWebView's deferred add skips the duplicate.
|
// Marks "wx" as registered so CreateWebView's deferred add skips the duplicate.
|
||||||
static void MarkScriptMessageHandlerAdded(wxWebView * webView);
|
static void MarkScriptMessageHandlerAdded(wxWebView * webView);
|
||||||
|
|
||||||
|
// On Windows, a WebView2 backend created during a GUI rebuild (language switch) can come up
|
||||||
|
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
||||||
|
static bool NeedsRecreateOnShow();
|
||||||
|
|
||||||
static void RecreateAll();
|
static void RecreateAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "PluginPages.hpp"
|
#include "PluginPages.hpp"
|
||||||
|
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/Notebook.hpp"
|
#include "slic3r/GUI/Notebook.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||||
@@ -183,8 +184,10 @@ void PluginPages::initialize(Notebook* parent)
|
|||||||
|
|
||||||
void PluginPages::shutdown()
|
void PluginPages::shutdown()
|
||||||
{
|
{
|
||||||
while (!m_pages.empty())
|
// Removing the selected tab selects the tab to its left. In tab order that is a built-in tab,
|
||||||
remove_page(m_pages.begin()->first);
|
// never an unbuilt plugin page that is removed next and would be built only to be destroyed.
|
||||||
|
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||||
|
remove_page(id);
|
||||||
m_parent = nullptr;
|
m_parent = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +228,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
|||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* page = new PluginPage(m_parent, std::move(capability));
|
wxBitmap bitmap;
|
||||||
|
|
||||||
if (!icon.empty()) {
|
if (!icon.empty()) {
|
||||||
try {
|
try {
|
||||||
boost::filesystem::path icon_path(icon);
|
boost::filesystem::path icon_path(icon);
|
||||||
@@ -234,7 +236,7 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
|||||||
if (extension == ".svg" || extension == ".png")
|
if (extension == ".svg" || extension == ".png")
|
||||||
icon_path.replace_extension();
|
icon_path.replace_extension();
|
||||||
|
|
||||||
page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20));
|
bitmap = create_scaled_bitmap(icon_path.string(), m_parent, 20);
|
||||||
} catch (const std::exception& error) {
|
} catch (const std::exception& error) {
|
||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -242,7 +244,15 @@ bool PluginPages::create_page(const PluginCapabilityId& id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pages.emplace(id, page);
|
// Built the first time its tab is shown, so a language switch never creates the page's browser
|
||||||
|
// while the main window is being rebuilt. Never prebuilt: the idle queue cannot drop a page
|
||||||
|
// that remove_page() destroys.
|
||||||
|
auto* page = new GUI::LazyPage<PluginPage>(m_parent, GUI::into_u8(page_tab_id(id)), -1,
|
||||||
|
[capability = std::move(capability)](wxWindow* parent) {
|
||||||
|
return new PluginPage(parent, capability);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_pages.emplace(id, Page{page, bitmap});
|
||||||
m_order.push_back(id);
|
m_order.push_back(id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -271,17 +281,11 @@ void PluginPages::on_plugin_register(const std::string& plugin_key)
|
|||||||
|
|
||||||
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
|
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
|
||||||
{
|
{
|
||||||
for (auto it = m_pages.begin(); it != m_pages.end();) {
|
// In tab order, as in shutdown().
|
||||||
if (it->first.plugin_key != plugin_key) {
|
for (const PluginCapabilityId& id : std::vector<PluginCapabilityId>(m_order))
|
||||||
++it;
|
if (id.plugin_key == plugin_key)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PluginCapabilityId id = it->first;
|
|
||||||
++it;
|
|
||||||
remove_page(id);
|
remove_page(id);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void PluginPages::remove_page(const PluginCapabilityId& id)
|
void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||||
{
|
{
|
||||||
@@ -289,8 +293,10 @@ void PluginPages::remove_page(const PluginCapabilityId& id)
|
|||||||
if (it == m_pages.end())
|
if (it == m_pages.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PluginPage* page = it->second;
|
GUI::LazyPage<PluginPage>* page = it->second.page;
|
||||||
page->detach_capability();
|
// Only a built page has installed a message sender on the capability.
|
||||||
|
if (PluginPage* built = page->get())
|
||||||
|
built->detach_capability();
|
||||||
|
|
||||||
m_pages.erase(it);
|
m_pages.erase(it);
|
||||||
m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end());
|
m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end());
|
||||||
@@ -348,26 +354,27 @@ void PluginPages::relayout()
|
|||||||
bool up_to_date = page_count >= tab_ids.size();
|
bool up_to_date = page_count >= tab_ids.size();
|
||||||
for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i)
|
for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i)
|
||||||
up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]);
|
up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]);
|
||||||
for (const auto& [id, page] : m_pages) {
|
for (const auto& [id, entry] : m_pages) {
|
||||||
if (!up_to_date)
|
if (!up_to_date)
|
||||||
break;
|
break;
|
||||||
const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end();
|
const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end();
|
||||||
up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted;
|
up_to_date = entry.page->in_book() == wanted;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!up_to_date) {
|
if (!up_to_date) {
|
||||||
const wxString id_to_reselect = m_parent->GetSelectedPageName();
|
const wxString id_to_reselect = m_parent->GetSelectedPageName();
|
||||||
|
|
||||||
for (const auto& [id, page] : m_pages) {
|
// In tab order, as in shutdown().
|
||||||
const int idx = m_parent->FindPage(page);
|
for (const auto& id : m_order) {
|
||||||
|
const int idx = m_parent->FindPage(m_pages.at(id).page);
|
||||||
if (idx != wxNOT_FOUND)
|
if (idx != wxNOT_FOUND)
|
||||||
m_parent->RemovePage(idx);
|
m_parent->RemovePage(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& id : tab_ids) {
|
for (const auto& id : tab_ids) {
|
||||||
PluginPage* page = m_pages.at(id);
|
const Page& entry = m_pages.at(id);
|
||||||
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), "",
|
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), entry.page, wxString::FromUTF8(id.name), "",
|
||||||
false, page->icon());
|
false, entry.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!id_to_reselect.empty())
|
if (!id_to_reselect.empty())
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <slic3r/GUI/LazyPage.hpp>
|
||||||
#include <slic3r/GUI/WebPanel.hpp>
|
#include <slic3r/GUI/WebPanel.hpp>
|
||||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||||
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
|
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
|
||||||
@@ -29,8 +30,6 @@ public:
|
|||||||
|
|
||||||
void detach_capability();
|
void detach_capability();
|
||||||
void push_message(const std::string& message);
|
void push_message(const std::string& message);
|
||||||
void set_icon(const wxBitmap& icon) { m_icon = icon; }
|
|
||||||
const wxBitmap& icon() const { return m_icon; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::optional<std::string> page_html() override;
|
std::optional<std::string> page_html() override;
|
||||||
@@ -41,7 +40,6 @@ private:
|
|||||||
|
|
||||||
std::shared_ptr<PagesPluginCapability> m_cap;
|
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||||
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||||
wxBitmap m_icon;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PluginPages
|
class PluginPages
|
||||||
@@ -73,7 +71,13 @@ private:
|
|||||||
void show_overflow_menu();
|
void show_overflow_menu();
|
||||||
static wxString page_tab_id(const PluginCapabilityId& id);
|
static wxString page_tab_id(const PluginCapabilityId& id);
|
||||||
|
|
||||||
std::map<PluginCapabilityId, PluginPage*> m_pages;
|
struct Page
|
||||||
|
{
|
||||||
|
GUI::LazyPage<PluginPage>* page{nullptr};
|
||||||
|
wxBitmap icon;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<PluginCapabilityId, Page> m_pages;
|
||||||
std::vector<PluginCapabilityId> m_order;
|
std::vector<PluginCapabilityId> m_order;
|
||||||
Notebook* m_parent{nullptr};
|
Notebook* m_parent{nullptr};
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_filament_mixer.cpp
|
test_filament_mixer.cpp
|
||||||
test_fill_plane_path.cpp
|
test_fill_plane_path.cpp
|
||||||
test_geometry.cpp
|
test_geometry.cpp
|
||||||
|
test_kdtree.cpp
|
||||||
test_multimaterial_segmentation.cpp
|
test_multimaterial_segmentation.cpp
|
||||||
test_placeholder_parser.cpp
|
test_placeholder_parser.cpp
|
||||||
test_polygon.cpp
|
test_polygon.cpp
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -299,3 +300,90 @@ TEST_CASE("Top level expolygons of an even-odd union", "[ClipperUtils]") {
|
|||||||
REQUIRE(area_sum == Catch::Approx(area(top_level) + area(nested)));
|
REQUIRE(area_sum == Catch::Approx(area(top_level) + area(nested)));
|
||||||
REQUIRE(top_level_expolygons(reference).size() == 1);
|
REQUIRE(top_level_expolygons(reference).size() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rings flattened to x,y,x,y... and sorted, with each ring rotated to start at its lowest point: two
|
||||||
|
// encodings of the same geometry compare equal however the pieces came back or wherever a ring started.
|
||||||
|
static std::vector<std::vector<coord_t>> canonical_rings(const ExPolygons &expolygons)
|
||||||
|
{
|
||||||
|
std::vector<std::vector<coord_t>> rings;
|
||||||
|
const auto add = [&rings](const Polygon &poly) {
|
||||||
|
if (poly.points.empty())
|
||||||
|
return;
|
||||||
|
Points pts = poly.points;
|
||||||
|
std::rotate(pts.begin(),
|
||||||
|
std::min_element(pts.begin(), pts.end(), [](const Point &a, const Point &b) {
|
||||||
|
return std::make_pair(a.x(), a.y()) < std::make_pair(b.x(), b.y());
|
||||||
|
}),
|
||||||
|
pts.end());
|
||||||
|
std::vector<coord_t> flat;
|
||||||
|
flat.reserve(pts.size() * 2);
|
||||||
|
for (const Point &p : pts) {
|
||||||
|
flat.emplace_back(p.x());
|
||||||
|
flat.emplace_back(p.y());
|
||||||
|
}
|
||||||
|
rings.emplace_back(std::move(flat));
|
||||||
|
};
|
||||||
|
for (const ExPolygon &expoly : expolygons) {
|
||||||
|
add(expoly.contour);
|
||||||
|
for (const Polygon &hole : expoly.holes)
|
||||||
|
add(hole);
|
||||||
|
}
|
||||||
|
std::sort(rings.begin(), rings.end());
|
||||||
|
return rings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same rings, every coordinate within `tolerance`.
|
||||||
|
static bool same_rings(const ExPolygons &a, const ExPolygons &b, coord_t tolerance)
|
||||||
|
{
|
||||||
|
const std::vector<std::vector<coord_t>> ra = canonical_rings(a), rb = canonical_rings(b);
|
||||||
|
return std::equal(ra.begin(), ra.end(), rb.begin(), rb.end(), [tolerance](const std::vector<coord_t> &x, const std::vector<coord_t> &y) {
|
||||||
|
return std::equal(x.begin(), x.end(), y.begin(), y.end(), [tolerance](coord_t u, coord_t v) { return std::abs(u - v) <= tolerance; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Tiled diff and intersection return the same polygons as the plain calls", "[ClipperUtils]") {
|
||||||
|
// A grid of disjoint framed squares, enough of them to be split into several tiles.
|
||||||
|
const int n = 40;
|
||||||
|
const coord_t cell = scaled<coord_t>(2.), side = scaled<coord_t>(1.5), frame = scaled<coord_t>(0.3);
|
||||||
|
ExPolygons subject;
|
||||||
|
for (int y = 0; y < n; ++ y)
|
||||||
|
for (int x = 0; x < n; ++ x) {
|
||||||
|
const Point o(x * cell, y * cell);
|
||||||
|
ExPolygon square(Polygon({ o, o + Point(side, 0), o + Point(side, side), o + Point(0, side) }));
|
||||||
|
Polygon hole({ o + Point(frame, frame), o + Point(frame, side - frame), o + Point(side - frame, side - frame), o + Point(side - frame, frame) });
|
||||||
|
square.holes.emplace_back(std::move(hole));
|
||||||
|
subject.emplace_back(std::move(square));
|
||||||
|
}
|
||||||
|
// Clip polygons crossing many squares, one of them large with holes of its own.
|
||||||
|
Polygons clip;
|
||||||
|
const coord_t span = n * cell;
|
||||||
|
for (int i = 0; i < 8; ++ i) {
|
||||||
|
const coord_t y0 = coord_t(i) * span / 8, y1 = y0 + scaled<coord_t>(0.9);
|
||||||
|
clip.emplace_back(Polygon({ Point(- cell, y0), Point(span, y0 + cell * 3), Point(span, y1 + cell * 3), Point(- cell, y1) }));
|
||||||
|
}
|
||||||
|
ExPolygon big(Polygon({ Point(span / 4, span / 4), Point(3 * span / 4, span / 4), Point(3 * span / 4, 3 * span / 4), Point(span / 4, 3 * span / 4) }));
|
||||||
|
for (int i = 0; i < 4; ++ i) {
|
||||||
|
const Point o(span / 4 + scaled<coord_t>(3.1) + i * scaled<coord_t>(9.7), span / 4 + scaled<coord_t>(5.3));
|
||||||
|
big.holes.emplace_back(Polygon({ o, o + Point(0, scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), 0) }));
|
||||||
|
}
|
||||||
|
polygons_append(clip, to_polygons(big));
|
||||||
|
|
||||||
|
const ApplySafetyOffset safety = GENERATE(ApplySafetyOffset::No, ApplySafetyOffset::Yes);
|
||||||
|
|
||||||
|
// The point of the fixture: below 128 pieces the helpers fall back to a single tile and the tiled
|
||||||
|
// path under test is never taken.
|
||||||
|
REQUIRE(ClipperUtils::tile_expolygons(subject, 32).size() > 1);
|
||||||
|
|
||||||
|
// With the safety offset a tile unites fewer clip polygons, so Clipper2 can round a crossing 1 unit differently.
|
||||||
|
const coord_t tolerance = safety == ApplySafetyOffset::Yes ? 1 : 0;
|
||||||
|
|
||||||
|
const ExPolygons diff_plain = diff_ex(subject, clip, safety);
|
||||||
|
const ExPolygons diff_tiled = diff_ex_by_piece(subject, clip, safety);
|
||||||
|
REQUIRE(area(diff_plain) > 0.);
|
||||||
|
CHECK(same_rings(diff_tiled, diff_plain, tolerance));
|
||||||
|
|
||||||
|
const ExPolygons intersection_plain = intersection_ex(subject, clip, safety);
|
||||||
|
const ExPolygons intersection_tiled = intersection_ex_by_piece(subject, clip, safety);
|
||||||
|
REQUIRE(area(intersection_plain) > 0.);
|
||||||
|
CHECK(same_rings(intersection_tiled, intersection_plain, tolerance));
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "libslic3r/KDTreeIndirect.hpp"
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
TEST_CASE("Visiting the nearby points gives what collecting them gives", "[KDTree]") {
|
||||||
|
std::mt19937 rng(19937);
|
||||||
|
std::uniform_real_distribution<float> coord(-50.f, 50.f);
|
||||||
|
// Points in a box, so that a radius search returns anything from none of them to all of them.
|
||||||
|
std::vector<Vec3f> points(2000);
|
||||||
|
for (Vec3f &p : points)
|
||||||
|
p = Vec3f(coord(rng), coord(rng), coord(rng));
|
||||||
|
|
||||||
|
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
|
||||||
|
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
|
||||||
|
std::vector<size_t> indices(points.size());
|
||||||
|
std::iota(indices.begin(), indices.end(), 0);
|
||||||
|
tree.build(indices);
|
||||||
|
|
||||||
|
const float radius = GENERATE(0.5f, 5.f, 25.f, 200.f);
|
||||||
|
for (int i = 0; i < 20; ++ i) {
|
||||||
|
const Vec3f center(coord(rng), coord(rng), coord(rng));
|
||||||
|
|
||||||
|
const std::vector<size_t> collected = find_nearby_points(tree, center, radius);
|
||||||
|
std::vector<size_t> visited;
|
||||||
|
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
|
||||||
|
|
||||||
|
// Same points, and in the same order: a caller that keeps the first of several equally good ones
|
||||||
|
// must get the same answer either way.
|
||||||
|
REQUIRE(visited == collected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A radius search returns every point within the radius and no other", "[KDTree]") {
|
||||||
|
std::mt19937 rng(2024);
|
||||||
|
std::uniform_real_distribution<float> coord(-20.f, 20.f);
|
||||||
|
std::vector<Vec3f> points(500);
|
||||||
|
for (Vec3f &p : points)
|
||||||
|
p = Vec3f(coord(rng), coord(rng), coord(rng));
|
||||||
|
|
||||||
|
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
|
||||||
|
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
|
||||||
|
std::vector<size_t> indices(points.size());
|
||||||
|
std::iota(indices.begin(), indices.end(), 0);
|
||||||
|
tree.build(indices);
|
||||||
|
|
||||||
|
const Vec3f center(1.f, -2.f, 3.f);
|
||||||
|
const float radius = 7.f;
|
||||||
|
|
||||||
|
std::vector<size_t> expected;
|
||||||
|
for (size_t i = 0; i < points.size(); ++ i)
|
||||||
|
if ((points[i] - center).squaredNorm() < radius * radius)
|
||||||
|
expected.emplace_back(i);
|
||||||
|
|
||||||
|
std::vector<size_t> visited;
|
||||||
|
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
|
||||||
|
std::sort(visited.begin(), visited.end());
|
||||||
|
|
||||||
|
REQUIRE(! expected.empty());
|
||||||
|
REQUIRE(visited == expected);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user