Compare commits

...
Author SHA1 Message Date
a6dbf2502d Device tab blank for webui printers after switching language (#14547)
* Save device url in all cases and load printer url after hot-reload finishes

* Recreate web view from scratch as only URL fix seems not robust enough

* Add the same robust browser recreation for WebViewDialog

It should eliminate possible issue with blank Home and other pages
in the same way as Printer page

* Remove redundant fallback leftover

* Fix webview reset state and replay Project info on page reload

The first-show webview reset now runs only on Windows, reloads the last
printer URL and resets the Project page's ready state. The Project tab
replays its 3MF info whenever the page reloads, so it no longer goes
blank after a theme switch or a slow first load. The Device tab no longer
loads an extra time on first open, and the Home tab no longer navigates
twice. NeedsRecreateOnShow() logs is_recreating_gui so one language
switch shows whether the reset ever fires.

* Build plugin pages on first show

A language switch rebuilt every plugin page's browser while the main
window was being recreated, which left plugin tabs blank on Windows.
Plugin pages are now lazy pages, never prebuilt, and are removed left
to right so removing pages never builds one only to destroy it.

A plugin page's script now starts when its tab is first opened;
messages posted before that are dropped.

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
2026-10-03 20:06:50 +08:00
HanifKoh 8a6377f087 Add Missing Includes Across src/libslic3r (#16068)
* Add Missing Includes Across src/libslic3r

Every libslic3r source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, with libslic3r headers spelled libslic3r/... so they resolve outside the library's private include paths. MultiMaterialSegmentation.hpp, Support/SupportParameters.hpp and Format/STEP.hpp are made self-contained by hand.

* Make the libslic3r Headers Compile on Their Own

Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Left out: I18N.hpp, which errors on purpose when included from GUI code, and VoxelizeCSGMesh.hpp and SLA/bicubic.h, which nothing includes and which no longer compile at all.

* Add the Includes Missing From the Hand-Fixed libslic3r Headers

clang-tidy would not edit these headers while they failed to compile on their own, so the first pass skipped them. With the headers now self-contained, a second pass adds the rest.

* Keep Windows Setup Ahead of the Added libslic3r Includes

Print.cpp and Thread.cpp open with a _WIN32 block that has to come first; without the precompiled header, Print.cpp otherwise reaches windows.h through OCCT with NONLS defined and boost/regex fails. OpenVDBUtils.cpp and SLA/SupportTreeBuilder.cpp had includes inside #ifndef NOMINMAX, which libslic3r defines on Windows, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory.

* Re-Add libslic3r Includes After the Clipper2 2.0.1 Migration

Rebasing onto main took main's version of the files the Clipper2 migration rewrote, so their added includes are restored here, along with includes for main's new code. Clipper2's individual headers are now ignored by clang-tidy: they only build the Z variant through clipper2_z.hpp, which defines USINGZ first, so including clipper.core.h and the like directly broke ClipperZUtils.cpp.
2026-10-03 15:31:11 +08:00
SoftFever c86e33db6d Make the orca-wxwidgets skill find the wx source on Windows and in worktrees 2026-10-03 14:11:21 +08:00
HanifKoh 84657ff11e Add Missing Includes Across the Remaining Sources and Tests (#16071)
* Ignore Clipper, libpng, mcut and Boost.Polygon Internals in clang-tidy

Each only works through a wrapper or umbrella header: libslic3r/clipper.hpp or clipper_z.hpp configure Clipper before including it, png.h pulls in libpng's config headers, and Boost.Polygon's headers only compile through polygon.hpp or voronoi.hpp.

* Ignore minilzo's Config Headers in clang-tidy

lzoconf.h and lzodefs.h are internal to minilzo.h, which is what the code includes.

* Add Missing Includes Across the Remaining Sources and Tests

Covers src/slic3r/Utils, src/slic3r/plugin, src/slic3r/Config, src/libvgcode, src/dev-utils, src/OrcaSlicer.cpp and tests/, the directories left after src/slic3r/GUI and src/libslic3r. Generated with clang-tidy misc-include-cleaner. libvgcode's own headers are included by relative path as in the rest of that library, and Catch2 and pybind11 with angle brackets as elsewhere in the repo.

* Make the GUI and Test Headers Compile on Their Own

Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Headers that only compile on one platform, or that nothing built includes, are left alone.

* Keep Windows and nanosvg Setup Ahead of the Added Includes

OrcaSlicer.cpp and several tests set _WIN32_WINNT, WIN32_LEAN_AND_MEAN or NOMINMAX before including Windows.h, and the profile validator defines NANOSVG_IMPLEMENTATION before any libslic3r header. The added includes had landed above those blocks, which broke the Windows build.

* Add the GUI Includes the First Pass Missed

Covers headers that only became editable once they compiled on their own, and wx symbols whose suggested header changed as the clang-tidy ignore list grew after the src/slic3r/GUI pass.

* Keep the Added Test Includes Below the NOMINMAX Guard

test_marchingsquares.cpp and test_texture_displacement.cpp had includes inside #ifndef NOMINMAX, which the tests inherit as defined on Windows from libslic3r, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory, as in #16068.
2026-10-03 13:45:21 +08:00
Kiss Lorandandyw4z 00bb4202fe Fix gizmo checkbox contrast; align Texture Displacement styling and panel refresh behavior (#16076)
* Fix gizmo checkbox styling and Texture Displacement resize

Use shared BBL checkboxes in Texture Displacement and restore white toolbar checkmarks so other gizmos keep proper checkbox contrast. Also fix Texture Displacement resizing only after mouse movement by requesting additional frames while its layout is still changing.

* Fix gizmo checkbox styling and Texture Displacement resize

Use shared BBL checkboxes in Texture Displacement and restore white toolbar checkmarks so other gizmos keep proper checkbox contrast. Also fix Texture Displacement resizing only after mouse movement by requesting additional frames while its layout is still changing.

* Update GLGizmoTextureDisplacement.cpp

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-10-03 03:12:31 +03:00
Kiss Lorand 1241dd5521 Fix per-plate bed type handling (#15916) 2026-10-02 21:02:36 -03:00
Kris Austin eb30ea1eb8 perf: speed up G-code export by 3-9% via cached config lookups (#16028) 2026-10-02 19:34:47 -03:00
Ian BassiandRodrigo Faselli 222c6a2df5 Improve performance by migrating to Clipper2 2.0.1 (#15969)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-02 17:33:41 -03:00
Ian BassiandKris Austin 70bc02467b Faster Preview View (#15884)
Co-authored-by: Kris Austin <kris.austin@gmail.com>
2026-10-02 14:45:58 -03:00
Kris Austin 4ffba13210 ci: time out macOS notarization after 30 minutes (#16087)
notarytool submit --wait has no timeout. On 2026-10-02 it hung for
over 5 hours in a main build. Since #16044 a new push no longer
cancels a running main build, so nothing stopped it and six waiting
main runs were replaced without starting.

Over the last 30 days the step succeeded 206 times, with a median of
4.3 minutes and a maximum of 14.8.
2026-10-02 14:40:51 -03:00
Ian Bassi a1ad2b4425 Add section view feature for 3D canvas (#15879) 2026-10-02 11:50:43 -03:00
Noisyfox 6c6e8be43d Fix debug build cmake errors (#14593)
* Fix debug build after qhull upgrade

We upgraded qhull from 8.0.1 to 8.0.2 in 504a5d3b70, which contains a commit qhull/qhull@16159c648c `use same CMake target name for Debug and non-Debug`, so this target name check is no longer required

* Fix issue like `IMPORTED_LOCATION not set for imported target "opencv_world" configuration "RelWithDebInfo".` when build Debug config
2026-10-02 09:46:24 -03:00
HanifKohandSoftFever 205de9ce63 Keep User Preset Values on Extruder Variants They Don't List (#16046)
* Keep User Preset Values on Extruder Variants They Don't List

A user preset stores the variant list its parent had when it was saved.
When the parent later gains variants, update_diff_values_to_child_config
matched variants by name only and left the new ones at the parent's
value, so the user's settings were silently replaced there, and a
re-save wrote the system values into the user's file.

An unmatched parent variant now takes the child's first variant of the
same extruder, the rule slicing already uses in get_config_index_base.
A child without a variant list covers the parent's first extruder. The
name match also no longer indexes the child's extruder ids when it has
none.

* Share One Variant Column Rule Between Slicing, User Presets and Projects

Three places chose which variant column a value comes from, each with
its own copy of "the same variant and owner, else the owner's first
column": get_config_index_base when slicing, the user preset merge in
update_diff_values_to_child_config, and normalize_filament_values_to_variants
for projects and the CLI.

find_variant_column now holds that rule and map_variant_columns applies
it to a variant list, so a change to how missing variants are filled
reaches all three. Each caller keeps its own copy step. There is no
behaviour change: G-code is identical before and after. The one
relaxation is that get_config_index_base no longer reads past a short
id list when its two lists differ in length, which its assert already
rules out.

* Rename variant column helpers to variant index

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-02 20:13:06 +08:00
1255af1e9c Bundle uv in Windows ARM64 builds (#16070)
* fix: include bundled UV binary for arm64

* fix: update unit test CI

* Install unit-test numpy only with the bundled uv

* Simplify the unit-test script's uv lookup

---------

Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-02 19:37:29 +08:00
Kris Austin 8bf7b73141 ci: build Windows ARM64 with CMake 4.3 like the other platforms (#16052)
The ARM64 jobs pinned CMake 3.31 because CMake 4 dropped pre-3.5 policy
compatibility and its ARMASM support broke Boost.Context. Both are
handled now. deps/CMakeLists.txt sets CMAKE_POLICY_VERSION_MINIMUM on
CMake 4, and Boost.Context uses the winfib implementation on ARM64, so
nothing assembles with armasm.

CMake 3.31 also predates VS 2026. Its InstallRequiredSystemLibraries
treats the v145 toolset as v143, searches only the VS 2017-2022 install
directories and finds no runtime, so the ARM64 installer ships without
msvcp140.dll and vcruntime140.dll. CMake 4.2 and newer find the VC145
redistributable.

get-cmake also installs Ninja, so the ARM64 jobs now use its latest
release instead of the one already on the runner, as x64 does.

The install now fails when InstallRequiredSystemLibraries returns no
msvcp140.dll or vcruntime140.dll, after a configure warning naming the
CMake and MSVC versions. A CMake that predates the Visual Studio in use,
on a developer machine or after the next runner image update, then
stops the installer build instead of shipping one that cannot start.

The build_win.bat prerequisite installer drops its matching 3.31.8 pin.
2026-10-02 08:24:39 -03:00
SoftFever e63c6d0594 Fix Windows ARM64 builds crashing on every HTTPS connection (#16073) 2026-10-02 19:04:55 +08:00
SoftFever 79a89f817f Add a never-Raise-a-popup rule to the orca-wxwidgets skill 2026-10-02 18:16:15 +08:00
Eric McCann 83ee4f4476 U1: add HF nozzle variants and output flow type to avoid warnings on printer (#16043)
This is the bulk of the profile changes for U1 that led to the cooling
catiant connection.

The ugly end gcode is used by the printer's UI to complain if normal
nozzle is installed but the file was sliced for highflow.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-10-02 18:06:13 +08:00
HanifKoh 390b7d8e6d Make Painted Multi-Material Slicing Deterministic (#15899)
* Make Painted Multi-Material Slicing Deterministic

Painted (multi-material) models sliced to slightly different G-code on
every run: ±1 µm wall coordinates and reordered islands. Hashing each stage
of the segmentation across runs showed the projected painted lines and the
per-layer Voronoi segmentation were stable; the raw top/bottom projections
from slice_mesh_slabs() were not. Three causes, all thread-order dependent:

- slice_slabs_make_lines() appends each slab's intersection lines from a
  parallel facet loop and never restored a canonical order, so the loop
  start vertices and polygon order from make_slab_loops() depended on
  scheduling. Sort every slab's lines with the same key slice_make_lines()
  already uses.
- segmentation_top_and_bottom_layers() wrote a layer's shell projections
  into neighbouring layers' vectors from the parallel loop, relying on a
  parity double-buffer that assumes TBB ranges are exactly one group wide
  and aligned, which blocked_range does not guarantee; two threads could
  append to the same vector. Each source layer now records its projections
  in its own slot and they are gathered per target layer in source order.
- The painted-line sort in post_process_painted_lines() was not a total
  order: projections of one span from facets of different colours tied on
  every key and the first one won the span. Colour and end points now break
  the tie.

Three multi-threaded runs of each painted fixture now give one G-code;
unpainted output is unchanged.

* Test That Slab Slicing Does Not Depend on the Thread Schedule

Projects a dense, tilted sphere with slice_mesh_slabs() on one thread and
then three times multi-threaded, and requires the polygons to match exactly,
vertex order included. Fails without the canonical line sort, passes with it.
2026-10-02 16:51:06 +08:00
HanifKoh 1a5f91d727 Add Missing Includes Across src/slic3r/GUI (#16048)
* Add Missing Includes Across src/slic3r/GUI

Every GUI source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, plus one hand edit making CalibrationPanel.hpp self-contained.

* Drop the OS-Specific Includes Added Outside Their Platform Guards

GLib, GTK, D-Bus and POSIX headers are only used inside platform #if blocks, which already include them. Added unconditionally at the top of the file they broke the Windows build.

* Add the clang-tidy Configuration That Generated These Includes

Only misc-include-cleaner's missing-include check, with the headers it must never suggest: per-platform, internal and OS-specific ones that would break other platforms or are not meant to be included directly.

* Match Windows Paths in the clang-tidy Ignore List

Header paths use backslashes on Windows, so every / in a pattern is now [/\\]. The Windows SDK headers are ignored alongside the other OS-specific ones, and the list is one pattern per line. Suggested by @raistlin7447 from a Windows clang-cl run.
2026-10-02 14:56:56 +08:00
Kris Austin 9d71125495 ci: allow running the Main build status workflow manually (#16057) 2026-10-01 18:25:12 -03:00
Kris Austin 88f05d3060 ci: move the nightly Build all to the quietest hour for macOS runners (#16053) 2026-10-01 17:58:00 -03:00
Kris Austin 4743d00793 ci: README build badge no longer shows failing when a queued main build is cancelled (#16054) 2026-10-01 17:56:57 -03:00
1749 changed files with 28442 additions and 8495 deletions
+65
View File
@@ -0,0 +1,65 @@
# clang-tidy configuration. Only missing includes are reported for now: a file
# should include the header for every symbol it uses, not rely on the
# precompiled header or another header's includes. Run with --fix to add them.
Checks: '-*,misc-include-cleaner'
CheckOptions:
# Missing includes only. Builds without the precompiled header break on these.
misc-include-cleaner.UnusedIncludes: false
# Headers that declare a symbol but are not the one to include: per-platform
# implementations of wxWidgets and Boost.Thread (a Linux run would suggest the
# GTK or pthread one), library internals and forward declarations (MSVC's STL
# __msvc_* and the Windows UCRT's corecrt_* included), CPython's headers behind
# Python.h (python3.x/ on Linux and macOS, libpython/include/ on Windows),
# curl's behind curl.h, oneTBB's behind tbb/, and
# admesh's stl.h, which the include path also exposes without its directory.
# Clipper's own clipper.hpp is only included through libslic3r/clipper.hpp or
# clipper_z.hpp, which configure it first, and Boost.Polygon's headers only
# work through boost/polygon/polygon.hpp or voronoi.hpp. Clipper2's headers
# are included through clipper2/clipper.h, or clipper2_z.hpp, which defines
# USINGZ first. minilzo's config
# headers are internal to minilzo.h.
# FFmpeg's C headers are left alone because they are only included inside
# extern "C", which an inserted include would miss. OS-specific headers (GLib,
# GTK, D-Bus, POSIX, the Windows SDK) are only used inside platform #if blocks,
# and an include added at the top of the file would break the other platforms'
# builds. A symbol these provide is not reported missing.
# Write every / as [/\\] so the patterns also match Windows paths.
# No comments inside the block below, because a # there silently becomes part of a pattern.
misc-include-cleaner.IgnoreHeaders: >-
wx[/\\](gtk|gtk1|msw|osx|unix|x11|motif|univ|qt|dfb|generic|private)[/\\].*;
.*[/\\]detail[/\\].*;
.*[/\\]impl[/\\].*;
.*_fwd\.hpp;
python3\.[0-9]+[/\\].*;
libpython[/\\]include[/\\].*;
bits[/\\].*;
corecrt_.*\.h;
__msvc_.*\.hpp;
boost[/\\]multiprecision[/\\]fwd\.hpp;
imconfig\.h;
expat_external\.h;
admesh[/\\]stl\.h;
boost[/\\]thread[/\\](pthread|win32)[/\\].*;
boost[/\\]regex[/\\]v[0-9]+[/\\].*;
curl[/\\](easy|multi|system|urlapi|header|options|websockets|mprintf)\.h;
oneapi[/\\]tbb[/\\].*;
opencv2[/\\]core[/\\]hal[/\\].*;
openssl[/\\]ossl_typ\.h;
libav[a-z]+[/\\].*;
libsw[a-z]+[/\\].*;
glib-2\.0[/\\].*;
gtk-3\.0[/\\].*;
dbus-1\.0[/\\].*;
sys[/\\].*;
unistd\.h;
strings\.h;
fcntl\.h;
termios\.h;
[/\\](um|shared)[/\\].*;
sal\.h;
clipper[/\\]clipper\.hpp;
png(lib)?conf\.h;
mcut[/\\]platform\.h;
boost[/\\]polygon[/\\].*;
clipper2[/\\]clipper\.(core|engine|offset|minkowski|rectclip|export|triangulation|version)\.h;
lzo(conf|defs)\.h
+12 -3
View File
@@ -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
@@ -206,7 +214,8 @@ known class with a pitfall entry and a fixing commit.
- **macOS:** capture-lost is never sent (a leaked capture freezes all clicks); transient popups hover- - **macOS:** capture-lost is never sent (a leaked capture freezes all clicks); transient popups hover-
dismiss across a gap — anchor flush and re-verify the cursor; native modals (file/dir dialogs, native dismiss across a gap — anchor flush and re-verify the cursor; native modals (file/dir dialogs, native
message boxes) and generic progress dialogs re-activate the main window, so re-raise a secondary window message boxes) and generic progress dialogs re-activate the main window, so re-raise a secondary window
afterwards with a deferred, liveness-guarded `Raise()`; a live menu accelerator consumes the key before afterwards with a deferred, liveness-guarded `Raise()` — but never `Raise()` a `wxPopupWindow`, which makes
it the key window; a live menu accelerator consumes the key before
any wx key event; Control+click arrives as a right-click. any wx key event; Control+click arrives as a right-click.
- **Windows:** `IsDark()` and `wxSYS_COLOUR_*` follow the system app mode, not Orca's theme — use - **Windows:** `IsDark()` and `wxSYS_COLOUR_*` follow the system app mode, not Orca's theme — use
`dark_mode()`; menu bitmaps follow `check_dark_mode()`; windows are not double-buffered by default in `dark_mode()`; menu bitmaps follow `check_dark_mode()`; windows are not double-buffered by default 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`).
@@ -57,8 +57,9 @@ Contents: [Rules](#rules) · [1. Choosing the window kind](#1-choosing-the-windo
17. Don't shrink a dropdown below two rows to fit the screen. (§7) 17. Don't shrink a dropdown below two rows to fit the screen. (§7)
18. Content that needs typing focus or hosts a `wxWebView` uses a frameless `wxDialog` that hides on 18. Content that needs typing focus or hosts a `wxWebView` uses a frameless `wxDialog` that hides on
deactivation, not a transient popup. (§10) deactivation, not a transient popup. (§10)
19. Display-only overlays (HUDs, toasts) use a plain `wxPopupWindow`: it never takes focus and never 19. Display-only overlays (HUDs, toasts) use a plain `wxPopupWindow`: `Show()` never gives it focus and it
auto-dismisses. (§4, §10) never auto-dismisses. Never `Raise()` any `wxPopupWindow`: `Raise()` is for top-level windows only, and on
macOS it makes the popup the key window. (§4, §5, §10)
20. On MSW, don't `SetFocus()` another window on hover while `wxCurrentPopupWindow` is non-null. (§8) 20. On MSW, don't `SetFocus()` another window on hover while `wxCurrentPopupWindow` is non-null. (§8)
21. Menu items use `wxID_ANY` and read `item->GetId()`. `wxNewId()` is deprecated. (§13) 21. Menu items use `wxID_ANY` and read `item->GetId()`. `wxNewId()` is deprecated. (§13)
22. Set a menu item's bitmap before `Append`. Don't expect icons on check or radio items. Never call 22. Set a menu item's bitmap before `Append`. Don't expect icons on check or radio items. Never call
@@ -356,6 +357,13 @@ compensates for (§6).
with `ShowWithoutActivating` → `setHidesOnDeactivate:YES` + `orderFront` (`src/osx/carbon/popupwin.cpp:56-75`, with `ShowWithoutActivating` → `setHidesOnDeactivate:YES` + `orderFront` (`src/osx/carbon/popupwin.cpp:56-75`,
`nonownedwnd.mm:938-945`). When the app deactivates, Cocoa hides the panel and shows it again on reactivation. `nonownedwnd.mm:938-945`). When the app deactivates, Cocoa hides the panel and shows it again on reactivation.
wx never calls `OnDismiss`, and `IsShown()` stays true. This applies to plain `wxPopupWindow` overlays too. wx never calls `OnDismiss`, and `IsShown()` stays true. This applies to plain `wxPopupWindow` overlays too.
- `Raise()` activates the popup. It is `makeKeyAndOrderFront` (`src/osx/nonownedwnd_osx.cpp:289-295`,
`nonownedwnd.mm:897-899`), and `wxNSPanel` answers `canBecomeKeyWindow` with YES (`nonownedwnd.mm:271`), so
the popup becomes the key window. Keys go to it, and the frame loses key status: `windowDidResignKey` →
`HandleActivated(0, false)` → `wxEVT_ACTIVATE(false)` on the frame (`nonownedwnd.mm:567-576`,
`nonownedwnd_osx.cpp:303-310`). Hiding the key popup gives key back to the frame, which then gets
`wxEVT_ACTIVATE(true)` (observed; AppKit behaviour, not in the wx tree). A popup at `NSPopUpMenuWindowLevel`
is already above its frame, so `Raise()` buys nothing.
- Capture: `Show(true)` makes `m_child` capture the mouse ("Assume that the mouse is outside the popup to begin - Capture: `Show(true)` makes `m_child` capture the mouse ("Assume that the mouse is outside the popup to begin
with", `popupcmn.cpp:421-426`). `OnIdle` releases the capture while the cursor is inside and re-captures it with", `popupcmn.cpp:421-426`). `OnIdle` releases the capture while the cursor is inside and re-captures it
outside, but only when the mouse position has changed since the last idle pass. `s_posLast` is a outside, but only when the mouse position has changed since the last idle pass. `s_posLast` is a
@@ -739,8 +747,26 @@ created with `wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxF
- **Rule:** For overlays that must never take keyboard focus (above a GL surface), use a plain - **Rule:** For overlays that must never take keyboard focus (above a GL surface), use a plain
`wxPopupWindow(top, wxBORDER_NONE)`, not a `wxFrame`. `wxPopupWindow(top, wxBORDER_NONE)`, not a `wxFrame`.
**Why:** A frame took the X input focus and swallowed every shortcut until the user clicked the canvas. A popup **Why:** A frame took the X input focus and swallowed every shortcut until the user clicked the canvas. A popup
window cannot take focus. On macOS these overlays hide while the app is inactive (§5). window does not take focus when shown. On macOS these overlays hide while the app is inactive (§5).
Cite: `CAD/DesignCanvas.cpp` (`m_hud`, `m_status_hud`). Cite: `CAD/DesignCanvas.cpp` (`m_hud`, `m_status_hud`).
- **Rule:** Never `Raise()` a `wxPopupWindow`. To bring an overlay up, `Show()` it if it is hidden, then
`Move()` it.
**Why:** `Raise()` is documented for top-level windows only (`interface/wx/window.h:3028-3029`), and a popup
derives from `wxNonOwnedWindow`, not `wxTopLevelWindow` (`include/wx/popupwin.h:33`). On macOS it makes the
popup the key window (§5): the popup takes the keys meant for the window below it, and the frame receives
`wxEVT_ACTIVATE(false)`. A frame activate handler that hides the overlay on deactivation and re-places it on
activation then loops: each `Raise()` deactivates the frame, the hide reactivates it, and the re-place raises
again, recursing until the main thread's stack overflows. A popup's `Show()` is `ShowWithoutActivating`
and is safe.
```cpp
// Wrong
if (!overlay->IsShown()) overlay->Show();
overlay->Move(pos);
overlay->Raise();
// Right
if (!overlay->IsShown()) overlay->Show();
overlay->Move(pos);
```
## 11. wxComboCtrl / wxComboPopup ## 11. wxComboCtrl / wxComboPopup
@@ -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) ·
@@ -428,7 +427,8 @@ this function does *not* show it", top-level windows only (`interface/wx/window.
since 3.3 (`docs/changes.txt:144-146`). **[source]** MSW = `::SetForegroundWindow`, subject to the since 3.3 (`docs/changes.txt:144-146`). **[source]** MSW = `::SetForegroundWindow`, subject to the
foreground lock — Windows may only flash the taskbar button (`src/msw/toplevel.cpp:650-655`); GTK = foreground lock — Windows may only flash the taskbar button (`src/msw/toplevel.cpp:650-655`); GTK =
`gtk_window_present` only if shown (`src/gtk/toplevel.cpp:1301-1310`; during a deferred X11 first show it `gtk_window_present` only if shown (`src/gtk/toplevel.cpp:1301-1310`; during a deferred X11 first show it
already counts as shown); macOS = `makeKeyAndOrderFront` only if shown (`src/osx/nonownedwnd_osx.cpp:289-295`, `src/osx/cocoa/nonownedwnd.mm:896-899`). already counts as shown); macOS = `makeKeyAndOrderFront` only if shown (`src/osx/nonownedwnd_osx.cpp:289-295`, `src/osx/cocoa/nonownedwnd.mm:897-899`),
which also makes a `wxPopupWindow` the key window — never `Raise()` a popup (`references/popups-menus.md` §5, §10).
**Enable.** `Enable(false)` on a parent disables children logically: `IsEnabled()` reflects ancestors, **Enable.** `Enable(false)` on a parent disables children logically: `IsEnabled()` reflects ancestors,
`IsThisEnabled()` the window's own flag (`interface/wx/window.h:3060-3070, 3116-3138`). **[source]** On MSW/macOS wx `IsThisEnabled()` the window's own flag (`interface/wx/window.h:3060-3070, 3116-3138`). **[source]** On MSW/macOS wx
@@ -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
+1 -1
View File
@@ -45,7 +45,7 @@ on:
schedule: schedule:
- cron: '0 17 * * *' # run once a day at 1 AM Singapore time (UTC+8) - cron: '15 2 * * *' # 10:15 AM Singapore time (UTC+8), when macOS runners are least busy
workflow_dispatch: # allows for manual dispatch workflow_dispatch: # allows for manual dispatch
inputs: inputs:
-17
View File
@@ -49,28 +49,11 @@ jobs:
key: ${{ inputs.cache-key }} key: ${{ inputs.cache-key }}
- uses: lukka/get-cmake@latest - uses: lukka/get-cmake@latest
# The windows-11-arm runner needs CMake <= 3.31 (handled in the next step).
if: ${{ !(runner.os == 'Windows' && inputs.arch == 'arm64') }}
with: with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false'). useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true useCloudCache: true
- name: Install CMake 3.31.x (Windows ARM64)
# windows-11-arm ships CMake 4.x, which removed pre-3.5 policy
# compatibility AND has incomplete ASM_ARMASM linker modules
# (breaks Boost.Context on ARM64). Pin to the last 3.x release.
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: pwsh
run: |
$ver = "3.31.6"
$url = "https://github.com/Kitware/CMake/releases/download/v$ver/cmake-$ver-windows-arm64.zip"
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\cmake.zip"
Expand-Archive -Path "$env:RUNNER_TEMP\cmake.zip" -DestinationPath "$env:RUNNER_TEMP\cmake" -Force
$cmakeBin = "$env:RUNNER_TEMP\cmake\cmake-$ver-windows-arm64\bin"
if (-not (Test-Path "$cmakeBin\cmake.exe")) { throw "cmake.exe not found at $cmakeBin" }
Add-Content -Path $env:GITHUB_PATH -Value $cmakeBin
- name: setup dev on Windows - name: setup dev on Windows
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v3
+1 -17
View File
@@ -54,28 +54,11 @@ jobs:
fail-on-cache-miss: true fail-on-cache-miss: true
- uses: lukka/get-cmake@latest - uses: lukka/get-cmake@latest
# The windows-11-arm runner needs CMake <= 3.31 (handled in the next step).
if: ${{ !(runner.os == 'Windows' && inputs.arch == 'arm64') }}
with: with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false'). useLocalCache: true # <--= Use the local cache (default is 'false').
useCloudCache: true useCloudCache: true
- name: Install CMake 3.31.x (Windows ARM64)
# windows-11-arm ships CMake 4.x, which removed pre-3.5 policy
# compatibility AND has incomplete ASM_ARMASM linker modules
# (breaks Boost.Context on ARM64). Pin to the last 3.x release.
if: runner.os == 'Windows' && inputs.arch == 'arm64'
shell: pwsh
run: |
$ver = "3.31.6"
$url = "https://github.com/Kitware/CMake/releases/download/v$ver/cmake-$ver-windows-arm64.zip"
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\cmake.zip"
Expand-Archive -Path "$env:RUNNER_TEMP\cmake.zip" -DestinationPath "$env:RUNNER_TEMP\cmake" -Force
$cmakeBin = "$env:RUNNER_TEMP\cmake\cmake-$ver-windows-arm64\bin"
if (-not (Test-Path "$cmakeBin\cmake.exe")) { throw "cmake.exe not found at $cmakeBin" }
Add-Content -Path $env:GITHUB_PATH -Value $cmakeBin
# Compiler cache. Pushes save it, so main keeps it warm; pull requests # Compiler cache. Pushes save it, so main keeps it warm; pull requests
# restore it and discard what they compiled. Objects are keyed on the # restore it and discard what they compiled. Objects are keyed on the
# preprocessed source, the compiler and the flags, so a leg only ever # preprocessed source, the compiler and the flags, so a leg only ever
@@ -276,6 +259,7 @@ jobs:
# Thanks to RaySajuuk, it's working now # Thanks to RaySajuuk, it's working now
- name: Sign app and notary - name: Sign app and notary
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/belt-printer' || startsWith(github.ref, 'refs/heads/release/')) && runner.os == 'macOS' && inputs.macos-combine-only
timeout-minutes: 30
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
env: env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+28
View File
@@ -0,0 +1,28 @@
# Reports the newest push build of main that was not cancelled, for the README badge.
name: Main build status
on:
workflow_run:
workflows: ["Build all"]
types: [completed]
branches: [main]
workflow_dispatch:
permissions:
actions: read
jobs:
status:
if: github.repository == 'OrcaSlicer/OrcaSlicer'
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ github.token }}
run: |
for attempt in 1 2 3; do
conclusion=$(gh api "repos/${{ github.repository }}/actions/workflows/build_all.yml/runs?branch=main&event=push&status=completed&per_page=100" \
--jq '[.workflow_runs[] | select(.conclusion != "cancelled")][0].conclusion') && break
sleep 10
done
echo "Latest finished push build of main: $conclusion"
[ "$conclusion" = success ]
-8
View File
@@ -44,14 +44,6 @@ jobs:
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
with: with:
name: ${{ inputs.artifact }} name: ${{ inputs.artifact }}
# run_unit_tests.sh installs the plugin tests' numpy with the uv the build stages
# beside them; the Windows arm64 build bundles none, so put one on PATH there.
- name: Install uv
if: runner.os == 'Windows' && runner.arch == 'ARM64'
uses: astral-sh/setup-uv@v10.2.0
with:
version: "0.11.21" # ORCA_UV_VERSION in CMakeLists.txt
enable-cache: false
- uses: lukka/get-cmake@latest - uses: lukka/get-cmake@latest
with: with:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version cmakeVersion: "~4.3.0" # use most recent 4.3.x version
+16 -2
View File
@@ -135,6 +135,7 @@ set(ORCA_UV_SHA256_aarch64-apple-darwin "1f921d491ba5ffeea774eb04d6681ecee3
set(ORCA_UV_SHA256_x86_64-apple-darwin "f3c8e5708a84b920c18b691214d54d2b0da6b984789caae95d47c95120cb7765") set(ORCA_UV_SHA256_x86_64-apple-darwin "f3c8e5708a84b920c18b691214d54d2b0da6b984789caae95d47c95120cb7765")
set(ORCA_UV_SHA256_aarch64-unknown-linux-gnu "88e800834007cc5efd4675f166eb2a51e7e3ad19876d85fa8805a6fb5c922397") set(ORCA_UV_SHA256_aarch64-unknown-linux-gnu "88e800834007cc5efd4675f166eb2a51e7e3ad19876d85fa8805a6fb5c922397")
set(ORCA_UV_SHA256_x86_64-unknown-linux-gnu "8c88519b0ef0af9801fcdee419bbb12116bd9e6b18e162ae093c932d8b264050") set(ORCA_UV_SHA256_x86_64-unknown-linux-gnu "8c88519b0ef0af9801fcdee419bbb12116bd9e6b18e162ae093c932d8b264050")
set(ORCA_UV_SHA256_aarch64-pc-windows-msvc "74e443f8004022dde57a1bd0d10c097830f9ea8feb4ec927db52cd5d805c2f48")
set(ORCA_UV_SHA256_x86_64-pc-windows-msvc "ace861f360c6de2babedc1607d0f454b6b09a820dbc8182dc15af927e4df9589") set(ORCA_UV_SHA256_x86_64-pc-windows-msvc "ace861f360c6de2babedc1607d0f454b6b09a820dbc8182dc15af927e4df9589")
# Version-scoped cache dir so a version bump invalidates the cached binary. # Version-scoped cache dir so a version bump invalidates the cached binary.
@@ -173,7 +174,10 @@ if(NOT ORCA_BUNDLED_UV_EXECUTABLE)
set(ORCA_UV_ARCH "x86_64-unknown-linux-gnu") set(ORCA_UV_ARCH "x86_64-unknown-linux-gnu")
endif() endif()
elseif(_orca_uv_proc MATCHES "aarch64|arm64|ARM64") elseif(_orca_uv_proc MATCHES "aarch64|arm64|ARM64")
if(APPLE) if(WIN32)
set(ORCA_UV_ARCH "aarch64-pc-windows-msvc")
set(ORCA_UV_EXT "zip")
elseif(APPLE)
set(ORCA_UV_ARCH "aarch64-apple-darwin") set(ORCA_UV_ARCH "aarch64-apple-darwin")
else() else()
set(ORCA_UV_ARCH "aarch64-unknown-linux-gnu") set(ORCA_UV_ARCH "aarch64-unknown-linux-gnu")
@@ -819,7 +823,9 @@ if(SLIC3R_STATIC)
set(TBB_STATIC 1) set(TBB_STATIC 1)
endif() endif()
set(TBB_DEBUG 1) set(TBB_DEBUG 1)
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "") if ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" OR MSVC)
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release "")
endif()
find_package(TBB REQUIRED) find_package(TBB REQUIRED)
# include_directories(SYSTEM ${TBB_INCLUDE_DIRS}) # include_directories(SYSTEM ${TBB_INCLUDE_DIRS})
# add_definitions(${TBB_DEFINITIONS}) # add_definitions(${TBB_DEFINITIONS})
@@ -1301,6 +1307,14 @@ if (WIN32)
endif() endif()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries) include(InstallRequiredSystemLibraries)
# A missing MSVC runtime is an error because an installer without it cannot start on a clean machine.
set(_orca_runtime_names ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS})
list(TRANSFORM _orca_runtime_names REPLACE "^.*/" "")
if (MSVC AND (NOT "msvcp140.dll" IN_LIST _orca_runtime_names OR NOT "vcruntime140.dll" IN_LIST _orca_runtime_names))
set(_orca_runtime_error "CMake ${CMAKE_VERSION} did not find msvcp140.dll and vcruntime140.dll for MSVC ${MSVC_VERSION}. Update CMake to a release that supports this Visual Studio.")
message(WARNING "${_orca_runtime_error}")
install(CODE "message(FATAL_ERROR \"${_orca_runtime_error}\")")
endif ()
install (PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} DESTINATION ".") install (PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} DESTINATION ".")
elseif (SLIC3R_FHS) elseif (SLIC3R_FHS)
# CMAKE_INSTALL_FULL_DATAROOTDIR: read-only architecture-independent data root (share) # CMAKE_INSTALL_FULL_DATAROOTDIR: read-only architecture-independent data root (share)
+1 -1
View File
@@ -6,7 +6,7 @@
<a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://trendshift.io/repositories/15552" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15552" alt="OrcaSlicer%2FOrcaSlicer | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
[![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml/badge.svg?branch=main)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml) [![GitHub Repo stars](https://img.shields.io/github/stars/OrcaSlicer/OrcaSlicer)](https://github.com/OrcaSlicer/OrcaSlicer/stargazers) [![Build all](https://img.shields.io/github/actions/workflow/status/OrcaSlicer/OrcaSlicer/main_build_status.yml?label=Build%20all)](https://github.com/OrcaSlicer/OrcaSlicer/actions/workflows/build_all.yml)
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints. OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection. Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
+1 -5
View File
@@ -282,11 +282,7 @@ if "%install_deps%" == "ON" (
call :note_failed "Visual Studio" !errorlevel! call :note_failed "Visual Studio" !errorlevel!
) )
REM CMake 4 dropped pre-3.5 policy support and ships incomplete ASM_ARMASM call :print_and_run winget install !winget_args! --id=Kitware.CMake
REM linker modules, which breaks Boost.Context on ARM64. CI pins the same way.
set "cmake_version_flag="
if /I "%arch%" == "ARM64" set "cmake_version_flag=--version 3.31.8"
call :print_and_run winget install !winget_args! --id=Kitware.CMake !cmake_version_flag!
call :note_failed CMake !errorlevel! call :note_failed CMake !errorlevel!
call :print_and_run winget install !winget_args! --id=StrawberryPerl.StrawberryPerl call :print_and_run winget install !winget_args! --id=StrawberryPerl.StrawberryPerl
call :note_failed Perl !errorlevel! call :note_failed Perl !errorlevel!
+10
View File
@@ -29,6 +29,15 @@ if(WIN32)
# driven through nmake from a Ninja configure step. # driven through nmake from a Ninja configure step.
set(_conf_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} perl Configure ) set(_conf_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} perl Configure )
set(_cross_comp_prefix_line "") set(_cross_comp_prefix_line "")
if("${DEPS_ARCH}" STREQUAL "arm64")
# OpenSSL's VC configs pass /Gs0, which puts a __chkstk probe in every
# function. MSVC 14.51 and 14.52 (VS 2026) for ARM64 emit that call
# before the prologue saves LR, so the function returns into itself;
# in tls_parse_all_extensions that breaks every TLS handshake. 14.44
# (VS 2022) is unaffected. Restore cl's default threshold: Configure
# appends /Gs4096 after /Gs0, and the later option wins.
set(_openssl_extra_cflags /Gs4096)
endif()
set(_make_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake) set(_make_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake)
set(_install_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake install_sw ) set(_install_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake install_sw )
else() else()
@@ -71,6 +80,7 @@ ExternalProject_Add(dep_OpenSSL
# prefix stays single-layout. # prefix stays single-layout.
"--libdir=lib" "--libdir=lib"
${_cross_comp_prefix_line} ${_cross_comp_prefix_line}
${_openssl_extra_cflags}
no-shared no-shared
no-asm no-asm
no-ssl3-method no-ssl3-method
-1
View File
@@ -15,7 +15,6 @@ add_subdirectory(stb_dxt) # Header-only STB DXT compression library
# Static libraries # Static libraries
add_subdirectory(Shiny) add_subdirectory(Shiny)
add_subdirectory(admesh) add_subdirectory(admesh)
add_subdirectory(clipper)
add_subdirectory(clipper2) add_subdirectory(clipper2)
add_subdirectory(expat) add_subdirectory(expat)
add_subdirectory(glu-libtess) add_subdirectory(glu-libtess)
-20
View File
@@ -1,20 +0,0 @@
cmake_minimum_required(VERSION 3.13)
project(clipper)
add_library(clipper STATIC
# We are using ClipperLib compiled as part of the libslic3r project using Slic3r::Point as its base type.
# clipper.cpp
# clipper.hpp
clipper_z.cpp
clipper_z.hpp
)
target_include_directories(clipper SYSTEM
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(clipper
PUBLIC Eigen3::Eigen
PRIVATE TBB::tbb TBB::tbbmalloc
)
File diff suppressed because it is too large Load Diff
-606
View File
@@ -1,606 +0,0 @@
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.4.2 *
* Date : 27 February 2017 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2017 *
* *
* License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt *
* *
* Attributions: *
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
* "A generic solution to polygon clipping" *
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
* http://portal.acm.org/citation.cfm?id=129906 *
* *
* Computer graphics and geometric modeling: implementation and algorithms *
* By Max K. Agoston *
* Springer; 1 edition (January 4, 2005) *
* http://books.google.com/books?q=vatti+clipping+agoston *
* *
* See also: *
* "Polygon Offsetting by Computing Winding Numbers" *
* Paper no. DETC2005-85513 pp. 565-575 *
* ASME 2005 International Design Engineering Technical Conferences *
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
* September 24-28, 2005 , Long Beach, California, USA *
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
* *
*******************************************************************************/
#ifndef clipper_hpp
#define clipper_hpp
#include <inttypes.h>
#include <functional>
#include <Eigen/Geometry>
#include <oneapi/tbb/scalable_allocator.h>
#define CLIPPER_VERSION "6.2.6"
//CLIPPERLIB_USE_XYZ: adds a Z member to IntPoint. Adds a minor cost to perfomance.
//#define CLIPPERLIB_USE_XYZ
//use_lines: Enables line clipping. Adds a very minor cost to performance.
#define use_lines
//use_deprecated: Enables temporary support for the obsolete functions
//#define use_deprecated
#include <array>
#include <vector>
#include <deque>
#include <stdexcept>
#include <cstring>
#include <cstdlib>
#include <ostream>
#include <functional>
#include <queue>
#ifdef CLIPPERLIB_NAMESPACE_PREFIX
namespace CLIPPERLIB_NAMESPACE_PREFIX {
#endif // CLIPPERLIB_NAMESPACE_PREFIX
#ifdef CLIPPERLIB_USE_XYZ
namespace ClipperLib_Z {
#else
namespace ClipperLib {
#endif
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
enum PolyType { ptSubject, ptClip };
//By far the most widely used winding rules for polygon filling are
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
//see http://glprogramming.com/red/chapter11.html
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
// If defined, Clipper will work with 32bit signed int coordinates to reduce memory
// consumption and to speed up exact orientation predicate calculation.
// In that case, coordinates and their differences (vectors of the coordinates) have to fit int32_t.
// #define CLIPPERLIB_INT32
// Point coordinate type
#ifdef CLIPPERLIB_INT32
// Coordinates and their differences (vectors of the coordinates) have to fit int32_t.
using cInt = int32_t;
using CrossProductType = int64_t;
#else
using cInt = int64_t;
using CrossProductType = double;
// Maximum cInt value to allow a cross product calculation using 32bit expressions.
static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823
// Maximum allowed cInt value.
static constexpr cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
#endif // CLIPPERLIB_INT32
#ifdef CLIPPERLIB_INTPOINT_TYPE
using IntPoint = CLIPPERLIB_INTPOINT_TYPE;
#else // CLIPPERLIB_INTPOINT_TYPE
using IntPoint = Eigen::Matrix<cInt,
#ifdef CLIPPERLIB_USE_XYZ
3
#else // CLIPPERLIB_USE_XYZ
2
#endif // CLIPPERLIB_USE_XYZ
, 1, Eigen::DontAlign>;
#endif // CLIPPERLIB_INTPOINT_TYPE
using DoublePoint = Eigen::Matrix<double, 2, 1, Eigen::DontAlign>;
//------------------------------------------------------------------------------
template<typename BaseType>
using Allocator = tbb::scalable_allocator<BaseType>;
//using Allocator = std::allocator<BaseType>;
using Path = std::vector<IntPoint, Allocator<IntPoint>>;
using Paths = std::vector<Path, Allocator<Path>>;
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
std::ostream& operator <<(std::ostream &s, const IntPoint &p);
std::ostream& operator <<(std::ostream &s, const Path &p);
std::ostream& operator <<(std::ostream &s, const Paths &p);
//------------------------------------------------------------------------------
#ifdef CLIPPERLIB_USE_XYZ
typedef std::function<void(const IntPoint& e1bot, const IntPoint& e1top, const IntPoint& e2bot, const IntPoint& e2top, IntPoint& pt)> ZFillCallback;
#endif
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
enum JoinType {jtSquare, jtRound, jtMiter};
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
class PolyNode;
typedef std::vector<PolyNode*, Allocator<PolyNode*>> PolyNodes;
class PolyNode
{
public:
PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
virtual ~PolyNode(){};
Path Contour;
PolyNodes Childs;
PolyNode* Parent;
// Traversal of the polygon tree in a depth first fashion.
PolyNode* GetNext() const { return Childs.empty() ? GetNextSiblingUp() : Childs.front(); }
bool IsHole() const;
bool IsOpen() const { return m_IsOpen; }
int ChildCount() const { return (int)Childs.size(); }
private:
unsigned Index; //node index in Parent.Childs
bool m_IsOpen;
JoinType m_jointype;
EndType m_endtype;
PolyNode* GetNextSiblingUp() const { return Parent ? ((Index == Parent->Childs.size() - 1) ? Parent->GetNextSiblingUp() : Parent->Childs[Index + 1]) : nullptr; }
void AddChild(PolyNode& child);
friend class Clipper; //to access Index
friend class ClipperOffset;
friend class PolyTree; //to implement the PolyTree::move operator
};
class PolyTree: public PolyNode
{
public:
PolyTree() {}
PolyTree(PolyTree &&src) { *this = std::move(src); }
virtual ~PolyTree(){Clear();};
PolyTree& operator=(PolyTree &&src) {
AllNodes = std::move(src.AllNodes);
Contour = std::move(src.Contour);
Childs = std::move(src.Childs);
Parent = nullptr;
Index = src.Index;
m_IsOpen = src.m_IsOpen;
m_jointype = src.m_jointype;
m_endtype = src.m_endtype;
for (size_t i = 0; i < Childs.size(); ++ i)
Childs[i]->Parent = this;
return *this;
}
PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); }
void Clear() { AllNodes.clear(); Childs.clear(); }
int Total() const;
void RemoveOutermostPolygon();
private:
PolyTree(const PolyTree &src) = delete;
PolyTree& operator=(const PolyTree &src) = delete;
std::vector<PolyNode, Allocator<PolyNode>> AllNodes;
friend class Clipper; //to access AllNodes
};
double Area(const Path &poly);
inline bool Orientation(const Path &poly) { return Area(poly) >= 0; }
int PointInPolygon(const IntPoint &pt, const Path &path);
// Union with "strictly simple" fix enabled.
Paths SimplifyPolygon(const Path &in_poly, PolyFillType fillType = pftNonZero, bool strictly_simple = true);
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
void CleanPolygon(Path& poly, double distance = 1.415);
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
void CleanPolygons(Paths& polys, double distance = 1.415);
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
void ReversePath(Path& p);
void ReversePaths(Paths& p);
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
//enums that are used internally ...
enum EdgeSide { esLeft = 1, esRight = 2};
// namespace Internal {
//forward declarations (for stuff used internally) ...
struct TEdge {
// Bottom point of this edge (with minimum Y).
IntPoint Bot;
// Current position.
IntPoint Curr;
// Top point of this edge (with maximum Y).
IntPoint Top;
// Slope (dx/dy). For horiontal edges, the slope is set to HORIZONTAL (-1.0E+40).
double Dx;
PolyType PolyTyp;
EdgeSide Side;
// Winding number delta. 1 or -1 depending on winding direction, 0 for open paths and flat closed paths.
int WindDelta;
int WindCnt;
int WindCnt2; //winding count of the opposite polytype
int OutIdx;
// Next edge in the input path.
TEdge *Next;
// Previous edge in the input path.
TEdge *Prev;
// Next edge in the Local Minima List chain.
TEdge *NextInLML;
TEdge *NextInAEL;
TEdge *PrevInAEL;
TEdge *NextInSEL;
TEdge *PrevInSEL;
};
struct IntersectNode {
IntersectNode(TEdge *Edge1, TEdge *Edge2, IntPoint Pt) :
Edge1(Edge1), Edge2(Edge2), Pt(Pt) {}
TEdge *Edge1;
TEdge *Edge2;
IntPoint Pt;
};
struct LocalMinimum {
cInt Y;
TEdge *LeftBound;
TEdge *RightBound;
};
// Point of an output polygon.
// 36B on 64bit system without CLIPPERLIB_USE_XYZ.
struct OutPt {
// 4B
int Idx;
// 16B without CLIPPERLIB_USE_XYZ / 24B with CLIPPERLIB_USE_XYZ
IntPoint Pt;
// 4B on 32bit system, 8B on 64bit system
OutPt *Next;
// 4B on 32bit system, 8B on 64bit system
OutPt *Prev;
};
using OutPts = std::vector<OutPt, Allocator<OutPt>>;
// Output polygon.
struct OutRec {
int Idx;
bool IsHole;
bool IsOpen;
//The 'FirstLeft' field points to another OutRec that contains or is the
//'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is
//parsed left from the current edge (owning OutRec) until the owner OutRec
//is found. This field simplifies sorting the polygons into a tree structure
//which reflects the parent/child relationships of all polygons.
//This field should be renamed Parent, and will be later.
OutRec* FirstLeft;
// Used only by void Clipper::BuildResult2(PolyTree& polytree)
PolyNode* PolyNd;
// Linked list of output points, dynamically allocated.
OutPt* Pts;
OutPt* BottomPt;
};
struct Join {
Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) :
OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {}
OutPt *OutPt1;
OutPt *OutPt2;
IntPoint OffPt;
};
// }; // namespace Internal
//------------------------------------------------------------------------------
//ClipperBase is the ancestor to the Clipper class. It should not be
//instantiated directly. This class simply abstracts the conversion of sets of
//polygon coordinates into edge objects that are stored in a LocalMinima list.
class ClipperBase
{
public:
ClipperBase() :
#ifndef CLIPPERLIB_INT32
m_UseFullRange(false),
#endif // CLIPPERLIB_INT32
m_HasOpenPaths(false) {}
~ClipperBase() { Clear(); }
bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
template<typename PathsProvider>
bool AddPaths(PathsProvider &&paths_provider, PolyType PolyTyp, bool Closed)
{
size_t num_paths = paths_provider.size();
if (num_paths == 0)
return false;
if (num_paths == 1)
return AddPath(*paths_provider.begin(), PolyTyp, Closed);
std::vector<int, Allocator<int>> num_edges(num_paths, 0);
int num_edges_total = 0;
size_t i = 0;
for (const Path &pg : paths_provider) {
// Remove duplicate end point from a closed input path.
// Remove duplicate points from the end of the input path.
int highI = (int)pg.size() -1;
if (Closed)
while (highI > 0 && (pg[highI] == pg[0]))
--highI;
while (highI > 0 && (pg[highI] == pg[highI -1]))
--highI;
if ((Closed && highI < 2) || (!Closed && highI < 1))
highI = -1;
num_edges[i ++] = highI + 1;
num_edges_total += highI + 1;
}
if (num_edges_total == 0)
return false;
// Allocate a new edge array.
std::vector<TEdge, Allocator<TEdge>> edges(num_edges_total);
// Fill in the edge array.
bool result = false;
TEdge *p_edge = edges.data();
i = 0;
for (const Path &pg : paths_provider) {
if (num_edges[i] && !pg.empty()) {
bool res = AddPathInternal(pg, num_edges[i] - 1, PolyTyp, Closed, p_edge);
if (res) {
p_edge += num_edges[i];
result = true;
}
}
++ i;
}
if (result)
// At least some edges were generated. Remember the edge array.
m_edges.emplace_back(std::move(edges));
return result;
}
void Clear();
IntRect GetBounds();
// By default, when three or more vertices are collinear in input polygons (subject or clip), the Clipper object removes the 'inner' vertices before clipping.
// When enabled the PreserveCollinear property prevents this default behavior to allow these inner vertices to appear in the solution.
bool PreserveCollinear() const {return m_PreserveCollinear;};
void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
protected:
bool AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges);
TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
void Reset();
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
TEdge* DescendToMin(TEdge *&E);
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
// Local minima (Y, left edge, right edge) sorted by ascending Y.
std::vector<LocalMinimum, Allocator<LocalMinimum>> m_MinimaList;
#ifdef CLIPPERLIB_INT32
static constexpr const bool m_UseFullRange = false;
#else // CLIPPERLIB_INT32
// True if the input polygons have abs values higher than loRange, but lower than hiRange.
// False if the input polygons have abs values lower or equal to loRange.
bool m_UseFullRange;
#endif // CLIPPERLIB_INT32
// A vector of edges per each input path.
using Edges = std::vector<TEdge, Allocator<TEdge>>;
std::vector<Edges, Allocator<Edges>> m_edges;
// Don't remove intermediate vertices of a collinear sequence of points.
bool m_PreserveCollinear;
// Is any of the paths inserted by AddPath() or AddPaths() open?
bool m_HasOpenPaths;
};
//------------------------------------------------------------------------------
class Clipper : public ClipperBase
{
public:
Clipper(int initOptions = 0);
~Clipper() { Clear(); }
void Clear() { ClipperBase::Clear(); DisposeAllOutRecs(); }
bool Execute(ClipType clipType,
Paths &solution,
PolyFillType fillType = pftEvenOdd)
{ return Execute(clipType, solution, fillType, fillType); }
bool Execute(ClipType clipType,
Paths &solution,
PolyFillType subjFillType,
PolyFillType clipFillType);
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType fillType = pftEvenOdd)
{ return Execute(clipType, polytree, fillType, fillType); }
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType subjFillType,
PolyFillType clipFillType);
bool ReverseSolution() const { return m_ReverseOutput; };
void ReverseSolution(bool value) {m_ReverseOutput = value;};
bool StrictlySimple() const {return m_StrictSimple;};
void StrictlySimple(bool value) {m_StrictSimple = value;};
//set the callback function for z value filling on intersections (otherwise Z is 0)
#ifdef CLIPPERLIB_USE_XYZ
void ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; }
#endif
protected:
void Reset();
virtual bool ExecuteInternal();
private:
// Output polygons.
std::deque<OutRec, Allocator<OutRec>> m_PolyOuts;
// Output points, allocated by a continuous sets of m_OutPtsChunkSize.
static constexpr const size_t m_OutPtsChunkSize = 32;
std::deque<std::array<OutPt, m_OutPtsChunkSize>, Allocator<std::array<OutPt, m_OutPtsChunkSize>>> m_OutPts;
// List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk.
OutPt *m_OutPtsFree;
size_t m_OutPtsChunkLast;
std::vector<Join, Allocator<Join>> m_Joins;
std::vector<Join, Allocator<Join>> m_GhostJoins;
std::vector<IntersectNode, Allocator<IntersectNode>> m_IntersectList;
ClipType m_ClipType;
// A priority queue (a binary heap) of Y coordinates.
using cInts = std::vector<cInt, Allocator<cInt>>;
std::priority_queue<cInt, cInts> m_Scanbeam;
// Maxima are collected by ProcessEdgesAtTopOfScanbeam(), consumed by ProcessHorizontal().
cInts m_Maxima;
TEdge *m_ActiveEdges;
TEdge *m_SortedEdges;
PolyFillType m_ClipFillType;
PolyFillType m_SubjFillType;
bool m_ReverseOutput;
// Does the result go to a PolyTree or Paths?
bool m_UsingPolyTree;
bool m_StrictSimple;
#ifdef CLIPPERLIB_USE_XYZ
ZFillCallback m_ZFill; //custom callback
#endif
void SetWindingCount(TEdge& edge) const;
bool IsEvenOddFillType(const TEdge& edge) const
{ return (edge.PolyTyp == ptSubject) ? m_SubjFillType == pftEvenOdd : m_ClipFillType == pftEvenOdd; }
bool IsEvenOddAltFillType(const TEdge& edge) const
{ return (edge.PolyTyp == ptSubject) ? m_ClipFillType == pftEvenOdd : m_SubjFillType == pftEvenOdd; }
void InsertLocalMinimaIntoAEL(const cInt botY);
void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
void AddEdgeToSEL(TEdge *edge);
void CopyAELToSEL();
void DeleteFromSEL(TEdge *e);
void DeleteFromAEL(TEdge *e);
void UpdateEdgeIntoAEL(TEdge *&e);
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
bool IsContributing(const TEdge& edge) const;
bool IsTopHorz(const cInt XPos);
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
void DoMaxima(TEdge *e);
void ProcessHorizontals();
void ProcessHorizontal(TEdge *horzEdge);
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutRec* GetOutRec(int idx);
void AppendPolygon(TEdge *e1, TEdge *e2);
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
OutRec* CreateOutRec();
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
OutPt* GetLastOutPt(TEdge *e);
OutPt* AllocateOutPt();
OutPt* DupOutPt(OutPt* outPt, bool InsertAfter);
// Add the point to a list of free points.
void DisposeOutPt(OutPt *pt) { pt->Next = m_OutPtsFree; m_OutPtsFree = pt; }
void DisposeOutPts(OutPt*& pp) { if (pp != nullptr) { pp->Prev->Next = m_OutPtsFree; m_OutPtsFree = pp; } }
void DisposeAllOutRecs();
bool ProcessIntersections(const cInt topY);
void BuildIntersectList(const cInt topY);
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
void BuildResult(Paths& polys);
void BuildResult2(PolyTree& polytree);
void SetHoleState(TEdge *e, OutRec *outrec);
bool FixupIntersectionOrder();
void FixupOutPolygon(OutRec &outrec);
void FixupOutPolyline(OutRec &outrec);
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
void FixHoleLinkage(OutRec &outrec);
bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft);
void JoinCommonEdges();
void DoSimplePolygons();
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
#ifdef CLIPPERLIB_USE_XYZ
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
#endif
};
//------------------------------------------------------------------------------
class ClipperOffset
{
public:
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25, double shortestEdgeLength = 0.) :
MiterLimit(miterLimit), ArcTolerance(roundPrecision), ShortestEdgeLength(shortestEdgeLength), m_lowest(-1, 0) {}
~ClipperOffset() { Clear(); }
void AddPath(const Path& path, JoinType joinType, EndType endType);
template<typename PathsProvider>
void AddPaths(PathsProvider &&paths, JoinType joinType, EndType endType) {
for (const Path &path : paths)
AddPath(path, joinType, endType);
}
void Execute(Paths& solution, double delta);
void Execute(PolyTree& solution, double delta);
void Clear();
double MiterLimit;
double ArcTolerance;
double ShortestEdgeLength;
private:
Paths m_destPolys;
Path m_srcPoly;
Path m_destPoly;
std::vector<DoublePoint, Allocator<DoublePoint>> m_normals;
double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad;
// x: index of the lowest contour in m_polyNodes
// y: index of the lowest point in the lowest contour
IntPoint m_lowest;
PolyNode m_polyNodes;
void FixOrientations();
void DoOffset(double delta);
void OffsetPoint(int j, int& k, JoinType jointype);
void DoSquare(int j, int k);
void DoMiter(int j, int k, double r);
void DoRound(int j, int k);
};
//------------------------------------------------------------------------------
class clipperException : public std::exception
{
public:
clipperException(const char* description): m_descr(description) {}
virtual ~clipperException() throw() {}
virtual const char* what() const throw() {return m_descr.c_str();}
private:
std::string m_descr;
};
//------------------------------------------------------------------------------
// Union with "strictly simple" fix enabled.
template<typename PathsProvider>
inline Paths SimplifyPolygons(PathsProvider &&in_polys, PolyFillType fillType = pftNonZero, bool strictly_simple = true) {
Clipper c;
c.StrictlySimple(strictly_simple);
c.AddPaths(std::forward<PathsProvider>(in_polys), ptSubject, true);
Paths out;
c.Execute(ctUnion, out, fillType, fillType);
return out;
}
} //ClipperLib namespace
#ifdef CLIPPERLIB_NAMESPACE_PREFIX
} // namespace CLIPPERLIB_NAMESPACE_PREFIX
#endif // CLIPPERLIB_NAMESPACE_PREFIX
#endif //clipper_hpp
-7
View File
@@ -1,7 +0,0 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
// Enable the Z coordinate support.
#define CLIPPERLIB_USE_XYZ
// and let it compile
#include "clipper.cpp"
-18
View File
@@ -1,18 +0,0 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
#ifndef clipper_z_hpp
#ifdef clipper_hpp
#error "You should include clipper_z.hpp before clipper.hpp"
#endif
#define clipper_z_hpp
// Enable the Z coordinate support.
#define CLIPPERLIB_USE_XYZ
#include "clipper.hpp"
#undef clipper_hpp
#undef CLIPPERLIB_USE_XYZ
#endif // clipper_z_hpp
+6 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX) project(Clipper2 VERSION 2.0.1 LANGUAGES C CXX)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
@@ -19,6 +19,7 @@ set(CLIPPER2_INC
Clipper2Lib/include/clipper2/clipper.minkowski.h Clipper2Lib/include/clipper2/clipper.minkowski.h
Clipper2Lib/include/clipper2/clipper.offset.h Clipper2Lib/include/clipper2/clipper.offset.h
Clipper2Lib/include/clipper2/clipper.rectclip.h Clipper2Lib/include/clipper2/clipper.rectclip.h
Clipper2Lib/include/clipper2/clipper.triangulation.h
Clipper2Lib/include/clipper2/clipper2_z.hpp Clipper2Lib/include/clipper2/clipper2_z.hpp
) )
@@ -26,6 +27,7 @@ set(CLIPPER2_SRC
Clipper2Lib/src/clipper.engine.cpp Clipper2Lib/src/clipper.engine.cpp
Clipper2Lib/src/clipper.offset.cpp Clipper2Lib/src/clipper.offset.cpp
Clipper2Lib/src/clipper.rectclip.cpp Clipper2Lib/src/clipper.rectclip.cpp
Clipper2Lib/src/clipper.triangulation.cpp
Clipper2Lib/src/clipper2_z.cpp Clipper2Lib/src/clipper2_z.cpp
) )
@@ -36,6 +38,9 @@ target_include_directories(Clipper2
PUBLIC Clipper2Lib/include PUBLIC Clipper2Lib/include
) )
# Engine nodes are allocated through tbbmalloc (see clipper.engine.cpp).
target_link_libraries(Clipper2 PRIVATE TBB::tbbmalloc)
if (WIN32) if (WIN32)
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(Clipper2 PRIVATE /W4 /WX) target_compile_options(Clipper2 PRIVATE /W4 /WX)
@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 12 May 2024 * * Date : 12 October 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Core Clipper Library structures and functions * * Purpose : Core Clipper Library structures and functions *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -251,6 +251,20 @@ namespace Clipper2Lib {
template <typename T> template <typename T>
using Paths = std::vector<Path<T>>; using Paths = std::vector<Path<T>>;
template <typename T, typename T2=T>
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
{
poly.emplace_back(p);
return poly;
}
template <typename T>
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
{
polys.emplace_back(p);
return polys;
}
using Path64 = Path<int64_t>; using Path64 = Path<int64_t>;
using PathD = Path<double>; using PathD = Path<double>;
using Paths64 = std::vector< Path64>; using Paths64 = std::vector< Path64>;
@@ -688,29 +702,28 @@ namespace Clipper2Lib {
return (x > 0) - (x < 0); return (x > 0) - (x < 0);
} }
struct MultiplyUInt64Result struct UInt128Struct
{ {
const uint64_t result = 0; const uint64_t lo = 0;
const uint64_t carry = 0; const uint64_t hi = 0;
bool operator==(const MultiplyUInt64Result& other) const bool operator==(const UInt128Struct& other) const
{ {
return result == other.result && carry == other.carry; return lo == other.lo && hi == other.hi;
};
}; };
inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835 };
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
{ {
// note to self - lamba expressions follow
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
const auto hi = [](uint64_t x) { return x >> 32; }; const auto hi = [](uint64_t x) { return x >> 32; };
const uint64_t x1 = lo(a) * lo(b); const uint64_t x1 = lo(a) * lo(b);
const uint64_t x2 = hi(a) * lo(b) + hi(x1); const uint64_t x2 = hi(a) * lo(b) + hi(x1);
const uint64_t x3 = lo(a) * hi(b) + lo(x2); const uint64_t x3 = lo(a) * hi(b) + lo(x2);
const uint64_t result = lo(x3) << 32 | lo(x1); return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3);
return { result, carry };
} }
// returns true if (and only if) a * b == c * d // returns true if (and only if) a * b == c * d
@@ -727,14 +740,50 @@ namespace Clipper2Lib {
const auto abs_c = static_cast<uint64_t>(std::abs(c)); const auto abs_c = static_cast<uint64_t>(std::abs(c));
const auto abs_d = static_cast<uint64_t>(std::abs(d)); const auto abs_d = static_cast<uint64_t>(std::abs(d));
const auto abs_ab = Multiply(abs_a, abs_b); const auto ab = MultiplyUInt64(abs_a, abs_b);
const auto abs_cd = Multiply(abs_c, abs_d); const auto cd = MultiplyUInt64(abs_c, abs_d);
// nb: it's important to differentiate 0 values here from other values // nb: it's important to differentiate 0 values here from other values
const auto sign_ab = TriSign(a) * TriSign(b); const auto sign_ab = TriSign(a) * TriSign(b);
const auto sign_cd = TriSign(c) * TriSign(d); const auto sign_cd = TriSign(c) * TriSign(d);
return abs_ab == abs_cd && sign_ab == sign_cd; return ab == cd && sign_ab == sign_cd;
#endif
}
template <typename T>
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3)
{
const auto a = pt2.x - pt1.x;
const auto b = pt3.y - pt2.y;
const auto c = pt2.y - pt1.y;
const auto d = pt3.x - pt2.x;
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
if (ab > cd) return 1;
else if (ab < cd) return -1;
else return 0;
#else
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
const auto sign_ab = TriSign(a) * TriSign(b);
const auto sign_cd = TriSign(c) * TriSign(d);
if (sign_ab == sign_cd)
{
int result;
if (ab.hi == cd.hi)
{
if (ab.lo == cd.lo) return 0;
result = (ab.lo > cd.lo) ? 1 : -1;
}
else result = (ab.hi > cd.hi) ? 1 : -1;
return (sign_ab > 0) ? result : -result;
}
return (sign_ab > sign_cd) ? 1 : -1;
#endif #endif
} }
@@ -838,6 +887,10 @@ namespace Clipper2Lib {
return Area<T>(poly) >= 0; return Area<T>(poly) >= 0;
} }
// GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
// be constrained to seg1. However, it's possible that 'ip' won't be inside
// seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
#if CLIPPER2_HI_PRECISION #if CLIPPER2_HI_PRECISION
// caution: this will compromise performance // caution: this will compromise performance
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253 // https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
@@ -845,7 +898,7 @@ namespace Clipper2Lib {
#define CC_MIN(x,y) ((x)>(y)?(y):(x)) #define CC_MIN(x,y) ((x)>(y)?(y):(x))
#define CC_MAX(x,y) ((x)<(y)?(y):(x)) #define CC_MAX(x,y) ((x)<(y)?(y):(x))
template<typename T> template<typename T>
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b, inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip) const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
{ {
double ln1dy = static_cast<double>(ln1b.y - ln1a.y); double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
@@ -891,11 +944,14 @@ namespace Clipper2Lib {
ip.x = originx + static_cast<T>(hitx); ip.x = originx + static_cast<T>(hitx);
ip.y = originy + static_cast<T>(hity); ip.y = originy + static_cast<T>(hity);
} }
#ifdef USINGZ
ip.z = 0;
#endif
return true; return true;
} }
#else #else
template<typename T> template<typename T>
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b, inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip) const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
{ {
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
@@ -913,6 +969,9 @@ namespace Clipper2Lib {
{ {
ip.x = static_cast<T>(ln1a.x + t * dx1); ip.x = static_cast<T>(ln1a.x + t * dx1);
ip.y = static_cast<T>(ln1a.y + t * dy1); ip.y = static_cast<T>(ln1a.y + t * dy1);
#ifdef USINGZ
ip.z = 0;
#endif
} }
return true; return true;
} }
@@ -949,21 +1008,44 @@ namespace Clipper2Lib {
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
const Point64& seg2a, const Point64& seg2b, bool inclusive = false) const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
{ {
double dy1 = static_cast<double>(seg1b.y - seg1a.y);
double dx1 = static_cast<double>(seg1b.x - seg1a.x);
double dy2 = static_cast<double>(seg2b.y - seg2a.y);
double dx2 = static_cast<double>(seg2b.x - seg2a.x);
double cp = dy1 * dx2 - dy2 * dx1;
if (cp == 0) return false; // ie parallel segments
if (inclusive) if (inclusive)
{ {
double res1 = CrossProduct(seg1a, seg2a, seg2b); //result **includes** segments that touch at an end point
double res2 = CrossProduct(seg1b, seg2a, seg2b); double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
if (res1 * res2 > 0) return false; if (t == 0) return true;
double res3 = CrossProduct(seg2a, seg1a, seg1b); if (t > 0)
double res4 = CrossProduct(seg2b, seg1a, seg1b); {
if (res3 * res4 > 0) return false; if (cp < 0 || t > cp) return false;
return (res1 || res2 || res3 || res4); // ensures not collinear
} }
else { else if (cp > 0 || t < cp) return false; // false when t more neg. than cp
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) && t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) * if (t == 0) return true;
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0); if (t > 0) return (cp > 0 && t <= cp);
else return (cp < 0 && t >= cp); // true when t less neg. than cp
}
else
{
//result **excludes** segments that touch at an end point
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
if (t == 0) return false;
if (t > 0)
{
if (cp < 0 || t >= cp) return false;
}
else if (cp > 0 || t <= cp ) return false; // false when t more neg. than cp
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
if (t == 0) return false;
if (t > 0) return (cp > 0 && t < cp);
else return (cp < 0 && t > cp); // true when t less neg. than cp
} }
} }
@@ -1051,7 +1133,7 @@ namespace Clipper2Lib {
val = 1 - val; // toggle val val = 1 - val; // toggle val
else else
{ {
double d = CrossProduct(*prev, *curr, pt); int d = CrossProductSign(*prev, *curr, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((d < 0) == is_above) val = 1 - val;
} }
@@ -1065,7 +1147,7 @@ namespace Clipper2Lib {
if (curr == cend) curr = cbegin; if (curr == cend) curr = cbegin;
if (curr == cbegin) prev = cend - 1; if (curr == cbegin) prev = cend - 1;
else prev = curr - 1; else prev = curr - 1;
double d = CrossProduct(*prev, *curr, pt); int d = CrossProductSign(*prev, *curr, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((d < 0) == is_above) val = 1 - val;
} }
@@ -15,6 +15,13 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
// Orca: engine nodes are allocated through tbbmalloc, see clipper.engine.cpp.
#define CLIPPER2_NODE_ALLOCATOR \
static void* operator new(size_t size); \
static void operator delete(void* ptr) noexcept; \
static void* operator new[](size_t size); \
static void operator delete[](void* ptr) noexcept;
#ifdef USINGZ #ifdef USINGZ
namespace Clipper2Lib_Z { namespace Clipper2Lib_Z {
#else #else
@@ -50,6 +57,7 @@ namespace Clipper2Lib {
} }
struct Vertex { struct Vertex {
CLIPPER2_NODE_ALLOCATOR
Point64 pt; Point64 pt;
Vertex* next = nullptr; Vertex* next = nullptr;
Vertex* prev = nullptr; Vertex* prev = nullptr;
@@ -57,6 +65,7 @@ namespace Clipper2Lib {
}; };
struct OutPt { struct OutPt {
CLIPPER2_NODE_ALLOCATOR
Point64 pt; Point64 pt;
OutPt* next = nullptr; OutPt* next = nullptr;
OutPt* prev = nullptr; OutPt* prev = nullptr;
@@ -81,6 +90,7 @@ namespace Clipper2Lib {
//OutRec: contains a path in the clipping solution. Edges in the AEL will //OutRec: contains a path in the clipping solution. Edges in the AEL will
//have OutRec pointers assigned when they form part of the clipping solution. //have OutRec pointers assigned when they form part of the clipping solution.
struct OutRec { struct OutRec {
CLIPPER2_NODE_ALLOCATOR
size_t idx = 0; size_t idx = 0;
OutRec* owner = nullptr; OutRec* owner = nullptr;
Active* front_edge = nullptr; Active* front_edge = nullptr;
@@ -106,6 +116,7 @@ namespace Clipper2Lib {
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
struct Active { struct Active {
CLIPPER2_NODE_ALLOCATOR
Point64 bot; Point64 bot;
Point64 top; Point64 top;
int64_t curr_x = 0; //current (updated at every new scanline) int64_t curr_x = 0; //current (updated at every new scanline)
@@ -133,6 +144,7 @@ namespace Clipper2Lib {
}; };
struct LocalMinima { struct LocalMinima {
CLIPPER2_NODE_ALLOCATOR
Vertex* vertex; Vertex* vertex;
PathType polytype; PathType polytype;
bool is_open; bool is_open;
@@ -303,6 +315,7 @@ namespace Clipper2Lib {
protected: protected:
PolyPath* parent_; PolyPath* parent_;
public: public:
CLIPPER2_NODE_ALLOCATOR
PolyPath(PolyPath* parent = nullptr): parent_(parent){} PolyPath(PolyPath* parent = nullptr): parent_(parent){}
virtual ~PolyPath() {}; virtual ~PolyPath() {};
//https://en.cppreference.com/w/cpp/language/rule_of_three //https://en.cppreference.com/w/cpp/language/rule_of_three
@@ -330,6 +343,7 @@ namespace Clipper2Lib {
//Even levels except level 0 //Even levels except level 0
return lvl && !(lvl & 1); return lvl && !(lvl & 1);
} }
// Area() of the namespace this header is compiled into (Clipper2Lib or Clipper2Lib_Z).
template<typename T> template<typename T>
static double Clipper2LibArea(const Path<T> &poly) static double Clipper2LibArea(const Path<T> &poly)
{ {
@@ -388,7 +402,8 @@ namespace Clipper2Lib {
double Area() const double Area() const
{ {
return std::accumulate(childs_.cbegin(), childs_.cend(), Clipper2LibArea<int64_t>(polygon_), return std::accumulate(childs_.cbegin(), childs_.cend(),
Clipper2LibArea<int64_t>(polygon_),
[](double a, const auto& child) {return a + child->Area(); }); [](double a, const auto& child) {return a + child->Area(); });
} }
@@ -462,7 +477,8 @@ namespace Clipper2Lib {
double Area() const double Area() const
{ {
return std::accumulate(childs_.begin(), childs_.end(), Clipper2LibArea<double>(polygon_), return std::accumulate(childs_.begin(), childs_.end(),
Clipper2LibArea<double>(polygon_),
[](double a, const auto& child) {return a + child->Area(); }); [](double a, const auto& child) {return a + child->Area(); });
} }
}; };
@@ -116,13 +116,10 @@ the four vertices that define the two segments that are intersecting.
#include "clipper2/clipper.engine.h" #include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h" #include "clipper2/clipper.offset.h"
#include "clipper2/clipper.rectclip.h" #include "clipper2/clipper.rectclip.h"
#include "clipper2/clipper.triangulation.h"
#include <cstdlib> #include <cstdlib>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib { namespace Clipper2Lib {
#endif
typedef int64_t* CPath64; typedef int64_t* CPath64;
typedef int64_t* CPaths64; typedef int64_t* CPaths64;
@@ -816,6 +813,24 @@ EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath6
return CreateCPathsFromPathsT(solution); return CreateCPathsFromPathsT(solution);
} }
EXTERN_DLL_EXPORT CPaths64 Triangulate64(const CPaths64 paths, bool use_delaunay)
{
Paths64 pp = ConvertCPathsToPathsT(paths);
Paths64 sol;
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
return CreateCPathsFromPathsT(sol);
}
EXTERN_DLL_EXPORT CPathsD TriangulateD(const CPathsD paths, int decimal_precison, bool use_delaunay)
{
if (decimal_precison < -8 || decimal_precison > 8) return nullptr;
const double scale = std::pow(10, decimal_precison);
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
Paths64 sol;
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
return CreateCPathsDFromPaths64(sol, 1 / scale);
}
#ifdef USINGZ #ifdef USINGZ
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt); typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt); typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 27 April 2024 * * Date : 5 March 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : This module provides a simple interface to the Clipper Library * * Purpose : This module provides a simple interface to the Clipper Library *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -13,14 +13,15 @@
#include "clipper2/clipper.core.h" #include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h" #include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h" #include "clipper2/clipper.offset.h"
#include "clipper2/clipper.minkowski.h"
#include "clipper2/clipper.rectclip.h" #include "clipper2/clipper.rectclip.h"
#include "clipper2/clipper.minkowski.h"
#include "clipper2/clipper.triangulation.h"
#include <type_traits> #include <type_traits>
#ifdef USINGZ #ifdef USINGZ
namespace Clipper2Lib_Z { namespace Clipper2Lib_Z {
#else #else
namespace Clipper2Lib { namespace Clipper2Lib {
#endif #endif
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
@@ -154,7 +155,7 @@
if (!delta) return paths; if (!delta) return paths;
if (error_code) return PathsD(); if (error_code) return PathsD();
const double scale = std::pow(10, precision); const double scale = std::pow(10, precision);
ClipperOffset clip_offset(miter_limit, arc_tolerance); ClipperOffset clip_offset(miter_limit, arc_tolerance * scale);
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et); clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
if (error_code) return PathsD(); if (error_code) return PathsD();
Paths64 solution; Paths64 solution;
@@ -355,6 +356,29 @@
#endif #endif
} }
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
while (current <= high && flags[current]) ++current;
if (current <= high) return current;
current = 0;
while (flags[current]) ++current;
return current;
}
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
else --current;
while (current > 0 && flags[current]) --current;
if (!flags[current]) return current;
current = high;
while (flags[current]) --current;
return current;
}
} // end details namespace } // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
@@ -615,29 +639,6 @@
return result; return result;
} }
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
while (current <= high && flags[current]) ++current;
if (current <= high) return current;
current = 0;
while (flags[current]) ++current;
return current;
}
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
else --current;
while (current > 0 && flags[current]) --current;
if (!flags[current]) return current;
current = high;
while (flags[current]) --current;
return current;
}
template <typename T> template <typename T>
inline Path<T> SimplifyPath(const Path<T> &path, inline Path<T> SimplifyPath(const Path<T> &path,
double epsilon, bool isClosedPath = true) double epsilon, bool isClosedPath = true)
@@ -669,13 +670,13 @@
start = curr; start = curr;
do do
{ {
curr = GetNext(curr, high, flags); curr = details::GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr); } while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break; if (curr == start) break;
} }
prior = GetPrior(curr, high, flags); prior = details::GetPrior(curr, high, flags);
next = GetNext(curr, high, flags); next = details::GetNext(curr, high, flags);
if (next == prior) break; if (next == prior) break;
// flag for removal the smaller of adjacent 'distances' // flag for removal the smaller of adjacent 'distances'
@@ -684,14 +685,14 @@
prior2 = prior; prior2 = prior;
prior = curr; prior = curr;
curr = next; curr = next;
next = GetNext(next, high, flags); next = details::GetNext(next, high, flags);
} }
else else
prior2 = GetPrior(prior, high, flags); prior2 = details::GetPrior(prior, high, flags);
flags[curr] = true; flags[curr] = true;
curr = next; curr = next;
next = GetNext(next, high, flags); next = details::GetNext(next, high, flags);
if (isClosedPath || ((curr != high) && (curr != 0))) if (isClosedPath || ((curr != high) && (curr != 0)))
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
@@ -716,6 +717,35 @@
return result; return result;
} }
template <typename T>
inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
{
// precondition: paths must not intersect, except for
// transient (and presumed 'micro') path intersections
PointInPolygonResult pip = PointInPolygonResult::IsOn;
for (const Point<T>& pt : path1)
{
switch (PointInPolygon(pt, path2))
{
case PointInPolygonResult::IsOutside:
if (pip == PointInPolygonResult::IsOutside) return false;
pip = PointInPolygonResult::IsOutside;
break;
case PointInPolygonResult::IsInside:
if (pip == PointInPolygonResult::IsInside) return true;
pip = PointInPolygonResult::IsInside;
break;
default:
break;
}
}
if (pip != PointInPolygonResult::IsInside) return false;
// result is likely true but check midpoint
Point<T> mp1 = GetBounds(path1).MidPoint();
return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
}
template <typename T> template <typename T>
inline void RDP(const Path<T> path, std::size_t begin, inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags) std::size_t end, double epsSqrd, std::vector<bool>& flags)
@@ -0,0 +1,30 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 6 December 2025 *
* Release : BETA RELEASE *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : Delaunay Triangulation *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
#ifndef CLIPPER_TRIANGULATION_H
#define CLIPPER_TRIANGULATION_H
#include <stack>
#include "clipper2/clipper.core.h"
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
enum class TriangulateResult { success, fail, no_polygons, paths_intersect };
// Triangulate - this function will not accept intesecting paths
TriangulateResult Triangulate(const Paths64& pp, Paths64& solution, bool useDelaunay = true);
TriangulateResult Triangulate(const PathsD& pp, int decPlaces, PathsD& solution, bool useDelaunay = true);
} // Clipper2Lib namespace
#endif // CLIPPER_TRIANGULATION_H
@@ -1,6 +1,6 @@
#ifndef CLIPPER_VERSION_H #ifndef CLIPPER_VERSION_H
#define CLIPPER_VERSION_H #define CLIPPER_VERSION_H
constexpr auto CLIPPER2_VERSION = "1.5.2"; constexpr auto CLIPPER2_VERSION = "2.0.1";
#endif // CLIPPER_VERSION_H #endif // CLIPPER_VERSION_H
@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 17 September 2024 * * Date : 5 November 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : This is the main polygon clipping module * * Purpose : This is the main polygon clipping module *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -10,6 +10,8 @@
#include "clipper2/clipper.engine.h" #include "clipper2/clipper.engine.h"
#include "clipper2/clipper.h" #include "clipper2/clipper.h"
#include <stdexcept> #include <stdexcept>
#include <new>
#include <oneapi/tbb/scalable_allocator.h>
// https://github.com/AngusJohnson/Clipper2/discussions/334 // https://github.com/AngusJohnson/Clipper2/discussions/334
// #discussioncomment-4248602 // #discussioncomment-4248602
@@ -27,6 +29,26 @@ namespace Clipper2Lib_Z {
namespace Clipper2Lib { namespace Clipper2Lib {
#endif #endif
// Orca: tbbmalloc scales far better than the default heap when all slicing threads clip at once.
static void* NodeAlloc(size_t size)
{
if (void* p = scalable_malloc(size)) return p;
throw std::bad_alloc();
}
#define CLIPPER2_DEFINE_NODE_ALLOCATOR(T) \
void* T::operator new(size_t size) { return NodeAlloc(size); } \
void T::operator delete(void* ptr) noexcept { scalable_free(ptr); } \
void* T::operator new[](size_t size) { return NodeAlloc(size); } \
void T::operator delete[](void* ptr) noexcept { scalable_free(ptr); }
CLIPPER2_DEFINE_NODE_ALLOCATOR(Vertex)
CLIPPER2_DEFINE_NODE_ALLOCATOR(OutPt)
CLIPPER2_DEFINE_NODE_ALLOCATOR(OutRec)
CLIPPER2_DEFINE_NODE_ALLOCATOR(Active)
CLIPPER2_DEFINE_NODE_ALLOCATOR(LocalMinima)
CLIPPER2_DEFINE_NODE_ALLOCATOR(PolyPath)
#undef CLIPPER2_DEFINE_NODE_ALLOCATOR
static const Rect64 invalid_rect = Rect64(false); static const Rect64 invalid_rect = Rect64(false);
// Every closed path (ie polygon) is made up of a series of vertices forming edge // Every closed path (ie polygon) is made up of a series of vertices forming edge
@@ -482,8 +504,7 @@ namespace Clipper2Lib {
inline void SetOwner(OutRec* outrec, OutRec* new_owner) inline void SetOwner(OutRec* outrec, OutRec* new_owner)
{ {
//precondition1: new_owner is never null //precondition1: new_owner is never null
while (new_owner->owner && !new_owner->owner->pts) new_owner->owner = GetRealOutRec(new_owner->owner);
new_owner->owner = new_owner->owner->owner;
OutRec* tmp = new_owner; OutRec* tmp = new_owner;
while (tmp && tmp != outrec) tmp = tmp->owner; while (tmp && tmp != outrec) tmp = tmp->owner;
if (tmp) new_owner->owner = outrec->owner; if (tmp) new_owner->owner = outrec->owner;
@@ -536,9 +557,9 @@ namespace Clipper2Lib {
val = 1 - val; // toggle val val = 1 - val; // toggle val
else else
{ {
double d = CrossProduct(op2->prev->pt, op2->pt, pt); int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (i == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((i < 0) == is_above) val = 1 - val;
} }
is_above = !is_above; is_above = !is_above;
op2 = op2->next; op2 = op2->next;
@@ -546,9 +567,9 @@ namespace Clipper2Lib {
if (is_above != starting_above) if (is_above != starting_above)
{ {
double d = CrossProduct(op2->prev->pt, op2->pt, pt); int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn; if (i == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val; if ((i < 0) == is_above) val = 1 - val;
} }
if (val == 0) return PointInPolygonResult::IsOutside; if (val == 0) return PointInPolygonResult::IsOutside;
@@ -578,29 +599,30 @@ namespace Clipper2Lib {
return result; return result;
} }
inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)
{ {
// we need to make some accommodation for rounding errors // this function accommodates rounding errors that
// so we won't jump if the first vertex is found outside // can cause path micro intersections
PointInPolygonResult result; PointInPolygonResult pip = PointInPolygonResult::IsOn;
int outside_cnt = 0;
OutPt* op = op1; OutPt* op = op1;
do do {
switch (PointInOpPolygon(op->pt, op2))
{ {
result = PointInOpPolygon(op->pt, op2); case PointInPolygonResult::IsOutside:
if (result == PointInPolygonResult::IsOutside) ++outside_cnt; if (pip == PointInPolygonResult::IsOutside) return false;
else if (result == PointInPolygonResult::IsInside) --outside_cnt; pip = PointInPolygonResult::IsOutside;
break;
case PointInPolygonResult::IsInside:
if (pip == PointInPolygonResult::IsInside) return true;
pip = PointInPolygonResult::IsInside;
break;
default: break;
}
op = op->next; op = op->next;
} while (op != op1 && std::abs(outside_cnt) < 2); } while (op != op1);
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); // result unclear, so try again using cleaned paths
// since path1's location is still equivocal, check its midpoint return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
Path64 path2 = GetCleanPath(op2);
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
} }
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void AddLocMin(LocalMinimaList& list, void AddLocMin(LocalMinimaList& list,
Vertex& vert, PathType polytype, bool is_open) Vertex& vert, PathType polytype, bool is_open)
@@ -1126,21 +1148,19 @@ namespace Clipper2Lib {
return newcomer.curr_x > resident.curr_x; return newcomer.curr_x > resident.curr_x;
//get the turning direction a1.top, a2.bot, a2.top //get the turning direction a1.top, a2.bot, a2.top
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
if (d != 0) return d < 0; if (i != 0) return i < 0;
//edges must be collinear to get here //edges must be collinear to get here
//for starting open paths, place them according to //for starting open paths, place them according to
//the direction they're about to turn //the direction they're about to turn
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
{ {
return CrossProduct(newcomer.bot, return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
resident.top, NextVertex(resident)->pt) <= 0;
} }
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
{ {
return CrossProduct(newcomer.bot, return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
newcomer.top, NextVertex(newcomer)->pt) >= 0;
} }
int64_t y = newcomer.bot.y; int64_t y = newcomer.bot.y;
@@ -1155,7 +1175,7 @@ namespace Clipper2Lib {
resident.bot, resident.top)) return true; resident.bot, resident.top)) return true;
else else
//compare turning direction of the alternate bound //compare turning direction of the alternate bound
return (CrossProduct(PrevPrevVertex(resident)->pt, return (CrossProductSign(PrevPrevVertex(resident)->pt,
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
} }
@@ -1565,7 +1585,7 @@ namespace Clipper2Lib {
FixSelfIntersects(outrec); FixSelfIntersects(outrec);
} }
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
{ {
// splitOp.prev -> splitOp && // splitOp.prev -> splitOp &&
// splitOp.next -> splitOp.next.next are intersecting // splitOp.next -> splitOp.next.next are intersecting
@@ -1574,7 +1594,7 @@ namespace Clipper2Lib {
outrec->pts = prevOp; outrec->pts = prevOp;
Point64 ip; Point64 ip;
GetSegmentIntersectPt(prevOp->pt, splitOp->pt, GetLineIntersectPt(prevOp->pt, splitOp->pt,
splitOp->next->pt, nextNextOp->pt, ip); splitOp->next->pt, nextNextOp->pt, ip);
#ifdef USINGZ #ifdef USINGZ
@@ -1630,7 +1650,7 @@ namespace Clipper2Lib {
if (using_polytree_) if (using_polytree_)
{ {
if (Path1InsidePath2(prevOp, newOp)) if (Path2ContainsPath1(prevOp, newOp))
{ {
newOr->splits = new OutRecList(); newOr->splits = new OutRecList();
newOr->splits->emplace_back(outrec); newOr->splits->emplace_back(outrec);
@@ -1652,20 +1672,33 @@ namespace Clipper2Lib {
void ClipperBase::FixSelfIntersects(OutRec* outrec) void ClipperBase::FixSelfIntersects(OutRec* outrec)
{ {
OutPt* op2 = outrec->pts; OutPt* op2 = outrec->pts;
if (op2->prev == op2->next->next)
return; // because triangles can't self-intersect
for (; ; ) for (; ; )
{ {
// triangles can't self-intersect
if (op2->prev == op2->next->next) break;
if (SegmentsIntersect(op2->prev->pt, if (SegmentsIntersect(op2->prev->pt,
op2->pt, op2->next->pt, op2->next->next->pt)) op2->pt, op2->next->pt, op2->next->next->pt))
{
if (SegmentsIntersect(op2->prev->pt,
op2->pt, op2->next->next->pt, op2->next->next->next->pt))
{
// adjacent intersections (ie a micro self-intersections)
op2 = DuplicateOp(op2, false);
op2->pt = op2->next->next->next->pt;
op2 = op2->next;
}
else
{ {
if (op2 == outrec->pts || op2->next == outrec->pts) if (op2 == outrec->pts || op2->next == outrec->pts)
outrec->pts = outrec->pts->prev; outrec->pts = outrec->pts->prev;
DoSplitOp(outrec, op2); DoSplitOp(outrec, op2);
if (!outrec->pts) break; if (!outrec->pts) break;
op2 = outrec->pts; op2 = outrec->pts;
if (op2->prev == op2->next->next)
break; // again, because triangles can't self-intersect
continue; continue;
} }
}
else else
op2 = op2->next; op2 = op2->next;
@@ -2112,9 +2145,8 @@ namespace Clipper2Lib {
e->prev_in_sel = e->prev_in_ael; e->prev_in_sel = e->prev_in_ael;
e->next_in_sel = e->next_in_ael; e->next_in_sel = e->next_in_ael;
e->jump = e->next_in_sel; e->jump = e->next_in_sel;
if (e->join_with == JoinWith::Left) // it is safe to ignore 'joined' edges here because
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications // if necessary they will be split in IntersectEdges()
else
e->curr_x = TopX(*e, top_y); e->curr_x = TopX(*e, top_y);
e = e->next_in_ael; e = e->next_in_ael;
} }
@@ -2262,15 +2294,14 @@ namespace Clipper2Lib {
void MoveSplits(OutRec* fromOr, OutRec* toOr) void MoveSplits(OutRec* fromOr, OutRec* toOr)
{ {
if (!fromOr->splits) return;
if (!toOr->splits) toOr->splits = new OutRecList(); if (!toOr->splits) toOr->splits = new OutRecList();
OutRecList::iterator orIter = fromOr->splits->begin(); OutRecList::iterator orIter = fromOr->splits->begin();
for (; orIter != fromOr->splits->end(); ++orIter) for (; orIter != fromOr->splits->end(); ++orIter)
if (toOr != *orIter) // #987
toOr->splits->emplace_back(*orIter); toOr->splits->emplace_back(*orIter);
fromOr->splits->clear(); fromOr->splits->clear();
} }
void ClipperBase::ProcessHorzJoins() void ClipperBase::ProcessHorzJoins()
{ {
for (const HorzJoin& j : horz_join_list_) for (const HorzJoin& j : horz_join_list_)
@@ -2300,7 +2331,7 @@ namespace Clipper2Lib {
if (using_polytree_) //#498, #520, #584, D#576, #618 if (using_polytree_) //#498, #520, #584, D#576, #618
{ {
if (Path1InsidePath2(or1->pts, or2->pts)) if (Path2ContainsPath1(or1->pts, or2->pts))
{ {
//swap or1's & or2's pts //swap or1's & or2's pts
OutPt* tmp = or1->pts; OutPt* tmp = or1->pts;
@@ -2311,7 +2342,7 @@ namespace Clipper2Lib {
//or2 is now inside or1 //or2 is now inside or1
or2->owner = or1; or2->owner = or1;
} }
else if (Path1InsidePath2(or2->pts, or1->pts)) else if (Path2ContainsPath1(or2->pts, or1->pts))
{ {
or2->owner = or1; or2->owner = or1;
} }
@@ -2324,12 +2355,13 @@ namespace Clipper2Lib {
else else
or2->owner = or1; or2->owner = or1;
} }
else else // joining, not splitting
{ {
or2->pts = nullptr; or2->pts = nullptr;
if (using_polytree_) if (using_polytree_)
{ {
SetOwner(or2, or1); SetOwner(or2, or1);
if (or2->splits)
MoveSplits(or2, or1); //#618 MoveSplits(or2, or1); //#618
} }
else else
@@ -2350,7 +2382,7 @@ namespace Clipper2Lib {
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
{ {
Point64 ip; Point64 ip;
if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip)) if (!GetLineIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
ip = Point64(e1.curr_x, top_y); //parallel edges ip = Point64(e1.curr_x, top_y); //parallel edges
//rounding errors can occasionally place the calculated intersection //rounding errors can occasionally place the calculated intersection
@@ -2934,22 +2966,28 @@ namespace Clipper2Lib {
bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits) bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
{ {
for (auto split : *splits) // nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
for (size_t idx = 0; idx < splits->size(); ++idx)
{ {
OutRec* split = (*splits)[idx];
if (!split->pts && split->splits &&
CheckSplitOwner(outrec, split->splits)) return true; //#942
split = GetRealOutRec(split); split = GetRealOutRec(split);
if(!split || split == outrec || split->recursive_split == outrec) continue; if (!split || split == outrec || split->recursive_split == outrec) continue;
split->recursive_split = outrec; // prevent infinite loops split->recursive_split = outrec; // prevent infinite loops
if (split->splits && CheckSplitOwner(outrec, split->splits)) if (split->splits && CheckSplitOwner(outrec, split->splits))
return true; return true;
else if (CheckBounds(split) &&
IsValidOwner(outrec, split) && if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
split->bounds.Contains(outrec->bounds) && !Path2ContainsPath1(outrec->pts, split->pts)) continue;
Path1InsidePath2(outrec->pts, split->pts))
{ if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
outrec->owner = split; //found in split split->owner = outrec->owner;
outrec->owner = split;
return true; return true;
}
} }
return false; return false;
} }
@@ -2960,13 +2998,12 @@ namespace Clipper2Lib {
// post-condition: if a valid path, outrec will have a polypath // post-condition: if a valid path, outrec will have a polypath
if (outrec->polypath || outrec->bounds.IsEmpty()) return; if (outrec->polypath || outrec->bounds.IsEmpty()) return;
while (outrec->owner) while (outrec->owner)
{ {
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break; if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
if (outrec->owner->pts && CheckBounds(outrec->owner) && if (outrec->owner->pts && CheckBounds(outrec->owner) &&
outrec->owner->bounds.Contains(outrec->bounds) && outrec->owner->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break; Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;
outrec->owner = outrec->owner->owner; outrec->owner = outrec->owner->owner;
} }
@@ -3029,6 +3066,7 @@ namespace Clipper2Lib {
{ {
OutRec* outrec = outrec_list_[i]; OutRec* outrec = outrec_list_[i];
if (!outrec || !outrec->pts) continue; if (!outrec || !outrec->pts) continue;
if (outrec->is_open) if (outrec->is_open)
{ {
Path64 path; Path64 path;
@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 22 January 2025 * * Date : 11 October 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) * * Purpose : Path Offset (Inflate/Shrink) *
@@ -37,22 +37,28 @@ const double arc_const = 0.002; // <-- 1/500
// Miscellaneous methods // Miscellaneous methods
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths) void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
{ {
std::optional<size_t> result; idx.reset();
Point64 botPt = Point64(INT64_MAX, INT64_MIN); Point64 botPt = Point64(INT64_MAX, INT64_MIN);
for (size_t i = 0; i < paths.size(); ++i) for (size_t i = 0; i < paths.size(); ++i)
{ {
double a = MAX_DBL;
for (const Point64& pt : paths[i]) for (const Point64& pt : paths[i])
{ {
if ((pt.y < botPt.y) || if ((pt.y < botPt.y) ||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue; ((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
result = i; if (a == MAX_DBL)
{
a = Area(paths[i]);
if (a == 0) break; // invalid closed path, so break from inner loop
is_neg_area = a < 0;
}
idx = i;
botPt.x = pt.x; botPt.x = pt.x;
botPt.y = pt.y; botPt.y = pt.y;
} }
} }
return result;
} }
inline double Hypot(double x, double y) inline double Hypot(double x, double y)
@@ -145,15 +151,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
if (end_type == EndType::Polygon) if (end_type == EndType::Polygon)
{ {
lowest_path_idx = GetLowestClosedPathIdx(paths_in); bool is_neg_area;
GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area);
// the lowermost path must be an outer path, so if its orientation is negative, // the lowermost path must be an outer path, so if its orientation is negative,
// then flag the whole group is 'reversed' (will negate delta etc.) // then flag the whole group is 'reversed' (will negate delta etc.)
// as this is much more efficient than reversing every path. // as this is much more efficient than reversing every path.
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0; is_reversed = lowest_path_idx.has_value() && is_neg_area;
} }
else else
{ {
lowest_path_idx = std::nullopt; lowest_path_idx.reset();
is_reversed = false; is_reversed = false;
} }
} }
@@ -236,7 +243,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{ {
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_); PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
PointD pt = ptQ; PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
//get the second intersect point through reflecion //get the second intersect point through reflecion
path_out.emplace_back(ReflectPoint(pt, ptQ)); path_out.emplace_back(ReflectPoint(pt, ptQ));
path_out.emplace_back(pt); path_out.emplace_back(pt);
@@ -245,7 +252,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
{ {
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
PointD pt = ptQ; PointD pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt); GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
path_out.emplace_back(pt); path_out.emplace_back(pt);
//get the second intersect point through reflecion //get the second intersect point through reflecion
path_out.emplace_back(ReflectPoint(pt, ptQ)); path_out.emplace_back(ReflectPoint(pt, ptQ));
@@ -291,7 +298,8 @@ void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle
#else #else
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y); path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#endif #endif
int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 // Orca: round the step count like Clipper1 did, so round offsets keep their vertices.
int steps = std::max(static_cast<int>(std::round(steps_per_rad_ * std::abs(angle))), 1);
for (int i = 1; i < steps; ++i) // ie 1 less than steps for (int i = 1; i < steps; ++i) // ie 1 less than steps
{ {
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
@@ -366,10 +374,30 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
DoSquare(path, j, k); DoSquare(path, j, k);
} }
// Orca: join concave corners at the crossing of both edge offsets where safe, 3-point loops make dense inward offsets slow.
static bool OffsetConcaveCrossing(const Path64& path, const PathD& norms, size_t j, size_t k, size_t next,
double delta, Path64& path_out)
{
const double sin_a = CrossProduct(norms[j], norms[k]);
const double cos_a = DotProduct(norms[j], norms[k]);
if (cos_a <= -0.999 || sin_a * delta >= 0) return false;
const double x = std::fabs(delta * sin_a) / (1 + cos_a);
if (4 * x * x > DistanceSqr(path[k], path[j]) || 4 * x * x > DistanceSqr(path[j], path[next])) return false;
const double q = delta / (1 + cos_a);
#ifdef USINGZ
path_out.emplace_back(path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q, path[j].z);
#else
path_out.emplace_back(path[j].x + (norms[k].x + norms[j].x) * q, path[j].y + (norms[k].y + norms[j].y) * q);
#endif
return true;
}
void ClipperOffset::OffsetPolygon(Group& group, const Path64& path) void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
{ {
path_out.clear(); path_out.clear();
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j) for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
if (deltaCallback64_ || path[j] == path[k] ||
!OffsetConcaveCrossing(path, norms, j, k, j + 1 == path.size() ? 0 : j + 1, group_delta_, path_out))
OffsetPoint(group, path, j, k); OffsetPoint(group, path, j, k);
solution->emplace_back(path_out); solution->emplace_back(path_out);
} }
@@ -604,7 +632,7 @@ void ClipperOffset::ExecuteInternal(double delta)
bool paths_reversed = CheckReverseOrientation(); bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ... //clean up self-intersections ...
Clipper64 c; Clipper64 c;
c.PreserveCollinear(false); c.PreserveCollinear(preserve_collinear_);
//the solution should retain the orientation of the input //the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed); c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ #ifdef USINGZ
@@ -1,8 +1,8 @@
/******************************************************************************* /*******************************************************************************
* Author : Angus Johnson * * Author : Angus Johnson *
* Date : 5 July 2024 * * Date : 11 October 2025 *
* Website : https://www.angusj.com * * Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 * * Copyright : Angus Johnson 2010-2025 *
* Purpose : FAST rectangular clipping * * Purpose : FAST rectangular clipping *
* License : https://www.boost.org/LICENSE_1_0.txt * * License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/ *******************************************************************************/
@@ -77,8 +77,8 @@ namespace Clipper2Lib {
bool GetSegmentIntersection(const Point64& p1, bool GetSegmentIntersection(const Point64& p1,
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip) const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
{ {
double res1 = CrossProduct(p1, p3, p4); int res1 = CrossProductSign(p1, p3, p4);
double res2 = CrossProduct(p2, p3, p4); int res2 = CrossProductSign(p2, p3, p4);
if (res1 == 0) if (res1 == 0)
{ {
ip = p1; ip = p1;
@@ -97,8 +97,8 @@ namespace Clipper2Lib {
} }
if ((res1 > 0) == (res2 > 0)) return false; if ((res1 > 0) == (res2 > 0)) return false;
double res3 = CrossProduct(p3, p1, p2); int res3 = CrossProductSign(p3, p1, p2);
double res4 = CrossProduct(p4, p1, p2); int res4 = CrossProductSign(p4, p1, p2);
if (res3 == 0) if (res3 == 0)
{ {
ip = p3; ip = p3;
@@ -116,7 +116,7 @@ namespace Clipper2Lib {
if ((res3 > 0) == (res4 > 0)) return false; if ((res3 > 0) == (res4 > 0)) return false;
// segments must intersect to get here // segments must intersect to get here
return GetSegmentIntersectPt(p1, p2, p3, p4, ip); return GetLineIntersectPt(p1, p2, p3, p4, ip);
} }
inline bool GetIntersection(const Path64& rectPath, inline bool GetIntersection(const Path64& rectPath,
@@ -227,7 +227,7 @@ namespace Clipper2Lib {
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp) const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
{ {
if (AreOpposites(prev, curr)) if (AreOpposites(prev, curr))
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
else else
return HeadingClockwise(prev, curr); return HeadingClockwise(prev, curr);
} }
File diff suppressed because it is too large Load Diff
@@ -6,3 +6,4 @@
#include "clipper.engine.cpp" #include "clipper.engine.cpp"
#include "clipper.offset.cpp" #include "clipper.offset.cpp"
#include "clipper.rectclip.cpp" #include "clipper.rectclip.cpp"
#include "clipper.triangulation.cpp"
@@ -132,14 +132,14 @@ template<>
inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&) inline void offset(Slic3r::ExPolygon& sh, coord_t distance, const PolygonTag&)
{ {
#define DISABLE_BOOST_OFFSET #define DISABLE_BOOST_OFFSET
auto res = Slic3r::offset_ex(sh, distance, Slic3r::ClipperLib::jtSquare); auto res = Slic3r::offset_ex(sh, distance, Slic3r::jtSquare);
if (!res.empty()) sh = res.front(); if (!res.empty()) sh = res.front();
} }
template<> template<>
inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&) inline void offset(Slic3r::Polygon& sh, coord_t distance, const PathTag&)
{ {
auto res = Slic3r::offset(sh, distance, Slic3r::ClipperLib::jtSquare); auto res = Slic3r::offset(sh, distance, Slic3r::jtSquare);
if (!res.empty()) sh = res.front(); if (!res.empty()) sh = res.front();
} }
-4
View File
@@ -19,11 +19,7 @@ if(Qhull_FOUND)
message(STATUS "Using qhull from system.") message(STATUS "Using qhull from system.")
if(SLIC3R_STATIC) if(SLIC3R_STATIC)
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhullstatic_r" RelWithDebInfo Release) slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhullstatic_r" RelWithDebInfo Release)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
target_link_libraries(qhull INTERFACE Qhull::qhullcpp_d Qhull::qhullstatic_rd)
else()
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
endif()
else() else()
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release) slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release)
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r)
+131
View File
@@ -0,0 +1,131 @@
# Polygon Clipping — High Level Design
## Purpose and scope
Almost every stage of slicing works on 2D regions: slices, perimeters, infill
areas, bridges, supports and brims are all produced by boolean operations and
offsets on polygons. libslic3r does this through two interfaces, both built on
the Clipper2 library vendored in `deps_src/clipper2`:
- `ClipperUtils` (`src/libslic3r/ClipperUtils.hpp`) takes and returns Slic3r
geometry: `Polygon(s)`, `ExPolygon(s)`, `Polyline(s)`, `Lines` and
`Surfaces`. It provides unions, intersections, differences and xor, closed
and open offsets, morphological opening and closing, variable width offsets
and polyline clipping.
- `ClipperZUtils` (`src/libslic3r/ClipperZUtils.hpp`) clips paths whose
vertices carry a Z value, which callers use to tag vertices with a source
index or an extrusion width.
No other code calls Clipper2.
`ClipperUtils` declares its own `JoinType`, `EndType`, `PolyFillType` and
`ClipType` enums and maps them to Clipper2's. Every call builds its own
Clipper2 objects and shares no state, so slicing threads can clip
concurrently.
Clipping is one of the largest costs of slicing, and nearly all of it goes
through `ClipperUtils`. The layer is therefore designed for throughput as much
as for predictable geometry.
## Vendored Clipper2
`deps_src/clipper2` builds the static target `Clipper2`. It carries four
changes to the upstream sources that must be carried over when Clipper2 is
updated. The namespace switch sits at the top of every header and source, the
other three are marked with `Orca:` comments.
| Change | Files | Why |
| --- | --- | --- |
| Z build in its own namespace | all headers and sources, `clipper2_z.cpp`, `clipper2_z.hpp` | The library is compiled a second time with `USINGZ` in namespace `Clipper2Lib_Z`, so the 2D and the Z variants link into one binary. |
| Engine nodes from tbbmalloc | `clipper.engine.h`, `clipper.engine.cpp` | Vertices, active edges, output points and records, local minima and `PolyTree` nodes are allocated one by one. `CLIPPER2_NODE_ALLOCATOR` routes them through `scalable_malloc`, because the default heap does not scale when all slicing threads clip at once. |
| Concave joins at the edge crossing | `clipper.offset.cpp` | For closed paths, a concave corner is joined at the crossing of the two offset edges when that point lies within half of both adjacent edges. The upstream 3-point loop makes inward offsets of dense curves very slow to union. |
| Rounded arc steps | `clipper.offset.cpp` | Round joins use the rounded number of steps, not the ceiling, which keeps the vertex count of round offsets that the rest of the code is tuned for. |
## ClipperUtils semantics
The callers of `ClipperUtils` rely on a fixed set of behaviours. Where
Clipper2 behaves differently by default, the wrapper adjusts it.
### Booleans
- The fill rule is non-zero unless the function takes a `PolyFillType`. One
rule applies to both subject and clip; Clipper2 has no per-operand rule.
- Collinear vertices are removed from the result. Clipper2 keeps them by
default, so every boolean sets `PreserveCollinear(false)`.
- Outer contours are CCW and holes are CW. No output contour touches
itself: where one would pass twice through a vertex, it is split there into
two contours.
- `ExPolygons` results are built from one `PolyTree64` pass. An island inside
a hole becomes an `ExPolygon` of its own.
- `ApplySafetyOffset::Yes` grows the clip polygons by `ClipperSafetyOffset`
before an intersection or a difference, so that edges shared by subject and
clip do not leave slivers.
- Open polylines are clipped with the non-zero rule and keep their direction.
### Offsets
- Before offsetting, input vertices closer than
`ClipperOffsetShortestEdgeFactor` × |delta| to the previously kept vertex
are dropped. This bounds the work on dense contours, and the error it
introduces is far below the offset distance.
- The miter limit is at least 2. For `jtRound`, a positive `miterLimit`
argument is the arc tolerance, capped at |delta| / 4, and 0.25 is used
otherwise. Other joins use the smaller of 0.25 and |delta| / 4 for round end
caps.
- A single `Polygon` keeps its orientation: a CCW polygon grows with a
positive delta, a CW polygon is a hole and shrinks.
- `Polygons` follow the same rule per path. When every CW path lies strictly
inside the bounding box of a CCW path, which is the usual case of contours
with their holes, all paths are offset in one Clipper2 group. Otherwise
each path is offset on its own and the results are united, with the
non-zero rule when growing and the positive rule when shrinking.
- `ExPolygons` and `Surfaces` are offset as one group after the contours are
oriented CCW and the holes CW, whatever their input orientation.
- Zero-area paths vanish under a negative offset instead of growing.
- Polyline offsets use the requested end type. Clipper2 already unites the
result, so no further union is done.
### Coordinate range
Clipper2 computes intersections and slopes in doubles, which hold integers
exactly only up to 2^53 (about 9e15 units, 9,000 km). Geometry passed to
`ClipperUtils` must stay well inside that range; near the int64 limit the
results shift by hundreds of units. This is why the arrange `InfiniteBed` is a
box of ±2^50 units around its centre rather than libnest2d's infinite box,
which reaches ±2.3e18.
## ClipperZUtils
`ZPoint` is a `Vec3crd`, and a `ZPath` is a vector of them.
`clip_zpaths()` runs one boolean with the non-zero rule on the Clipper2 Z
build. The subject may be open, the clip is closed, and the result lists the
closed paths before the open ones.
The Z of each output vertex follows these rules:
- An input vertex keeps its Z.
- An intersection that lies on an end point of one of the two crossing edges
takes that end point's Z, preferring the subject edge.
- Any other intersection gets its Z from the callback, which receives both
crossing edges, the subject edge first.
Clipper2 calls the callback only when it creates an output vertex at an
intersection, not for every crossing it processes. A callback that records
intersections, like `ClipperZIntersectionVisitor`, therefore sees only those.
The users are:
| User | Z carries |
| --- | --- |
| `Algorithm::wave_seeds()` (region expansion) | source and boundary index; intersections get a negative index into the visitor's list of crossing pairs |
| `Algorithm::split_line()` | index of the source vertex; an intersection gets the negated index of its source edge, so the pieces can be put back in path order |
| `PerimeterGenerator` overhang and top-surface clipping of Arachne walls | extrusion width, interpolated along the edge at intersections |
| Tree support anchors in `SupportCommon` | index of the source contour, -1 at intersections |
| `extrusion_paths_append()` | extrusion width, turned into extrusion paths |
## Testing
`tests/libslic3r/test_clipper_utils.cpp` and `test_clipper_offset.cpp` cover
the wrapper's booleans, orientation and offset rules. The perimeter, support
and region expansion users are exercised by the slicing tests in
`tests/fff_print`.
+93
View File
@@ -0,0 +1,93 @@
# 3D Scene Benchmark: High Level Design
## Why it exists
Rendering changes, such as the realistic view, shadows or SSAO, need a number to compare
before and after, and user reports of a slow viewport need a way to say how slow. The FPS
overlay and the render timings overlay show live values while someone drags the camera,
which varies from run to run with the model, the path of the mouse and the view.
The benchmark renders a fixed model along a fixed camera path in both 3D views, so two
runs on the same machine differ only by the code or the settings, and prints a report that
can be pasted into an issue.
## What it does
`run_scene_benchmark()` in `src/slic3r/GUI/SceneBenchmark.cpp` is reached from Help >
Benchmark 3D Scene, the command palette and Preferences > Graphics. After a confirmation
it starts a new project, which asks to save the current one if needed, loads the
OrcaSliced Combo handy model and arranges it. A small dialog in a corner of the 3D view
then shows the progress; every other window is disabled until the run ends, so a click
cannot change the scene being measured. Cancel or Esc stops the run.
The run goes through these stages, driven by a timer while it waits and by idle events
while it renders:
1. Loading: waits until the UI job worker is idle, so the arrange job has moved the
objects. The orbit target is the center of the objects on the current plate, and the
base zoom fits their bounding box in the viewport.
2. Prepare: renders the scene in the Prepare view.
3. Slicing: slices the plate and switches to Preview, then waits for the G-code preview
to load. If slicing fails, the report holds Prepare alone.
4. Preview: renders the scene in the Preview view, with the slicing progress notification
hidden.
5. Layers: renders the Preview view again while the layer slider moves, which is what
makes dragging it feel slow on large prints.
The dialog then shows the report, with a button to copy it. A scene cut short, because its
view was hidden, is left out of the report.
## Rendering a scene
Each scene renders 30 warm-up frames, then the camera path twice, 360 frames each time.
- The first pass times the frames. A frame's time is the interval between the starts of
consecutive benchmark frames, so it includes the event loop between them.
- The second pass averages the render timings. The frame profiler flushes the GL command
queue after each section, which slows a frame down, so it only runs in this pass.
`FrameProfiler::start_averaging()` flags every frame begun afterwards, and
`finish_averaging()` waits for the flagged frames still on the GPU and returns the mean
CPU and GPU time of each section per profiled frame.
- A section's GPU time is taken between a timestamp before its commands and one after
them. The first is only sent along with those commands, so when the GPU finishes a
section before the CPU has issued the next one, the wait counts in neither.
The dialog renders one frame per idle event by calling `GLCanvas3D::render()`, which
redraws the whole scene. While `GLCanvas3D::set_benchmarking()` is on, the canvas does not
render from its own idle handler, so no other frame is drawn in between, and it skips the
picking pass and the FPS and render timings overlays, which depend on the mouse and on
preferences. The FPS cap does not apply, since it only paces idle redraws.
VSync is turned off for the scene through `wxGLCanvas::SetSwapInterval(0)`, so the frame
rate is what the GPU and CPU can reach rather than the display's refresh rate, and the
previous interval is restored afterwards. When the platform cannot report the current
interval (EGL), it is left as it is and the report says so.
The camera path makes two turns around the target while the view rises three times from
25 degrees below the plate to 85 degrees above it and the zoom goes twice between 0.6 and
1.4 times the base zoom. The camera stays at the default distance, so the perspective is
the same in every run. The camera the scene started with is restored at its end.
The Layers scene holds the camera at the start of that path and moves the top of the layer
slider instead, from the last layer down to the first and back up in each pass. It goes
through `IMSlider::SetHigherValue()`, as a drag does, so every frame applies a new layer
range to the toolpaths and the objects before drawing them, including a new shadow map when
the shadows are static. Its warm-up frames lead into the start of the path, so the slider
moves in every frame. The slider position it started from is restored at its end.
## The report
The report is plain English text, so it reads the same in every language:
- The version and build commit, the GPU and OpenGL version, the viewport size and camera
type, and the graphics settings that change the cost of a frame: MSAA samples as read
from the framebuffer, FXAA, the scene cache, VSync and the realistic view options.
- The printer and process presets the model was sliced with, marked when they have
unsaved changes, and the toolpath vertices and layers they produced, since the Preview
scenes cost more with more toolpaths.
- For each scene, the average FPS and the average, median, 95th percentile, 99th
percentile and maximum frame time. Percentiles are nearest-rank, so each is a measured
frame (`frame_time_stats()`).
- For each scene, the render timings table: the CPU and GPU milliseconds of each section
of a frame, and their total. Without timer queries (OpenGL 3.3 or `ARB_timer_query`) the
table says that the driver does not support them.
+105
View File
@@ -0,0 +1,105 @@
# Section view — High Level Design
## Purpose and scope
Section view hides whatever lies between the camera and a plane, so the user can look inside
objects in Prepare and in the assembly view, and inside the toolpaths in Preview. It is a view
setting: it changes nothing in the model, the slice or the project file, and it does not reach
plate thumbnails.
The user controls it from the section button of the canvas toolbar in the bottom left corner of
the 3D view. The button opens a panel above it with a slider for the depth of the cut, a "Set
viewing angle" button that turns the plane to face the camera at the same depth, and a button that
resets the depth to zero. The panel is an ordinary overlay window, not a popup, so the scene keeps
taking clicks and drags while it is open; the button or Esc closes it again. Esc closes the panel
before it closes a gizmo or clears the selection. The button is highlighted
while a section cuts the scene and has shortcuts of its own: the mouse wheel over it moves the
plane, a right click switches the section off and back on, and a middle click sets the viewing
angle. Alt + mouse wheel moves the plane anywhere in the 3D view, with or without a gizmo open.
## State
Prepare and Preview share one section view, so a cut made in either tab is the same cut in the
other. The assembly view, whose objects sit apart from their places on the plate, and the Design
tab keep their own. The section itself is two values.
- **Ratio**, from 0 to 1. At 0 the section is off. As the ratio grows, the plane sweeps the
sphere around the objects, from its side facing the camera to the opposite side, so at 1
everything is cut away.
- **Normal**, taken from the camera direction the first time the section is switched on, and
again whenever the user sets the viewing angle. The plane keeps that orientation while the
camera orbits and while the section is off, so the cut face can be seen from any side and
bringing the depth back to 0 does not lose the angle.
The ratio in use when the section is switched off is kept, and the right click on the button
brings the section back at that ratio, which restores the same cut. Whether the panel is open is
shared along with the section.
The sphere is recomputed every frame from the volumes of the canvas the section view belongs to:
the objects on the current plate, or every object when that plate is empty. Preview holds no
objects of its own, so it places the plane across the volumes of Prepare, which makes it cut the
toolpaths exactly where Prepare cuts the objects. Only G-code opened on its own, without objects,
is measured by its toolpaths. In the assembly view the sphere is around the whole assembly. The
ratio therefore keeps its meaning when objects move or the user switches plates. It is not a
fixed position in world space.
Only the tab on screen can change the section, and switching tabs redraws the whole scene and
closes the open gizmo, so neither tab ever shows a stale cut.
## Where the plane applies
`GLCanvas3D::_get_section_view_plane()` turns the state into a plane in the convention of
`ClippingPlane::is_point_clipped()`. Everything that draws or picks the scene reads that plane.
- **Volumes.** The plane goes to the `clipping_plane` uniform of the volume shaders. The same
uniform serves the gouraud, phong and X-ray passes and the colour picking pass.
- **Cut faces.** Clipping only discards fragments, which would leave the cut volumes hollow.
`_render_section_view_caps()` draws their cut faces with one `MeshClipper` per model part the
plane passes through. A clipper recomputes its face only when the plane or the volume moves.
Modifiers, the wipe tower and SLA auxiliaries get no face.
- **Toolpaths.** libvgcode takes the plane through `Viewer::set_clipping_plane()`. It draws each
extrusion as only the faces of a diamond-section prism that turn towards the camera, so
discarding the fragments on the clipped side would leave open shells. Instead, the segment
shader follows the view ray from a fragment that is cut away to the plane. When the
extrusion's diamond section still holds that point, the fragment is shaded as the cut face, lit
as the plane faces; otherwise it is discarded. The cut face keeps the depth of the fragment it
replaces, which is safe: along that ray everything else still shown lies behind the plane. The
shader writes no `gl_FragDepth`, so early depth testing survives. Option markers are cut away
whole, by their centres. The shadow casters draw with a program of their own, which takes the
plane and discards the fragments on the clipped side, so what is cut away casts no shadow either.
Their cut faces are not drawn, since the part left behind casts the shadow of its own section.
Preview shells are drawn by another shader and are not clipped.
- **Picking.** `get_raycaster_clipping_plane()` returns the same plane, so hover, selection and
the perspective pan anchor ignore what the user cannot see.
## Gizmos
A gizmo that clips its object itself owns the gizmo data pool's `ObjectClipper`, and its plane
replaces the canvas section while the gizmo is open. `GLGizmosManager::get_clipping_plane()`
reports that plane, or nothing when no open gizmo has a clipper. There are two cases.
- **Painting tools and brim ears** show the canvas section on the object they edit.
`GLGizmosManager::update_section_view()` copies the ratio and normal into their clipper
whenever the pool is updated or the section changes. The clipper then places the plane across
the edited instance, which is the only object shown. The painting tools keep clipping their
own triangles, raycasts and cut face through it. Brim ears always cut horizontally from the
top, because the ears sit on the plate. At ratio 0 the clipper holds no plane at all, so the
raycasts are not clipped.
- **Cut and mesh boolean** use the clipper for their own purposes, so the canvas section is
suspended while they are open.
Every other gizmo, including move, rotate and scale, leaves the canvas section in place.
## Alt + mouse wheel
The canvas handles Alt + wheel after the gizmos had their turn, so it works the same in every
tab and with any gizmo open. On Windows, releasing Alt when no key was pressed since it went down
opens the window menu, and a wheel turn does not count as a key. Under the custom title bar that
menu is invisible, yet it takes the keyboard and the next click, which looks like a frozen 3D
view. After Alt + wheel the canvas therefore consumes the Alt release instead of passing it on.
## Redraw
The button and the panel are part of the ImGui overlay, which is built after the frame's scene is
drawn. A change to the section therefore marks the scene dirty and asks for one more frame. The
cached scene is never reused across a change.
+1
View File
@@ -180,6 +180,7 @@ src/slic3r/GUI/PrivacyUpdateDialog.cpp
src/slic3r/GUI/PublishDialog.cpp src/slic3r/GUI/PublishDialog.cpp
src/slic3r/GUI/PublishSettingsDialog.cpp src/slic3r/GUI/PublishSettingsDialog.cpp
src/slic3r/GUI/SavePresetDialog.cpp src/slic3r/GUI/SavePresetDialog.cpp
src/slic3r/GUI/SceneBenchmark.cpp
src/slic3r/GUI/Search.cpp src/slic3r/GUI/Search.cpp
src/slic3r/GUI/SettingsIndex.cpp src/slic3r/GUI/SettingsIndex.cpp
src/slic3r/GUI/SpeedDialDialog.cpp src/slic3r/GUI/SpeedDialDialog.cpp
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#fafafa;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#009688;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#00675b;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#008172;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#26a69a;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#ffffff; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#ffffff; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#393c42;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#283232;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#b6b6b6; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#b6b6b6; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36"><circle cx="17.5" cy="17.5" r="17.5" style="fill:#e5f0ee;"/><polygon points="13.82,11.62 21.18,15.88 21.18,23.38 13.82,19.12" style="fill:#2b3436; fill-opacity:.55;"/><polygon points="8.75,14.55 16.11,18.80 16.11,26.30 8.75,22.05" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="8.75,14.55 12.43,12.43 19.79,16.68 16.11,18.80" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polyline points="19.79,16.68 19.79,24.18 16.11,26.30" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round; stroke-opacity:.45;"/><polygon points="13.82,11.62 17.50,9.50 24.86,13.75 24.86,21.25 21.18,23.38 13.82,19.12" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><polyline points="13.82,11.62 21.18,15.88 24.86,13.75" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/><line x1="21.18" y1="15.88" x2="21.18" y2="23.38" style="fill:none; stroke:#2b3436; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+121 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Snapmaker", "name": "Snapmaker",
"version": "02.04.00.16", "version": "02.04.00.17",
"force_update": "0", "force_update": "0",
"description": "Snapmaker configurations", "description": "Snapmaker configurations",
"machine_model_list": [ "machine_model_list": [
@@ -1448,6 +1448,10 @@
"name": "Snapmaker PETG HF @U1 0.2 nozzle", "name": "Snapmaker PETG HF @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.2 nozzle.json" "sub_path": "filament/Snapmaker PETG HF @U1 0.2 nozzle.json"
}, },
{
"name": "Snapmaker PETG HF @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.4 nozzle.json"
},
{ {
"name": "Snapmaker PETG HF @U1 0.6 nozzle", "name": "Snapmaker PETG HF @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.6 nozzle.json" "sub_path": "filament/Snapmaker PETG HF @U1 0.6 nozzle.json"
@@ -1920,6 +1924,122 @@
"name": "Snapmaker Breakaway Support For PLA @U1 0.8 nozzle", "name": "Snapmaker Breakaway Support For PLA @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker Breakaway Support For PLA @U1 0.8 nozzle.json" "sub_path": "filament/Snapmaker Breakaway Support For PLA @U1 0.8 nozzle.json"
}, },
{
"name": "Generic PETG @U1 0.2 nozzle",
"sub_path": "filament/Generic PETG @U1 0.2 nozzle.json"
},
{
"name": "Generic PETG @U1 0.4 nozzle",
"sub_path": "filament/Generic PETG @U1 0.4 nozzle.json"
},
{
"name": "Generic PETG @U1 0.6 nozzle",
"sub_path": "filament/Generic PETG @U1 0.6 nozzle.json"
},
{
"name": "Generic PETG @U1 0.8 nozzle",
"sub_path": "filament/Generic PETG @U1 0.8 nozzle.json"
},
{
"name": "Generic PETG HF @U1 0.2 nozzle",
"sub_path": "filament/Generic PETG HF @U1 0.2 nozzle.json"
},
{
"name": "Generic PETG HF @U1 0.4 nozzle",
"sub_path": "filament/Generic PETG HF @U1 0.4 nozzle.json"
},
{
"name": "Generic PETG HF @U1 0.6 nozzle",
"sub_path": "filament/Generic PETG HF @U1 0.6 nozzle.json"
},
{
"name": "Generic PETG HF @U1 0.8 nozzle",
"sub_path": "filament/Generic PETG HF @U1 0.8 nozzle.json"
},
{
"name": "Generic PETG-CF @U1 0.4 nozzle",
"sub_path": "filament/Generic PETG-CF @U1 0.4 nozzle.json"
},
{
"name": "Generic PETG-CF @U1 0.6 nozzle",
"sub_path": "filament/Generic PETG-CF @U1 0.6 nozzle.json"
},
{
"name": "Generic PETG-CF @U1 0.8 nozzle",
"sub_path": "filament/Generic PETG-CF @U1 0.8 nozzle.json"
},
{
"name": "Generic PLA @U1 0.2 nozzle",
"sub_path": "filament/Generic PLA @U1 0.2 nozzle.json"
},
{
"name": "Generic PLA @U1 0.4 nozzle",
"sub_path": "filament/Generic PLA @U1 0.4 nozzle.json"
},
{
"name": "Generic PLA @U1 0.6 nozzle",
"sub_path": "filament/Generic PLA @U1 0.6 nozzle.json"
},
{
"name": "Generic PLA @U1 0.8 nozzle",
"sub_path": "filament/Generic PLA @U1 0.8 nozzle.json"
},
{
"name": "Generic PLA High Speed @U1 0.2 nozzle",
"sub_path": "filament/Generic PLA High Speed @U1 0.2 nozzle.json"
},
{
"name": "Generic PLA High Speed @U1 0.4 nozzle",
"sub_path": "filament/Generic PLA High Speed @U1 0.4 nozzle.json"
},
{
"name": "Generic PLA High Speed @U1 0.6 nozzle",
"sub_path": "filament/Generic PLA High Speed @U1 0.6 nozzle.json"
},
{
"name": "Generic PLA High Speed @U1 0.8 nozzle",
"sub_path": "filament/Generic PLA High Speed @U1 0.8 nozzle.json"
},
{
"name": "Generic PLA Silk @U1 0.2 nozzle",
"sub_path": "filament/Generic PLA Silk @U1 0.2 nozzle.json"
},
{
"name": "Generic PLA Silk @U1 0.4 nozzle",
"sub_path": "filament/Generic PLA Silk @U1 0.4 nozzle.json"
},
{
"name": "Generic PLA Silk @U1 0.6 nozzle",
"sub_path": "filament/Generic PLA Silk @U1 0.6 nozzle.json"
},
{
"name": "Generic PLA Silk @U1 0.8 nozzle",
"sub_path": "filament/Generic PLA Silk @U1 0.8 nozzle.json"
},
{
"name": "Generic PLA-CF @U1 0.4 nozzle",
"sub_path": "filament/Generic PLA-CF @U1 0.4 nozzle.json"
},
{
"name": "Generic PLA-CF @U1 0.6 nozzle",
"sub_path": "filament/Generic PLA-CF @U1 0.6 nozzle.json"
},
{
"name": "Generic PLA-CF @U1 0.8 nozzle",
"sub_path": "filament/Generic PLA-CF @U1 0.8 nozzle.json"
},
{
"name": "Generic TPU @U1 0.4 nozzle",
"sub_path": "filament/Generic TPU @U1 0.4 nozzle.json"
},
{
"name": "Generic TPU @U1 0.6 nozzle",
"sub_path": "filament/Generic TPU @U1 0.6 nozzle.json"
},
{
"name": "Generic TPU @U1 0.8 nozzle",
"sub_path": "filament/Generic TPU @U1 0.8 nozzle.json"
},
{ {
"name": "PolyTerra J1 PLA", "name": "PolyTerra J1 PLA",
"sub_path": "filament/Polymaker/PolyTerra J1 PLA.json" "sub_path": "filament/Polymaker/PolyTerra J1 PLA.json"
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG @U1 0.2 nozzle",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "Iw7T4qqDf1fbSAa1",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.2 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"10",
"1"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG @U1 0.4 nozzle",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "VHRa4TXU7HPz7Fld",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"10",
"12"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG @U1 0.6 nozzle",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "Ubr8v7BTxiQMntVk",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"10",
"12"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG @U1 0.8 nozzle",
"inherits": "Generic PETG @System",
"from": "system",
"setting_id": "ro0mXCLzC5rY95oX",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"10",
"12"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG HF @U1 0.2 nozzle",
"inherits": "Generic PETG HF @System",
"from": "system",
"setting_id": "7yjLWAdcQe4ndo1z",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.2 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"20",
"1"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG HF @U1 0.4 nozzle",
"inherits": "Generic PETG HF @System",
"from": "system",
"setting_id": "XWWLYZWkD9ZNRPeO",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"20",
"16"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG HF @U1 0.6 nozzle",
"inherits": "Generic PETG HF @System",
"from": "system",
"setting_id": "1dj4JdFabSql3cEg",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"20",
"16"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG HF @U1 0.8 nozzle",
"inherits": "Generic PETG HF @System",
"from": "system",
"setting_id": "1LYZ0KG5kQtaOOKt",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"20",
"16"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PETG-CF @U1 0.4 nozzle",
"inherits": "Generic PETG-CF @System",
"from": "system",
"setting_id": "wIKcBM1qLgzLEUJP",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"11.5",
"11"
],
"fan_min_speed": [
"5",
"5"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,23 @@
{
"type": "filament",
"name": "Generic PETG-CF @U1 0.6 nozzle",
"inherits": "Generic PETG-CF @System",
"from": "system",
"setting_id": "FvrnBiPbjSvIF3kN",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"5",
"5"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,23 @@
{
"type": "filament",
"name": "Generic PETG-CF @U1 0.8 nozzle",
"inherits": "Generic PETG-CF @System",
"from": "system",
"setting_id": "SCfvzDgUWkFHmbtP",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"5",
"5"
],
"fan_max_speed": [
"40",
"40"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA @U1 0.2 nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "8pb3tert1D4w0SKP",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.2 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"1.6"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA @U1 0.4 nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "Uilm8oVmZzyxE3ba",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA @U1 0.6 nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "aj4G9OSdgtQPqpkI",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA @U1 0.8 nozzle",
"inherits": "Generic PLA @System",
"from": "system",
"setting_id": "zom29gBgIZmvjFmh",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA High Speed @U1 0.2 nozzle",
"inherits": "Generic PLA High Speed @System",
"from": "system",
"setting_id": "rT8e5f81vwUpwPuq",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.2 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"18",
"2"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA High Speed @U1 0.4 nozzle",
"inherits": "Generic PLA High Speed @System",
"from": "system",
"setting_id": "ZUetX0WveqW8fy6f",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"18",
"18"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA High Speed @U1 0.6 nozzle",
"inherits": "Generic PLA High Speed @System",
"from": "system",
"setting_id": "y2WY6qLA3xKZM1Mm",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"18",
"18"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA High Speed @U1 0.8 nozzle",
"inherits": "Generic PLA High Speed @System",
"from": "system",
"setting_id": "VwXJrdwxvzjlIGal",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"18",
"18"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA Silk @U1 0.2 nozzle",
"inherits": "Generic PLA Silk @System",
"from": "system",
"setting_id": "381SJIDBre8HxHy2",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.2 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"7.5",
"2"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PLA Silk @U1 0.4 nozzle",
"inherits": "Generic PLA Silk @System",
"from": "system",
"setting_id": "Fd1g9OdeM3pUx8f5",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PLA Silk @U1 0.6 nozzle",
"inherits": "Generic PLA Silk @System",
"from": "system",
"setting_id": "8rhmpBFmMj9vVZwG",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic PLA Silk @U1 0.8 nozzle",
"inherits": "Generic PLA Silk @System",
"from": "system",
"setting_id": "Fo7U7GYHZh1aZPLs",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA-CF @U1 0.4 nozzle",
"inherits": "Generic PLA-CF @System",
"from": "system",
"setting_id": "6AWubtDE4PqrKVNJ",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA-CF @U1 0.6 nozzle",
"inherits": "Generic PLA-CF @System",
"from": "system",
"setting_id": "AvM44HgMCkOCnmzY",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,31 @@
{
"type": "filament",
"name": "Generic PLA-CF @U1 0.8 nozzle",
"inherits": "Generic PLA-CF @System",
"from": "system",
"setting_id": "tvYAUvQEbbdBCIg2",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"filament_max_volumetric_speed": [
"12",
"12"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic TPU @U1 0.4 nozzle",
"inherits": "Generic TPU @System",
"from": "system",
"setting_id": "9d0jRdMgkPMahYL7",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"70",
"70"
],
"fan_max_speed": [
"70",
"70"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic TPU @U1 0.6 nozzle",
"inherits": "Generic TPU @System",
"from": "system",
"setting_id": "LLjEPSU4l09N3Zb8",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.6 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"70",
"70"
],
"fan_max_speed": [
"70",
"70"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -0,0 +1,27 @@
{
"type": "filament",
"name": "Generic TPU @U1 0.8 nozzle",
"inherits": "Generic TPU @System",
"from": "system",
"setting_id": "Wfhd3jLEjSKzCeHE",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.8 nozzle)"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"70",
"70"
],
"fan_max_speed": [
"70",
"70"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -10,15 +10,18 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"0",
"0" "0"
], ],
"filament_max_volumetric_speed": [ "filament_max_volumetric_speed": [
"15",
"15" "15"
], ],
"filament_vendor": [ "filament_vendor": [
"Polymaker" "Polymaker"
], ],
"nozzle_temperature_range_high": [ "nozzle_temperature_range_high": [
"230",
"230" "230"
], ],
"temperature_vitrification": [ "temperature_vitrification": [
@@ -29,5 +32,21 @@
], ],
"textured_plate_temp_initial_layer": [ "textured_plate_temp_initial_layer": [
"65" "65"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -16,12 +16,15 @@
"Polymaker" "Polymaker"
], ],
"nozzle_temperature": [ "nozzle_temperature": [
"230",
"230" "230"
], ],
"nozzle_temperature_initial_layer": [ "nozzle_temperature_initial_layer": [
"230",
"230" "230"
], ],
"nozzle_temperature_range_high": [ "nozzle_temperature_range_high": [
"230",
"230" "230"
], ],
"textured_plate_temp": [ "textured_plate_temp": [
@@ -29,5 +32,21 @@
], ],
"textured_plate_temp_initial_layer": [ "textured_plate_temp_initial_layer": [
"65" "65"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -13,6 +13,7 @@
"1.23" "1.23"
], ],
"filament_flow_ratio": [ "filament_flow_ratio": [
"0.96",
"0.96" "0.96"
], ],
"filament_vendor": [ "filament_vendor": [
@@ -29,5 +30,21 @@
], ],
"textured_plate_temp_initial_layer": [ "textured_plate_temp_initial_layer": [
"65" "65"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -9,9 +9,27 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"pressure_advance": [ "pressure_advance": [
"0.05",
"0.05" "0.05"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"10",
"10"
],
"fan_max_speed": [
"30",
"30"
],
"additional_cooling_fan_speed": [
"0",
"0"
] ]
} }
@@ -9,18 +9,35 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"filament_retraction_length": [ "filament_retraction_length": [
"1.0",
"1.0" "1.0"
], ],
"filament_z_hop": [ "filament_z_hop": [
"0.0",
"0.0" "0.0"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"1",
"1" "1"
], ],
"pressure_advance": [ "pressure_advance": [
"0.05",
"0.05" "0.05"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"20",
"20"
],
"fan_max_speed": [
"50",
"50"
] ]
} }
@@ -9,18 +9,35 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"filament_retraction_length": [ "filament_retraction_length": [
"1.0",
"1.0" "1.0"
], ],
"filament_z_hop": [ "filament_z_hop": [
"0.0",
"0.0" "0.0"
], ],
"enable_pressure_advance": [ "enable_pressure_advance": [
"1",
"1" "1"
], ],
"pressure_advance": [ "pressure_advance": [
"0.08",
"0.08" "0.08"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"10",
"10"
] ]
} }
@@ -9,16 +9,32 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"filament_retraction_length": [ "filament_retraction_length": [
"1.0",
"1.0" "1.0"
], ],
"filament_z_hop": [ "filament_z_hop": [
"0.0",
"0.0" "0.0"
], ],
"description": " ", "description": " ",
"enable_pressure_advance": [ "enable_pressure_advance": [
"1",
"1" "1"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"10",
"10"
] ]
} }
@@ -9,6 +9,7 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"supertack_plate_temp": [ "supertack_plate_temp": [
@@ -18,6 +19,19 @@
"70" "70"
], ],
"pressure_advance": [ "pressure_advance": [
"0.04",
"0.04" "0.04"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"10",
"10"
],
"fan_max_speed": [
"20",
"20"
] ]
} }
@@ -9,7 +9,24 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"description": " " "description": " ",
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"0",
"0"
],
"fan_max_speed": [
"10",
"10"
],
"additional_cooling_fan_speed": [
"0",
"0"
]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,9 +10,27 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
], ],
"filament_wipe": [ "filament_wipe": [
"0",
"0" "0"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)" "Snapmaker U1 (0.4 nozzle)"
], ],
"filament_minimal_purge_on_wipe_tower": [ "filament_minimal_purge_on_wipe_tower": [
"15",
"15" "15"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"fan_min_speed": [
"100",
"100"
],
"fan_max_speed": [
"100",
"100"
],
"additional_cooling_fan_speed": [
"70",
"70"
] ]
} }

Some files were not shown because too many files have changed in this diff Show More