mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 10:21:17 +00:00
Since main moved to Clipper2, parts of this branch no longer gave the same G-code as main: - bridge_over_infill dropped expand(limiting_area, 0.3 * flow.spacing()) as a no-op. The offset is below one unit, but Clipper2 still unites its result, which splits and merges touching polygons and so changes the anchor lines. Running it on the polygons next to the bridge gives main's anchors without the whole-layer pass. - tsp_remove_crossings stopped at the first repeated ordering, where main runs on to its pn * pn cap. The loop is periodic from that point, so it now takes only the steps to the ordering main stops on. - With a single tile, the tiled booleans now make the plain call instead of cutting the clip to the tile first. The tiled boolean test compared rings exactly. With the safety offset a tile unites only the clip polygons near it, and Clipper2 can then round a crossing 1 unit differently, so that case allows 1 unit. Comments that named ClipperLib now say Clipper, and docs/HLSD/polygon-clipping.md describes the tiled booleans. G-code of the five handy models in four configurations and of a baked texture relief is byte-identical to main. Colour-painted models still differ: segmenting each island on its own splits a colour's region into different pieces than one diagram over the layer, which on one model also changes the first layer's tool order.
149 lines
8.0 KiB
Markdown
149 lines
8.0 KiB
Markdown
# 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.
|
||
|
||
### Tiled booleans
|
||
|
||
The sweep slows down with the number of edges crossing a scan line, so a layer
|
||
cut into thousands of pieces makes every whole-layer boolean expensive.
|
||
`diff_ex_by_piece()` and `intersection_ex_by_piece()` take a subject of
|
||
non-overlapping `ExPolygons`, group them into tiles with
|
||
`ClipperUtils::tile_expolygons()`, and run each tile in parallel against only
|
||
the clip polygons near it, cut to the tile's box. Below 128 pieces there is a
|
||
single tile, and they are the plain `diff_ex()` / `intersection_ex()`.
|
||
|
||
The result covers the same area as the plain call. Without the safety offset
|
||
the rings are the same. With it, each tile unites only the clip polygons near
|
||
it, so a clip edge that the whole-layer union splits where it crosses a distant
|
||
clip polygon stays whole, and a crossing with the subject can round 1 unit
|
||
differently. The tiles' results are concatenated in tile order, so the order of
|
||
the output `ExPolygons` differs from the plain call.
|
||
|
||
### Offsets
|
||
|
||
- 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`.
|