Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The format of this changelog is based on
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
[Semantic Versioning](https://semver.org/).

## Upcoming

- Added `xor2d` for polygon XOR

## 1.6.0 (2025-10-16)

- Improved metadata handling for `LayoutTarget` and `SolidModelTarget`
Expand Down
3 changes: 2 additions & 1 deletion docs/src/polygons.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For example, if you ask for the bounding box of a path node (which could define

## Clipping

Geometric Boolean operations on polygons are called "clipping" operations. For 2D geometry, these—`union2d`, `difference2d`, and `intersection2d`—are the only geometric Booleans available. Other geometry types are first converted to polygons using `to_polygons` to perform clipping.
Geometric Boolean operations on polygons are called "clipping" operations. For 2D geometry, these—`union2d`, `difference2d`, `intersection2d`, and `xor2d`—are the only geometric Booleans available. Other geometry types are first converted to polygons using `to_polygons` to perform clipping.

!!! info

Expand All @@ -27,6 +27,7 @@ More general operations may be accomplished using the `clip` function.
union2d
difference2d
intersect2d
xor2d
clip
cliptree
```
Expand Down
6 changes: 4 additions & 2 deletions src/DeviceLayout.jl
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ import .Polygons:
rounded_corner,
sweep_poly,
unfold,
union2d
union2d,
xor2d
export Polygons,
Polygon,
Ellipse,
Expand All @@ -409,7 +410,8 @@ export Polygons,
rounded_corner,
sweep_poly,
unfold,
union2d
union2d,
xor2d

include("align.jl")
using .Align
Expand Down
31 changes: 30 additions & 1 deletion src/polygons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export circle,
rounded_corner,
sweep_poly,
unfold,
union2d
union2d,
xor2d

const USCALE = 1.0 * Unitful.fm
const SCALE = 10.0^9
Expand Down Expand Up @@ -1156,6 +1157,34 @@ function intersect2d(plus, minus)
)
end

"""
xor2d(p1, p2)

Return the symmetric difference (XOR) of `p1` and `p2` as a `ClippedPolygon`.

The XOR operation returns regions that are in either `p1` or `p2`, but not in both.
This is useful for finding non-overlapping regions between two sets of polygons.

Each of `p1` and `p2` may be a `GeometryEntity` or array of `GeometryEntity`. All entities
are first converted to polygons using [`to_polygons`](@ref).

Each of `p1` and `p2` can also be a `GeometryStructure` or `GeometryReference`, in which case
`elements(flatten(p))` will be converted to polygons.

Each can also be a pair `geom => layer`, where `geom` is a
`GeometryStructure` or `GeometryReference`, while `layer` is a `DeviceLayout.Meta`, a layer name `Symbol`, and/or a collection
of either, in which case only the elements in those layers will be used.
"""
function xor2d(p1, p2)
return clip(
Clipper.ClipTypeXor,
p1,
p2,
pfs=Clipper.PolyFillTypePositive,
pfc=Clipper.PolyFillTypePositive
)
end

function add_path!(
c::Clipper.Clip,
path::Vector{Point{T}},
Expand Down
5 changes: 5 additions & 0 deletions test/test_clipping.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
@test to_polygons(
union2d(Rectangle(2, 2) + Point(1, 1), [Rectangle(2, 2), Rectangle(10, 10)])
)[1] == Polygon(Point{Int}[(10, 10), (0, 10), (0, 0), (10, 0)])

# XOR
r1 = Rectangle(2.0, 2.0)
r2 = Rectangle(2.0, 2.0) + Point(1.0, 1.0)
@test xor2d(r1, r2) == union2d(difference2d(r2, r1), difference2d(r1, r2))
end

@testset "> ClippedPolygon operations w/o units" begin
Expand Down