Compare commits

..
Author SHA1 Message Date
ExPikaPaka d80c69341c Say what the code does, not what it replaced
The timings and the runs that never finished belong in the commit
messages, where they can be read against the change; a reader of the
code cannot check them. Kept the cost that still explains the design.

MultiPoint also spells out the consequence: a moved-from Polygon or
Polyline is now really empty where it used to silently keep its points.

The two wall spacing comments the parallel loop reindented are plain
ASCII now, so the whole file is.
2026-10-02 18:13:46 -03:00
ExPikaPaka 4a63a7d916 Compare the tiled booleans polygon by polygon
Area alone would pass on a result whose pieces were merged across tiles
or which kept the cut edges of the clip. The rings are compared after
rotating each to its lowest point and sorting, so only the ordering is
free. The fixture now also asserts it really is split into more than one
tile, which is the path being tested.
2026-10-02 18:13:46 -03:00
ExPikaPaka de1dfd0611 Hand the island's walls over instead of copying them
append(const ExtrusionEntity &) clones; the collection each island
produced was deep-copied into the layer's loops and then thrown away.
The no-overlap areas are moved as well.
2026-10-02 18:13:46 -03:00
ExPikaPaka 11a5971cef Include what the new code uses
The Windows build stopped on test_kdtree.cpp: it calls std::iota without
including <numeric>, which libstdc++ happens to pull in anyway. Added
there, and the same for <limits> and <algorithm>/<cmath> where the
recent changes rely on them being included by something else.
2026-10-02 18:13:45 -03:00
ExPikaPaka 82f37ddb3a Fix the Windows build: near and far are macros there
bridge_over_infill's helper for splitting polygons by proximity named
its locals near and far. The Windows headers define both as macros that
expand to nothing, so "Polygons near;" declared nothing and the uses of
it did not compile. Renamed; no behaviour change.
2026-10-02 18:13:37 -03:00
ExPikaPaka fb03d1a1cb Visit seam candidates as the search finds them
Collecting every candidate within the radius into a vector cost more
than the search itself. Same order, so the seams are unchanged;
align_seam_points ~19.6 s at 0.1 mm / 2000k, was ~21.
2026-10-02 18:13:37 -03:00
ExPikaPaka 2b4bdead73 Run a layer's regions in parallel where they are independent
detect_surfaces_type, process_external_surfaces and the vertical shells
each waited on their own heaviest layer in turn. The LOTR map plate
slices in ~10.5 min at 0.1 mm / 2000k, was ~11.5; ~87 s at normal
settings, was ~97.
2026-10-02 18:13:37 -03:00
ExPikaPaka b1f0d6c6f6 Move polygons instead of copying them on move
MultiPoint had no rvalue constructor, so the derived move constructors
bound to the const reference and copied; append reserved exactly, so
collecting pieces one by one was quadratic. Colour segmentation ~3 s at
0.1 mm / 2000k, was ~40, and ordinary prints gain too.
2026-10-02 18:13:37 -03:00
ExPikaPaka 14751a8b06 Project painted faces onto the shell layers per tile
Only the slices within the deepest shell offset decide the result, so
the work is done per tile of the face. Top and bottom segmentation
~130 s at 0.1 mm / 2000k, was ~180.
2026-10-02 18:13:36 -03:00
ExPikaPaka 9a86d79038 Tile the booleans on layers of many pieces
ClipperLib slows down with the number of edges on a scan line, and a
layer cut through a fine relief has tens of thousands of pieces.
detect_surfaces_type ~50 s at 0.1 mm / 2000k, was ~145.
2026-10-02 18:13:05 -03:00
ExPikaPaka d905f1a39b Merge colour and top/bottom regions per island
The merge took anything from 3 to 38 minutes at 0.1 mm / 2000k, now
~2.5. Every region is grouped with the islands it overlaps, so the
result is the same.
2026-10-02 18:12:41 -03:00
ExPikaPaka 84ec518f26 Slice fine texture relief without stalling
A colour texture baked at 0.1 mm / 2000k made the top layers thousands
of islands and slicing never finished. Colour segmentation runs per
island, the merge subtracts piece by piece, the support check tests only
nearby islands, and the travel ordering finds crossings through a grid.
2026-10-02 18:12:41 -03:00
ExPikaPaka 1fc153308f Generate walls and split solid infill in parallel
Same output, ~2.2 min for the LOTR map plate, was ~2.6.
2026-10-02 18:12:41 -03:00
ExPikaPaka 3167c3665a Run colour segmentation and vertical shells in parallel
Same output, ~2.6 min for the LOTR map plate, was ~2.9.
2026-10-02 18:12:40 -03:00
ExPikaPaka 6d34d83e78 Faster slicing of colour-painted layers
A layer split into ~1000 colour fragments (a colour texture baked over a
large top face) made several per-fragment loops redo whole-layer ClipperLib
work, so slicing took ~33 min; it now takes ~3 min with the same output.

- make_fills: clip the layer's no-overlap area to each expolygon's box
  before intersecting
- discover_vertical_shells: small-piece filter compares only against the
  nearby part of the layer
- bridge_over_infill: whole-layer union/diff/intersections restricted to the
  candidate's neighbourhood; fill boundary expanded once per spacing; anchor
  tree built only from lines crossing the scan range; bbox pre-check in the
  collision test; limiting outline taken directly instead of through
  expand(..., 0.3 * flow.spacing()), which offsets by 0.135 scaled units
  (flow.spacing() is in mm) and only cost a whole-layer pass per candidate
2026-10-02 18:11:17 -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
Ian Chua 2f4758446a feat: add foundation for configurable printer agent connection (#16027)
* feat: add foundation for configurable printer agent connection

* fix: regression after merge

* chore: remove duplcate http include

* fix: remove unrelated moonraker.cpp changes

* chore: port add_platform_root_certificates to 15711

* fix: clean diff
2026-10-01 22:50:56 +08:00
HanifKoh c023632a7d Lock Config and Preset Files Across Instances and Write Them Atomically (#15861)
* Lock Config and Preset Files Across Instances and Write Them Atomically

Every running instance shares one OrcaSlicer.conf and one user preset
tree, and nothing kept their writers apart. Two instances saving at the
same moment, or the cloud preset sync thread writing while the GUI thread
saved, could interleave, and a reader in another instance could open a
preset JSON or .info file between truncate and close and get a partial
file, dropping that preset for the session with a parse error.

Add InstanceLock, a scoped guard that serialises the threads of one
process through a recursive mutex and other processes through an advisory
OS file lock: flock on POSIX, held on the guard's own descriptor so no
other close in the process can drop it, and LockFileEx on Windows. The
outermost guard opens the lock file and closes it on release, so nothing
stays open between saves and a data dir can be removed once nothing is
saving into it; the file itself is kept, since deleting it would let a
third instance lock a fresh file while the second still holds the old
one. It is best effort: when the lock file cannot be opened or locked, or
another instance still holds it after a second, the guard logs once and
lets the write proceed, then leaves the file alone for ten seconds, so a
hung instance never blocks every other one and a holder stuck in a
debugger does not cost a stall per save. The guard sits at the leaf
readers and writers: set_sync_info_and_save() calls save_info() under the
preset collection mutex, so a batch lock around save_user_presets() would
invert the order against the sync thread. The user preset scan reads its
files on worker threads without the guard, since the mutex would
serialise them, and takes it per file in the serial commit step, so a
save never waits for the whole scan. Each read keeps the bytes of the
preset and its .info as they were before parsing; commit compares them
with the disk under the guard and reads a file that changed again, so it
never deletes or writes back over another instance's newer save, nor
installs a .json and .info from two different saves; a preset another
instance removed in the meantime is not installed. Without the guard, in
a cool-down, the scan still loads the presets but leaves their files
alone: an unreadable file stays for the next scan, and a derived
compatible printer is not written back. Read-only scans, which is what
the CLI does, take no lock and create no lock file.

AppConfig holds OrcaSlicer.conf.lock in load() and save(); load is
included because the Windows path restores from the .bak copy. Every
user preset writer and reader holds user.lock: Preset::save(), which
writes no .info when the preset itself could not be written, since an
.info without its preset reads as a cloud deletion request, save_info(),
reload() and remove_files(), each preset the scan commits, the
bundle metadata reads and write, the .info removal after a cloud-confirmed
delete, the orphaned-.info scan on the sync thread, the bundle folder
removal on unsubscribe and the physical printer writers and delete
paths. A bundle import extracts under cache/ into a folder per process
and per import, where no scan reads.

Preset JSON, .info, bundle metadata, physical printer and config files,
and the caches and state files that already used a temporary by hand,
now go through write_file_atomically(), which writes <file>.<pid>.<n>.tmp
beside the target and renames it over, so a reader that never waits sees
a complete old or new file. A symlink is followed; a target that is not
a regular file is written in place; and when no temporary can be created
beside an existing target, or the rename itself is refused, by a Windows
reader holding the file open or a mount that cannot replace in one step,
the helper writes in place as before, since losing the save is worse
than a torn read. On POSIX the rename replaces the
target atomically where the old code removed it first and left a window
with no file at all; only a mount that refuses a one-step replace gets
the old remove-then-rename. A crash between temporary and rename leaves
the temporary behind, which no scan reads. Preset::save() returns
whether it wrote the preset, so the scan counts a compatible printer it
could not write back as an error. A failed config write keeps
the config dirty, and the idle handler waits ten seconds before retrying
while an explicit save always tries.

* Run the Cross-Process Lock Test on Every Platform

The test that checks the guard yields to a lock held elsewhere forked a
child to hold it, so it was left out on Windows. The OS lock belongs to
the handle on Windows and to the open file description elsewhere, so a
second handle in the same process is refused like another instance
would be. The test now holds the lock that way and runs everywhere.
2026-10-01 22:18:21 +08:00
1029 changed files with 24128 additions and 9373 deletions
+47
View File
@@ -0,0 +1,47 @@
# 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, CPython's
# headers behind Python.h, curl's behind curl.h, oneTBB's behind tbb/, and
# admesh's stl.h, which the include path also exposes without its directory.
# 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]+[/\\].*;
bits[/\\].*;
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
+2 -1
View File
@@ -206,7 +206,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-
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
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.
- **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
@@ -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)
18. Content that needs typing focus or hosts a `wxWebView` uses a frameless `wxDialog` that hides on
deactivation, not a transient popup. (§10)
19. Display-only overlays (HUDs, toasts) use a plain `wxPopupWindow`: it never takes focus and never
auto-dismisses. (§4, §10)
19. Display-only overlays (HUDs, toasts) use a plain `wxPopupWindow`: `Show()` never gives it focus and it
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)
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
@@ -356,6 +357,13 @@ compensates for (§6).
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.
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
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
@@ -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
`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
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`).
- **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
@@ -428,7 +428,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
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
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,
`IsThisEnabled()` the window's own flag (`interface/wx/window.h:3060-3070, 3116-3138`). **[source]** On MSW/macOS wx
+1 -1
View File
@@ -45,7 +45,7 @@ on:
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
inputs:
-17
View File
@@ -49,28 +49,11 @@ jobs:
key: ${{ inputs.cache-key }}
- 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:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
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
if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@v3
+1 -17
View File
@@ -54,28 +54,11 @@ jobs:
fail-on-cache-miss: true
- 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:
cmakeVersion: "~4.3.0" # use most recent 4.3.x version
useLocalCache: true # <--= Use the local cache (default is 'false').
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
# restore it and discard what they compiled. Objects are keyed on the
# preprocessed source, the compiler and the flags, so a leg only ever
@@ -276,6 +259,7 @@ jobs:
# Thanks to RaySajuuk, it's working now
- 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
timeout-minutes: 30
working-directory: ${{ github.workspace }}
env:
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
with:
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
with:
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_aarch64-unknown-linux-gnu "88e800834007cc5efd4675f166eb2a51e7e3ad19876d85fa8805a6fb5c922397")
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")
# 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")
endif()
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")
else()
set(ORCA_UV_ARCH "aarch64-unknown-linux-gnu")
@@ -819,7 +823,9 @@ if(SLIC3R_STATIC)
set(TBB_STATIC 1)
endif()
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)
# include_directories(SYSTEM ${TBB_INCLUDE_DIRS})
# add_definitions(${TBB_DEFINITIONS})
@@ -1301,6 +1307,14 @@ if (WIN32)
endif()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
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 ".")
elseif (SLIC3R_FHS)
# 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>
[![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.
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!
)
REM CMake 4 dropped pre-3.5 policy support and ships incomplete ASM_ARMASM
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 :print_and_run winget install !winget_args! --id=Kitware.CMake
call :note_failed CMake !errorlevel!
call :print_and_run winget install !winget_args! --id=StrawberryPerl.StrawberryPerl
call :note_failed Perl !errorlevel!
+10
View File
@@ -29,6 +29,15 @@ if(WIN32)
# driven through nmake from a Ninja configure step.
set(_conf_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} perl Configure )
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(_install_cmd ${CMAKE_COMMAND} -E env ${_openssl_msvc_env} nmake install_sw )
else()
@@ -71,6 +80,7 @@ ExternalProject_Add(dep_OpenSSL
# prefix stays single-layout.
"--libdir=lib"
${_cross_comp_prefix_line}
${_openssl_extra_cflags}
no-shared
no-asm
no-ssl3-method
-1
View File
@@ -15,7 +15,6 @@ add_subdirectory(stb_dxt) # Header-only STB DXT compression library
# Static libraries
add_subdirectory(Shiny)
add_subdirectory(admesh)
add_subdirectory(clipper)
add_subdirectory(clipper2)
add_subdirectory(expat)
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)
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_CXX_STANDARD 17)
@@ -19,6 +19,7 @@ set(CLIPPER2_INC
Clipper2Lib/include/clipper2/clipper.minkowski.h
Clipper2Lib/include/clipper2/clipper.offset.h
Clipper2Lib/include/clipper2/clipper.rectclip.h
Clipper2Lib/include/clipper2/clipper.triangulation.h
Clipper2Lib/include/clipper2/clipper2_z.hpp
)
@@ -26,6 +27,7 @@ set(CLIPPER2_SRC
Clipper2Lib/src/clipper.engine.cpp
Clipper2Lib/src/clipper.offset.cpp
Clipper2Lib/src/clipper.rectclip.cpp
Clipper2Lib/src/clipper.triangulation.cpp
Clipper2Lib/src/clipper2_z.cpp
)
@@ -36,6 +38,9 @@ target_include_directories(Clipper2
PUBLIC Clipper2Lib/include
)
# Engine nodes are allocated through tbbmalloc (see clipper.engine.cpp).
target_link_libraries(Clipper2 PRIVATE TBB::tbbmalloc)
if (WIN32)
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(Clipper2 PRIVATE /W4 /WX)
@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 12 May 2024 *
* Date : 12 October 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : Core Clipper Library structures and functions *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -251,6 +251,20 @@ namespace Clipper2Lib {
template <typename 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 PathD = Path<double>;
using Paths64 = std::vector< Path64>;
@@ -685,32 +699,31 @@ namespace Clipper2Lib {
inline int TriSign(int64_t x) // returns 0, 1 or -1
{
return (x > 0) - (x < 0);
return (x > 0) - (x < 0);
}
struct MultiplyUInt64Result
struct UInt128Struct
{
const uint64_t result = 0;
const uint64_t carry = 0;
const uint64_t lo = 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 hi = [](uint64_t x) { return x >> 32; };
const uint64_t x1 = lo(a) * lo(b);
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
const uint64_t result = lo(x3) << 32 | lo(x1);
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3);
return { result, carry };
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
}
// 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_d = static_cast<uint64_t>(std::abs(d));
const auto abs_ab = Multiply(abs_a, abs_b);
const auto abs_cd = Multiply(abs_c, abs_d);
const auto ab = MultiplyUInt64(abs_a, abs_b);
const auto cd = MultiplyUInt64(abs_c, abs_d);
// nb: it's important to differentiate 0 values here from other values
const auto sign_ab = TriSign(a) * TriSign(b);
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
}
@@ -838,6 +887,10 @@ namespace Clipper2Lib {
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
// caution: this will compromise performance
// 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_MAX(x,y) ((x)<(y)?(y):(x))
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)
{
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
@@ -891,11 +944,14 @@ namespace Clipper2Lib {
ip.x = originx + static_cast<T>(hitx);
ip.y = originy + static_cast<T>(hity);
}
#ifdef USINGZ
ip.z = 0;
#endif
return true;
}
#else
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)
{
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
@@ -913,7 +969,10 @@ namespace Clipper2Lib {
{
ip.x = static_cast<T>(ln1a.x + t * dx1);
ip.y = static_cast<T>(ln1a.y + t * dy1);
}
#ifdef USINGZ
ip.z = 0;
#endif
}
return true;
}
#endif
@@ -940,30 +999,53 @@ namespace Clipper2Lib {
}
template<typename T>
inline int GetSign(const T& val)
{
if (!val) return 0;
inline int GetSign(const T& val)
{
if (!val) return 0;
return (val > 0) ? 1 : -1;
}
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
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)
{
double res1 = CrossProduct(seg1a, seg2a, seg2b);
double res2 = CrossProduct(seg1b, seg2a, seg2b);
if (res1 * res2 > 0) return false;
double res3 = CrossProduct(seg2a, seg1a, seg1b);
double res4 = CrossProduct(seg2b, seg1a, seg1b);
if (res3 * res4 > 0) return false;
return (res1 || res2 || res3 || res4); // ensures not collinear
//result **includes** segments that touch at an end point
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
if (t == 0) return true;
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 true;
if (t > 0) return (cp > 0 && t <= cp);
else return (cp < 0 && t >= cp); // true when t less neg. than cp
}
else {
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
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
else
{
double d = CrossProduct(*prev, *curr, pt);
int d = CrossProductSign(*prev, *curr, pt);
if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val;
}
@@ -1065,7 +1147,7 @@ namespace Clipper2Lib {
if (curr == cend) curr = cbegin;
if (curr == cbegin) prev = cend - 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) == is_above) val = 1 - val;
}
@@ -15,6 +15,13 @@
#include <functional>
#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
namespace Clipper2Lib_Z {
#else
@@ -50,6 +57,7 @@ namespace Clipper2Lib {
}
struct Vertex {
CLIPPER2_NODE_ALLOCATOR
Point64 pt;
Vertex* next = nullptr;
Vertex* prev = nullptr;
@@ -57,6 +65,7 @@ namespace Clipper2Lib {
};
struct OutPt {
CLIPPER2_NODE_ALLOCATOR
Point64 pt;
OutPt* next = nullptr;
OutPt* prev = nullptr;
@@ -81,6 +90,7 @@ namespace Clipper2Lib {
//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.
struct OutRec {
CLIPPER2_NODE_ALLOCATOR
size_t idx = 0;
OutRec* owner = nullptr;
Active* front_edge = nullptr;
@@ -106,6 +116,7 @@ namespace Clipper2Lib {
///////////////////////////////////////////////////////////////////
struct Active {
CLIPPER2_NODE_ALLOCATOR
Point64 bot;
Point64 top;
int64_t curr_x = 0; //current (updated at every new scanline)
@@ -133,6 +144,7 @@ namespace Clipper2Lib {
};
struct LocalMinima {
CLIPPER2_NODE_ALLOCATOR
Vertex* vertex;
PathType polytype;
bool is_open;
@@ -303,6 +315,7 @@ namespace Clipper2Lib {
protected:
PolyPath* parent_;
public:
CLIPPER2_NODE_ALLOCATOR
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
virtual ~PolyPath() {};
//https://en.cppreference.com/w/cpp/language/rule_of_three
@@ -330,15 +343,16 @@ namespace Clipper2Lib {
//Even levels except level 0
return lvl && !(lvl & 1);
}
template<typename T>
static double Clipper2LibArea(const Path<T> &poly)
{
// Area() of the namespace this header is compiled into (Clipper2Lib or Clipper2Lib_Z).
template<typename T>
static double Clipper2LibArea(const Path<T> &poly)
{
#ifdef USINGZ
return Clipper2Lib_Z::Area<T>(poly);
return Clipper2Lib_Z::Area<T>(poly);
#else
return Clipper2Lib::Area<T>(poly);
return Clipper2Lib::Area<T>(poly);
#endif
}
}
};
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
@@ -388,7 +402,8 @@ namespace Clipper2Lib {
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(); });
}
@@ -462,7 +477,8 @@ namespace Clipper2Lib {
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(); });
}
};
@@ -19,17 +19,17 @@
The path structures used extensively in other parts of this library are all
based on std::vector classes. Since C++ classes can't be accessed by other
languages, these paths are exported here as very simple array structures
(either of int64_t or double) that can be parsed by just about any
languages, these paths are exported here as very simple array structures
(either of int64_t or double) that can be parsed by just about any
programming language.
These 2D paths are defined by series of x and y coordinates together with an
optional user-defined 'z' value (see Z-values below). Hence, a vertex refers
to a single x and y coordinate (+/- a user-defined value). Data structures
have names with suffixes that indicate the array type (either int64_t or
double). For example, the data structure CPath64 contains an array of int64_t
values, whereas the data structure CPathD contains an array of double.
Where documentation omits the type suffix (eg CPath), it is referring to an
to a single x and y coordinate (+/- a user-defined value). Data structures
have names with suffixes that indicate the array type (either int64_t or
double). For example, the data structure CPath64 contains an array of int64_t
values, whereas the data structure CPathD contains an array of double.
Where documentation omits the type suffix (eg CPath), it is referring to an
array whose data type could be either int64_t or double.
For conciseness, the following letters are used in the diagrams below:
@@ -39,10 +39,10 @@ A: Number of elements in an array
CPath64 and CPathD:
These are arrays of either int64_t or double values. Apart from
the first two elements, these arrays are a series of vertices
that together define a path. The very first element contains the
number of vertices (N) in the path, while second element should
These are arrays of either int64_t or double values. Apart from
the first two elements, these arrays are a series of vertices
that together define a path. The very first element contains the
number of vertices (N) in the path, while second element should
contain a 0 value.
_______________________________________________________________
| counters | vertex1 | vertex2 | ... | vertexN |
@@ -52,9 +52,9 @@ _______________________________________________________________
CPaths64 and CPathsD:
These are also arrays of either int64_t or double values that
contain any number of consecutive CPath structures. However,
contain any number of consecutive CPath structures. However,
preceding the first path is a pair of values. The first value
contains the length of the entire array structure (A), and the
contains the length of the entire array structure (A), and the
second contains the number (ie count) of contained paths (C).
Memory allocation for CPaths64 = A * sizeof(int64_t)
Memory allocation for CPathsD = A * sizeof(double)
@@ -65,12 +65,12 @@ __________________________________________
CPolytree64 and CPolytreeD:
The entire polytree structure is an array of int64_t or double. The
first element in the array indicates the array's total length (A).
The second element indicates the number (C) of CPolyPath structures
The entire polytree structure is an array of int64_t or double. The
first element in the array indicates the array's total length (A).
The second element indicates the number (C) of CPolyPath structures
that are the TOP LEVEL CPolyPath in the polytree, and these top
level CPolyPath immediately follow these first two array elements.
These top level CPolyPath structures may, in turn, contain nested
level CPolyPath immediately follow these first two array elements.
These top level CPolyPath structures may, in turn, contain nested
CPolyPath children, and these collectively make a tree structure.
_________________________________________________________
| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC |
@@ -116,13 +116,10 @@ the four vertices that define the two segments that are intersecting.
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "clipper2/clipper.rectclip.h"
#include "clipper2/clipper.triangulation.h"
#include <cstdlib>
#ifdef USINGZ
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
#endif
typedef int64_t* CPath64;
typedef int64_t* CPaths64;
@@ -254,9 +251,9 @@ ZCallback64 dllCallback64 = nullptr;
ZCallbackD dllCallbackD = nullptr;
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3;
#else
#else
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2;
#endif
#endif
template <typename T>
static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
@@ -396,7 +393,7 @@ static Path<T> ConvertCPathToPathT(T* path)
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
result.emplace_back(x, y, z);
#else
#else
result.emplace_back(x, y);
#endif
}
@@ -414,7 +411,7 @@ static Paths<T> ConvertCPathsToPathsT(T* paths)
for (size_t i = 0; i < cnt; ++i)
{
size_t cnt2 = static_cast<size_t>(*v);
v += 2;
v += 2;
Path<T> path;
path.reserve(cnt2);
for (size_t j = 0; j < cnt2; ++j)
@@ -447,7 +444,7 @@ static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale)
#ifdef USINGZ
z_type z = Reinterpret<z_type>(*v++);
result.emplace_back(x, y, z);
#else
#else
result.emplace_back(x, y);
#endif
}
@@ -492,7 +489,7 @@ static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v)
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
#ifdef USINGZ
* v++ = Reinterpret<int64_t>(pt.z); // raw memory copy
#endif
}
@@ -508,7 +505,7 @@ static void CreateCPolyPathD(const PolyPathD* pp, double*& v)
{
*v++ = pt.x;
*v++ = pt.y;
#ifdef USINGZ
#ifdef USINGZ
* v++ = Reinterpret<double>(pt.z); // raw memory copy
#endif
}
@@ -816,6 +813,24 @@ EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath6
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
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);
@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 27 April 2024 *
* Date : 5 March 2025 *
* 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 *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -13,14 +13,15 @@
#include "clipper2/clipper.core.h"
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.offset.h"
#include "clipper2/clipper.minkowski.h"
#include "clipper2/clipper.rectclip.h"
#include "clipper2/clipper.minkowski.h"
#include "clipper2/clipper.triangulation.h"
#include <type_traits>
#ifdef USINGZ
namespace Clipper2Lib_Z {
namespace Clipper2Lib_Z {
#else
namespace Clipper2Lib {
namespace Clipper2Lib {
#endif
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
@@ -154,14 +155,14 @@
if (!delta) return paths;
if (error_code) return PathsD();
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);
if (error_code) return PathsD();
Paths64 solution;
clip_offset.Execute(delta * scale, solution);
return ScalePaths<double, int64_t>(solution, 1 / scale, error_code);
}
template <typename T>
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
{
@@ -355,6 +356,29 @@
#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
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
@@ -615,29 +639,6 @@
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>
inline Path<T> SimplifyPath(const Path<T> &path,
double epsilon, bool isClosedPath = true)
@@ -669,13 +670,13 @@
start = curr;
do
{
curr = GetNext(curr, high, flags);
curr = details::GetNext(curr, high, flags);
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
prior = details::GetPrior(curr, high, flags);
next = details::GetNext(curr, high, flags);
if (next == prior) break;
// flag for removal the smaller of adjacent 'distances'
@@ -684,14 +685,14 @@
prior2 = prior;
prior = curr;
curr = next;
next = GetNext(next, high, flags);
next = details::GetNext(next, high, flags);
}
else
prior2 = GetPrior(prior, high, flags);
prior2 = details::GetPrior(prior, high, flags);
flags[curr] = true;
curr = next;
next = GetNext(next, high, flags);
next = details::GetNext(next, high, flags);
if (isClosedPath || ((curr != high) && (curr != 0)))
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
@@ -716,6 +717,35 @@
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>
inline void RDP(const Path<T> path, std::size_t begin,
std::size_t end, double epsSqrd, std::vector<bool>& flags)
@@ -39,7 +39,7 @@ private:
class Group {
public:
Paths64 paths_in;
std::optional<size_t> lowest_path_idx{};
std::optional<size_t> lowest_path_idx{};
bool is_reversed = false;
JoinType join_type;
EndType end_type;
@@ -100,7 +100,7 @@ public:
void AddPath(const Path64& path, JoinType jt_, EndType et_);
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
void Clear() { groups_.clear(); norms.clear(); };
void Execute(double delta, Paths64& sols_64);
void Execute(double delta, PolyTree64& polytree);
void Execute(DeltaCallback64 delta_cb, Paths64& paths);
@@ -114,7 +114,7 @@ public:
bool PreserveCollinear() const { return preserve_collinear_; }
void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;}
bool ReverseSolution() const { return reverse_solution_; }
void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;}
@@ -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
#define CLIPPER_VERSION_H
constexpr auto CLIPPER2_VERSION = "1.5.2";
constexpr auto CLIPPER2_VERSION = "2.0.1";
#endif // CLIPPER_VERSION_H
@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 17 September 2024 *
* Date : 5 November 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : This is the main polygon clipping module *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -10,6 +10,8 @@
#include "clipper2/clipper.engine.h"
#include "clipper2/clipper.h"
#include <stdexcept>
#include <new>
#include <oneapi/tbb/scalable_allocator.h>
// https://github.com/AngusJohnson/Clipper2/discussions/334
// #discussioncomment-4248602
@@ -27,10 +29,30 @@ namespace Clipper2Lib_Z {
namespace Clipper2Lib {
#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);
// Every closed path (ie polygon) is made up of a series of vertices forming edge
// 'bounds' that alternate between ascending bounds (containing edges going up
// Every closed path (ie polygon) is made up of a series of vertices forming edge
// 'bounds' that alternate between ascending bounds (containing edges going up
// relative to the Y-axis) and descending bounds. 'Local Minima' refers to
// vertices where ascending and descending bounds join at the bottom, and
// 'Local Maxima' are where ascending and descending bounds join at the top.
@@ -482,8 +504,7 @@ namespace Clipper2Lib {
inline void SetOwner(OutRec* outrec, OutRec* new_owner)
{
//precondition1: new_owner is never null
while (new_owner->owner && !new_owner->owner->pts)
new_owner->owner = new_owner->owner->owner;
new_owner->owner = GetRealOutRec(new_owner->owner);
OutRec* tmp = new_owner;
while (tmp && tmp != outrec) tmp = tmp->owner;
if (tmp) new_owner->owner = outrec->owner;
@@ -536,9 +557,9 @@ namespace Clipper2Lib {
val = 1 - val; // toggle val
else
{
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val;
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (i == 0) return PointInPolygonResult::IsOn;
if ((i < 0) == is_above) val = 1 - val;
}
is_above = !is_above;
op2 = op2->next;
@@ -546,9 +567,9 @@ namespace Clipper2Lib {
if (is_above != starting_above)
{
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
if (d == 0) return PointInPolygonResult::IsOn;
if ((d < 0) == is_above) val = 1 - val;
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
if (i == 0) return PointInPolygonResult::IsOn;
if ((i < 0) == is_above) val = 1 - val;
}
if (val == 0) return PointInPolygonResult::IsOutside;
@@ -578,30 +599,31 @@ namespace Clipper2Lib {
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
// so we won't jump if the first vertex is found outside
PointInPolygonResult result;
int outside_cnt = 0;
// this function accommodates rounding errors that
// can cause path micro intersections
PointInPolygonResult pip = PointInPolygonResult::IsOn;
OutPt* op = op1;
do
{
result = PointInOpPolygon(op->pt, op2);
if (result == PointInPolygonResult::IsOutside) ++outside_cnt;
else if (result == PointInPolygonResult::IsInside) --outside_cnt;
do {
switch (PointInOpPolygon(op->pt, op2))
{
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;
}
op = op->next;
} while (op != op1 && std::abs(outside_cnt) < 2);
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0);
// since path1's location is still equivocal, check its midpoint
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
Path64 path2 = GetCleanPath(op2);
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
} while (op != op1);
// result unclear, so try again using cleaned paths
return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void AddLocMin(LocalMinimaList& list,
Vertex& vert, PathType polytype, bool is_open)
{
@@ -1126,21 +1148,19 @@ namespace Clipper2Lib {
return newcomer.curr_x > resident.curr_x;
//get the turning direction a1.top, a2.bot, a2.top
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top);
if (d != 0) return d < 0;
int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
if (i != 0) return i < 0;
//edges must be collinear to get here
//for starting open paths, place them according to
//the direction they're about to turn
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
{
return CrossProduct(newcomer.bot,
resident.top, NextVertex(resident)->pt) <= 0;
return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
}
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
{
return CrossProduct(newcomer.bot,
newcomer.top, NextVertex(newcomer)->pt) >= 0;
return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
}
int64_t y = newcomer.bot.y;
@@ -1155,7 +1175,7 @@ namespace Clipper2Lib {
resident.bot, resident.top)) return true;
else
//compare turning direction of the alternate bound
return (CrossProduct(PrevPrevVertex(resident)->pt,
return (CrossProductSign(PrevPrevVertex(resident)->pt,
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
}
@@ -1565,7 +1585,7 @@ namespace Clipper2Lib {
FixSelfIntersects(outrec);
}
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
{
// splitOp.prev -> splitOp &&
// splitOp.next -> splitOp.next.next are intersecting
@@ -1574,7 +1594,7 @@ namespace Clipper2Lib {
outrec->pts = prevOp;
Point64 ip;
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
GetLineIntersectPt(prevOp->pt, splitOp->pt,
splitOp->next->pt, nextNextOp->pt, ip);
#ifdef USINGZ
@@ -1630,7 +1650,7 @@ namespace Clipper2Lib {
if (using_polytree_)
{
if (Path1InsidePath2(prevOp, newOp))
if (Path2ContainsPath1(prevOp, newOp))
{
newOr->splits = new OutRecList();
newOr->splits->emplace_back(outrec);
@@ -1652,19 +1672,32 @@ namespace Clipper2Lib {
void ClipperBase::FixSelfIntersects(OutRec* outrec)
{
OutPt* op2 = outrec->pts;
if (op2->prev == op2->next->next)
return; // because triangles can't self-intersect
for (; ; )
{
// triangles can't self-intersect
if (op2->prev == op2->next->next) break;
if (SegmentsIntersect(op2->prev->pt,
op2->pt, op2->next->pt, op2->next->next->pt))
{
if (op2 == outrec->pts || op2->next == outrec->pts)
outrec->pts = outrec->pts->prev;
DoSplitOp(outrec, op2);
if (!outrec->pts) break;
op2 = outrec->pts;
continue;
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)
outrec->pts = outrec->pts->prev;
DoSplitOp(outrec, op2);
if (!outrec->pts) break;
op2 = outrec->pts;
if (op2->prev == op2->next->next)
break; // again, because triangles can't self-intersect
continue;
}
}
else
op2 = op2->next;
@@ -1805,14 +1838,14 @@ namespace Clipper2Lib {
switch (fillrule_)
{
case FillRule::Positive:
if (edge_c->wind_cnt != 1) return;
case FillRule::Positive:
if (edge_c->wind_cnt != 1) return;
break;
case FillRule::Negative:
if (edge_c->wind_cnt != -1) return;
case FillRule::Negative:
if (edge_c->wind_cnt != -1) return;
break;
default:
if (std::abs(edge_c->wind_cnt) != 1) return;
default:
if (std::abs(edge_c->wind_cnt) != 1) return;
}
#ifdef USINGZ
@@ -1933,7 +1966,7 @@ namespace Clipper2Lib {
const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1;
const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1;
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
if ((!IsHotEdge(e1) && !e1_windcnt_in_01) ||
(!IsHotEdge(e2) && !e2_windcnt_in_01))
return;
@@ -2112,10 +2145,9 @@ namespace Clipper2Lib {
e->prev_in_sel = e->prev_in_ael;
e->next_in_sel = e->next_in_ael;
e->jump = e->next_in_sel;
if (e->join_with == JoinWith::Left)
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
else
e->curr_x = TopX(*e, top_y);
// it is safe to ignore 'joined' edges here because
// if necessary they will be split in IntersectEdges()
e->curr_x = TopX(*e, top_y);
e = e->next_in_ael;
}
}
@@ -2262,15 +2294,14 @@ namespace Clipper2Lib {
void MoveSplits(OutRec* fromOr, OutRec* toOr)
{
if (!fromOr->splits) return;
if (!toOr->splits) toOr->splits = new OutRecList();
OutRecList::iterator orIter = fromOr->splits->begin();
for (; orIter != fromOr->splits->end(); ++orIter)
toOr->splits->emplace_back(*orIter);
if (toOr != *orIter) // #987
toOr->splits->emplace_back(*orIter);
fromOr->splits->clear();
}
void ClipperBase::ProcessHorzJoins()
{
for (const HorzJoin& j : horz_join_list_)
@@ -2299,8 +2330,8 @@ namespace Clipper2Lib {
}
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
OutPt* tmp = or1->pts;
@@ -2311,7 +2342,7 @@ namespace Clipper2Lib {
//or2 is now inside or1
or2->owner = or1;
}
else if (Path1InsidePath2(or2->pts, or1->pts))
else if (Path2ContainsPath1(or2->pts, or1->pts))
{
or2->owner = or1;
}
@@ -2324,13 +2355,14 @@ namespace Clipper2Lib {
else
or2->owner = or1;
}
else
else // joining, not splitting
{
or2->pts = nullptr;
if (using_polytree_)
{
SetOwner(or2, or1);
MoveSplits(or2, or1); //#618
if (or2->splits)
MoveSplits(or2, or1); //#618
}
else
or2->owner = or1;
@@ -2350,7 +2382,7 @@ namespace Clipper2Lib {
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
{
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
//rounding errors can occasionally place the calculated intersection
@@ -2934,22 +2966,28 @@ namespace Clipper2Lib {
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);
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
if (split->splits && CheckSplitOwner(outrec, split->splits))
return true;
else if (CheckBounds(split) &&
IsValidOwner(outrec, split) &&
split->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, split->pts))
{
outrec->owner = split; //found in split
return true;
}
return true;
if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
!Path2ContainsPath1(outrec->pts, split->pts)) continue;
if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
split->owner = outrec->owner;
outrec->owner = split;
return true;
}
return false;
}
@@ -2960,13 +2998,12 @@ namespace Clipper2Lib {
// post-condition: if a valid path, outrec will have a polypath
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
while (outrec->owner)
{
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
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;
}
@@ -3029,6 +3066,7 @@ namespace Clipper2Lib {
{
OutRec* outrec = outrec_list_[i];
if (!outrec || !outrec->pts) continue;
if (outrec->is_open)
{
Path64 path;
@@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 January 2025 *
* Date : 11 October 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : Path Offset (Inflate/Shrink) *
@@ -37,29 +37,35 @@ const double arc_const = 0.002; // <-- 1/500
// 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);
for (size_t i = 0; i < paths.size(); ++i)
{
double a = MAX_DBL;
for (const Point64& pt : paths[i])
{
if ((pt.y < botPt.y) ||
((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.y = pt.y;
}
}
return result;
}
inline double Hypot(double x, double y)
{
// given that this is an internal function, and given the x and y parameters
// will always be coordinate values (or the difference between coordinate values),
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
// x and y should always be within INT64_MIN to INT64_MAX. Consequently,
// there should be no risk that the following computation will overflow
// see https://stackoverflow.com/a/32436148/359538
return std::sqrt(x * x + y * y);
@@ -145,15 +151,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
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,
// then flag the whole group is 'reversed' (will negate delta etc.)
// 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
{
lowest_path_idx = std::nullopt;
lowest_path_idx.reset();
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 pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
//get the second intersect point through reflecion
path_out.emplace_back(ReflectPoint(pt, ptQ));
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 pt = ptQ;
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
path_out.emplace_back(pt);
//get the second intersect point through reflecion
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
path_out.emplace_back(pt.x + offsetVec.x, pt.y + offsetVec.y);
#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
{
offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
@@ -333,9 +341,9 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
if (cos_a > -0.999 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
{
// is concave
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// by far the simplest way to construct concave joins, especially those joining very
// short segments, is to insert 3 points that produce negative regions. These regions
// will be removed later by the finishing union operation. This is also the best way
// to ensure that path reversals (ie over-shrunk paths) are removed.
#ifdef USINGZ
path_out.emplace_back(GetPerpendic(path[j], norms[k], group_delta_), path[j].z);
@@ -366,11 +374,31 @@ void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size
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)
{
path_out.clear();
for (Path64::size_type j = 0, k = path.size() - 1; j < path.size(); k = j, ++j)
OffsetPoint(group, path, j, k);
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);
solution->emplace_back(path_out);
}
@@ -380,7 +408,7 @@ void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
Path64 reverse_path(path);
std::reverse(reverse_path.begin(), reverse_path.end());
//rebuild normals
//rebuild normals
std::reverse(norms.begin(), norms.end());
norms.emplace_back(norms[0]);
norms.erase(norms.begin());
@@ -601,10 +629,10 @@ void ClipperOffset::ExecuteInternal(double delta)
if (!solution->size()) return;
bool paths_reversed = CheckReverseOrientation();
bool paths_reversed = CheckReverseOrientation();
//clean up self-intersections ...
Clipper64 c;
c.PreserveCollinear(false);
c.PreserveCollinear(preserve_collinear_);
//the solution should retain the orientation of the input
c.ReverseSolution(reverse_solution_ != paths_reversed);
#ifdef USINGZ
@@ -1,8 +1,8 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 5 July 2024 *
* Date : 11 October 2025 *
* Website : https://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Copyright : Angus Johnson 2010-2025 *
* Purpose : FAST rectangular clipping *
* License : https://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************/
@@ -77,8 +77,8 @@ namespace Clipper2Lib {
bool GetSegmentIntersection(const Point64& p1,
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
{
double res1 = CrossProduct(p1, p3, p4);
double res2 = CrossProduct(p2, p3, p4);
int res1 = CrossProductSign(p1, p3, p4);
int res2 = CrossProductSign(p2, p3, p4);
if (res1 == 0)
{
ip = p1;
@@ -97,8 +97,8 @@ namespace Clipper2Lib {
}
if ((res1 > 0) == (res2 > 0)) return false;
double res3 = CrossProduct(p3, p1, p2);
double res4 = CrossProduct(p4, p1, p2);
int res3 = CrossProductSign(p3, p1, p2);
int res4 = CrossProductSign(p4, p1, p2);
if (res3 == 0)
{
ip = p3;
@@ -116,7 +116,7 @@ namespace Clipper2Lib {
if ((res3 > 0) == (res4 > 0)) return false;
// 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,
@@ -227,7 +227,7 @@ namespace Clipper2Lib {
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
{
if (AreOpposites(prev, curr))
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
else
return HeadingClockwise(prev, curr);
}
File diff suppressed because it is too large Load Diff
@@ -6,3 +6,4 @@
#include "clipper.engine.cpp"
#include "clipper.offset.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&)
{
#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();
}
template<>
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();
}
+1 -5
View File
@@ -19,11 +19,7 @@ if(Qhull_FOUND)
message(STATUS "Using qhull from system.")
if(SLIC3R_STATIC)
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)
endif()
target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r)
else()
slic3r_remap_configs("Qhull::qhullcpp;Qhull::qhull_r" RelWithDebInfo Release)
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/PublishSettingsDialog.cpp
src/slic3r/GUI/SavePresetDialog.cpp
src/slic3r/GUI/SceneBenchmark.cpp
src/slic3r/GUI/Search.cpp
src/slic3r/GUI/SettingsIndex.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",
"version": "02.04.00.16",
"version": "02.04.00.17",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [
@@ -1448,6 +1448,10 @@
"name": "Snapmaker PETG HF @U1 0.2 nozzle",
"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",
"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",
"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",
"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)"
],
"enable_pressure_advance": [
"0",
"0"
],
"filament_max_volumetric_speed": [
"15",
"15"
],
"filament_vendor": [
"Polymaker"
],
"nozzle_temperature_range_high": [
"230",
"230"
],
"temperature_vitrification": [
@@ -29,5 +32,21 @@
],
"textured_plate_temp_initial_layer": [
"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"
],
"nozzle_temperature": [
"230",
"230"
],
"nozzle_temperature_initial_layer": [
"230",
"230"
],
"nozzle_temperature_range_high": [
"230",
"230"
],
"textured_plate_temp": [
@@ -29,5 +32,21 @@
],
"textured_plate_temp_initial_layer": [
"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"
],
"filament_flow_ratio": [
"0.96",
"0.96"
],
"filament_vendor": [
@@ -29,5 +30,21 @@
],
"textured_plate_temp_initial_layer": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"pressure_advance": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"filament_retraction_length": [
"1.0",
"1.0"
],
"filament_z_hop": [
"0.0",
"0.0"
],
"enable_pressure_advance": [
"1",
"1"
],
"pressure_advance": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"filament_retraction_length": [
"1.0",
"1.0"
],
"filament_z_hop": [
"0.0",
"0.0"
],
"enable_pressure_advance": [
"1",
"1"
],
"pressure_advance": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"filament_retraction_length": [
"1.0",
"1.0"
],
"filament_z_hop": [
"0.0",
"0.0"
],
"description": " ",
"enable_pressure_advance": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"supertack_plate_temp": [
@@ -18,6 +19,19 @@
"70"
],
"pressure_advance": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"15",
"15"
],
"filament_wipe": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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)"
],
"filament_minimal_purge_on_wipe_tower": [
"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,18 +10,30 @@
"Snapmaker U1 (0.4 nozzle)"
],
"fan_max_speed": [
"100",
"100"
],
"fan_min_speed": [
"100",
"100"
],
"filament_flow_ratio": [
"0.98",
"0.98"
],
"filament_max_volumetric_speed": [
"15",
"15"
],
"slow_down_layer_time": [
"4"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
],
"additional_cooling_fan_speed": [
"70",
"70"
]
}
@@ -10,6 +10,23 @@
"Snapmaker U1 (0.4 nozzle)"
],
"filament_minimal_purge_on_wipe_tower": [
"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