Abstract polygons
In this package, any polygon regardless of its concrete representation in memory should be a subtype of DeviceLayout.AbstractPolygon
. Usually, when we write "polygon" in unformatted text, we mean AbstractPolygon
. (In this documentation, we try to follow this pattern for common words and corresponding abstract types. For example, we'll use "coordinate system" to mean any AbstractCoordinateSystem
including Cell
, not necessarily just CoordinateSystem
.)
DeviceLayout.AbstractPolygon
— Typeabstract type AbstractPolygon{T} <: GeometryEntity{T} end
Anything you could call a polygon regardless of the underlying representation. Currently only Rectangle
or Polygon
are concrete subtypes, but one could imagine further subtypes to represent specific shapes that appear in highly optimized pattern formats. Examples include the OASIS format (which has 25 implementations of trapezoids) or e-beam lithography pattern files like the Raith GPF format.
The most important polygon subtype is Polygon
, which is defined by a vector of points. Polygon
is the primitive entity type for Cell
—any shape being rendered to a Cell
must end up represented as one or more Polygon
. The GeometryEntity
interface provides a to_polygons
function that produces that representation. Most functions in the geometry interface (besides transformation, which must be implemented by subtypes) will fall back to calling to_polygons
on entities first if there is no specialized method.
For example, if you ask for the bounding box of a path node (which could define a shape like multiple parallel brushstrokes) bounds(node)
will simply find the bounding box of the polygon(s) from to_polygons(node)
, using the default tolerance for discretization of curves.
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.
Boolean operations in 3D with SolidModel
are handled by the Open CASCADE Technology kernel, which works directly with rich geometry types rendered from our native CoordinateSystem
.
For many use cases, union2d
, difference2d
, and intersect2d
behave as expected and are easiest to use. More general operations may be accomplished using the clip
function.
DeviceLayout.Polygons.union2d
— Functionunion2d(p1, p2)
Return the geometric union of p1 and p2 as a ClippedPolygon
.
Each of p1
and p2
may be a GeometryEntity
or array of GeometryEntity
. All entities are first converted to polygons using to_polygons
.
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 used.
This is not implemented as a method of union
because you can have a set union of arrays of polygons, which is a distinct operation.
The Clipper polyfill rule is PolyFillTypePositive, meaning as long as a region lies within more non-hole (by orientation) than hole polygons, it lies in the union.
union2d(p)
Return the geometric union of p
or all entities in p
.
DeviceLayout.Polygons.difference2d
— Functiondifference2d(p1, p2)
Return the geometric union of p1
minus the geometric union of p2
as a ClippedPolygon
.
Each of p1
and p2
may be a GeometryEntity
or array of GeometryEntity
. All entities are first converted to polygons using to_polygons
.
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.
DeviceLayout.Polygons.intersect2d
— Functionintersect2d(p1, p2)
Return the geometric union of p1
intersected with the geometric union of p2
as a ClippedPolygon
.
Each of p1
and p2
may be a GeometryEntity
or array of GeometryEntity
. All entities are first converted to polygons using to_polygons
.
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.
DeviceLayout.Polygons.clip
— Functionclip(op::Clipper.ClipType, s, c; kwargs...) where {S<:Coordinate, T<:Coordinate}
clip(op::Clipper.ClipType, s::AbstractVector{A}, c::AbstractVector{B};
kwargs...) where {S, T, A<:Polygon{S}, B<:Polygon{T}}
clip(op::Clipper.ClipType,
s::AbstractVector{Polygon{T}}, c::AbstractVector{Polygon{T}};
pfs::Clipper.PolyFillType=Clipper.PolyFillTypeEvenOdd,
pfc::Clipper.PolyFillType=Clipper.PolyFillTypeEvenOdd) where {T}
Return the ClippedPolygon
resulting from a polygon clipping operation.
Uses the Clipper
library and the Clipper.jl
wrapper to perform polygon clipping.
Positional arguments
The first argument must be one of the following types to specify a clipping operation:
Clipper.ClipTypeDifference
Clipper.ClipTypeIntersection
Clipper.ClipTypeUnion
Clipper.ClipTypeXor
Note that these are types; you should not follow them with ()
.
The second and third argument may be a GeometryEntity
or array of GeometryEntity
. All entities are first converted to polygons using to_polygons
. Each 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 taken from the flattened structure.
Keyword arguments
pfs
and pfc
specify polygon fill rules for the s
and c
arguments, respectively. These arguments may include:
Clipper.PolyFillTypeNegative
Clipper.PolyFillTypePositive
Clipper.PolyFillTypeEvenOdd
Clipper.PolyFillTypeNonZero
See the Clipper
docs for further information.
See also union2d, difference2d, and intersect2d.
DeviceLayout.Polygons.cliptree
— Functioncliptree(op::Clipper.ClipType, s::AbstractPolygon{S}, c::AbstractPolygon{T};
kwargs...) where {S<:Coordinate, T<:Coordinate}
cliptree(op::Clipper.ClipType, s::AbstractVector{A}, c::AbstractVector{B};
kwargs...) where {S, T, A<:AbstractPolygon{S}, B<:AbstractPolygon{T}}
cliptree(op::Clipper.ClipType,
s::AbstractVector{Polygon{T}}, c::AbstractVector{Polygon{T}};
pfs::Clipper.PolyFillType=Clipper.PolyFillTypeEvenOdd,
pfc::Clipper.PolyFillType=Clipper.PolyFillTypeEvenOdd) where {T}
Return a Clipper.PolyNode
representing parent-child relationships between polygons and interior holes. The units and number type may need to be converted.
Uses the Clipper
library and the Clipper.jl
wrapper to perform polygon clipping.
Positional arguments
The first argument must be one of the following types to specify a clipping operation:
Clipper.ClipTypeDifference
Clipper.ClipTypeIntersection
Clipper.ClipTypeUnion
Clipper.ClipTypeXor
Note that these are types; you should not follow them with ()
. The second and third arguments are AbstractPolygon
s or vectors thereof.
Keyword arguments
pfs
and pfc
specify polygon fill rules for the s
and c
arguments, respectively. These arguments may include:
Clipper.PolyFillTypeNegative
Clipper.PolyFillTypePositive
Clipper.PolyFillTypeEvenOdd
Clipper.PolyFillTypeNonZero
See the Clipper
docs for further information.
The results of clipping are represented using the ClippedPolygon <: AbstractPolygon
type, which stores a tree of positive and negative contours. These mainly exist to represent polygons with holes without having to generate "keyhole" polygons as required by the GDSII format. This ends up being convenient for other backends that don't want keyhole polygons as well as for applying different styles to different boundary or hole contours.
DeviceLayout.Polygons.ClippedPolygon
— Typestruct ClippedPolygon{T} <: AbstractPolygon{T}
tree::Clipper.PolyNode{Point{T}}
end
Collection of polygons defined by a call to Clipper.
Styles
In addition to other generic entity styles like NoRender
, AbstractPolygon
s can be paired with the Rounded
style. ClippedPolygon
s support StyleDict
, which allows for different styles to be applied to different contours in its tree.
DeviceLayout.Polygons.Rounded
— Typestruct Rounded{T <: Coordinate} <: GeometryEntityStyle
abs_r::T = zero(T)
rel_r::Float64 = 0.0
min_side_len::T = r
min_angle::Float64 = 1e-3
p0::Vector{Point{T}} = []
inverse_selection::Bool = false
end
Rounded polygon style defined by either radius absolute radius abs_r
or relative radius rel_r
. Only one of abs_r
or rel_r
can be non-zero at once. Can't handle shapes with interior cuts, or shapes with too sharp of angles relative to segment length. If rel_r
is non-zero the radius of curvature at each vertex is calculated with rel_r * min(l₁, l₂)
where l₁
and l₂
denote the length of the two attached line segments.
Example usage:
r = Rectangle(10μm, 10μm)
rsty = Rounded(1μm)
# Create a rounded rectangle StyledEntity with different options for syntax
rounded_rect = rsty(r)
rounded_rect = styled(r, rsty)
rounded_rect = Rounded(r, 1μm)
# Turn the result into a plain Polygon
rounded_rect_discretized_poly = to_polygons(rounded_rect)
Keyword arguments
min_side_len
: The minimum side length that will get rounded (e.g. for 90-degree angles, it makes sense to havemin_side_len = 2 * rounding_radius
). This currently uses exact comparison, so it may result in very short straight edges or failure to round a corner due to floating point imprecision.min_angle
: If adjacent sides are collinear within the tolerance set bymin_angle
, rounding will not be performed.p0
: set of target points used to select vertices to attempt to round when applied to a polygon. Selected vertices wheremin_side_len
andmin_angle
are satisfied will be rounded. If empty, all vertices will be selected. Otherwise, for each point inp0
, the nearest point in the styled polygon will be selected. Note that for aClippedPolygon
, the samep0
will be used for every contour; for different rounding styles on different contours, useStyleDict
.inverse_selection
: If true, the selection fromp0
is inverted; that is, all corners will be rounded except those selected byp0
.
DeviceLayout.Polygons.StyleDict
— Typestruct StyleDict{S} <: GeometryEntityStyle where {S}
styles::Dict{Vector{Int}, GeometryEntityStyle},
default::S
end
Style used for applying differing styles to different Polygons at different levels within a ClippedPolygon
or CurvilinearRegion
. Styles are stored by the sequence of child indices required to find the corresponding Clipper.PolyNode
within the ClippedPolygon
. For a CurvilinearRegion
only dictionaries of depth 2 (a single parent and one set of holes) are valid.
Offsetting
DeviceLayout.offset
— Functionoffset{S<:Coordinate}(s::AbstractPolygon{S}, delta::Coordinate;
j::Clipper.JoinType=Clipper.JoinTypeMiter,
e::Clipper.EndType=Clipper.EndTypeClosedPolygon)
offset{S<:AbstractPolygon}(subject::AbstractVector{S}, delta::Coordinate;
j::Clipper.JoinType=Clipper.JoinTypeMiter,
e::Clipper.EndType=Clipper.EndTypeClosedPolygon)
offset{S<:Polygon}(s::AbstractVector{S}, delta::Coordinate;
j::Clipper.JoinType=Clipper.JoinTypeMiter,
e::Clipper.EndType=Clipper.EndTypeClosedPolygon)
Using the Clipper
library and the Clipper.jl
wrapper, perform polygon offsetting.
The orientations of polygons must be consistent, such that outer polygons share the same orientation, and any holes have the opposite orientation. Additionally, any holes should be contained within outer polygons; offsetting hole edges may create positive artifacts at corners.
The first argument should be an AbstractPolygon
. The second argument is how much to offset the polygon. Keyword arguments include a join type:
Clipper.JoinTypeMiter
Clipper.JoinTypeRound
Clipper.JoinTypeSquare
and also an end type:
Clipper.EndTypeClosedPolygon
Clipper.EndTypeClosedLine
Clipper.EndTypeOpenSquare
Clipper.EndTypeOpenRound
Clipper.EndTypeOpenButt
Rectangle API
DeviceLayout.Rectangles.Rectangle
— Typestruct Rectangle{T} <: AbstractPolygon{T}
ll::Point{T}
ur::Point{T}
function Rectangle(a,b)
# Ensure ll is lower-left, ur is upper-right.
ll = Point(a.<=b) .* a + Point(b.<=a) .* b
ur = Point(a.<=b) .* b + Point(b.<=a) .* a
new(ll,ur)
end
end
A rectangle, defined by opposing lower-left and upper-right corner coordinates. Lower-left and upper-right are guaranteed to be such by the inner constructor.
DeviceLayout.Rectangles.Rectangle
— MethodRectangle(ll::Point, ur::Point)
Convenience constructor for Rectangle
objects.
DeviceLayout.Rectangles.Rectangle
— MethodRectangle(width, height)
Constructs Rectangle
objects by specifying the width and height rather than the lower-left and upper-right corners.
The rectangle will sit with the lower-left corner at the origin. With centered rectangles we would need to divide width and height by 2 to properly position. If we wanted an object of Rectangle{Int}
type, this would not be possible if either width
or height
were odd numbers. This definition ensures type stability in the constructor.
Rectangle
has the special importance of being the return type of bounds
.
DeviceLayout.bounds
— Methodbounds(r::Rectangle)
No-op (just returns r
).
DeviceLayout.Rectangles.height
— Methodheight(r::Rectangle)
Return the height of a rectangle.
DeviceLayout.Rectangles.isproper
— Methodisproper(r::Rectangle)
Return true
if the rectangle has a non-zero area. Otherwise, returns false
. Note that the upper-right and lower-left corners are enforced to be the ur
and ll
fields of a Rectangle
by the inner constructor.
DeviceLayout.Points.lowerleft
— Methodlowerleft(r::Rectangle)
Return the lower-left corner of a rectangle (Point object).
DeviceLayout.Points.upperright
— Methodupperright(r::Rectangle)
Return the upper-right corner of a rectangle (Point object).
DeviceLayout.Polygons.points
— Methodpoints{T}(x::Rectangle{T})
Return the array of Point
objects defining the rectangle.
DeviceLayout.Rectangles.width
— Methodwidth(r::Rectangle)
Return the width of a rectangle.
Polygon API
DeviceLayout.Polygons.Polygon
— Typestruct Polygon{T} <: AbstractPolygon{T}
p::Vector{Point{T}}
Polygon(x) = new(x)
Polygon(x::AbstractPolygon) = convert(Polygon{T}, x)
end
Polygon defined by list of coordinates. The first point should not be repeated at the end (although this is true for the GDS format).
DeviceLayout.Polygons.Polygon
— MethodPolygon{T}(parr::AbstractVector{Point{T}})
Convenience constructor for a Polygon{T}
object.
DeviceLayout.Polygons.Polygon
— MethodPolygon(p0::Point, p1::Point, p2::Point, p3::Point...)
Convenience constructor for a Polygon{T}
object.
DeviceLayout.Polygons.perimeter
— Functionperimeter(poly::AbstractPolygon)
The (Euclidean) perimeter of an AbstractPolygon
.
perimeter(poly::ClippedPolygon)
The (Euclidean) perimeter of the outermost contour of a ClippedPolygon
perimeter(poly::Ellipse)
Approximate (Euclidean) perimeter of an Ellipse
using Ramanujan's approximation formula https://arxiv.org/pdf/math/0506384.pdf
DeviceLayout.Polygons.points
— Functionpoints(x::Polygon)
Return the array of Point
objects defining the polygon.
points{T}(x::Rectangle{T})
Return the array of Point
objects defining the rectangle.
points(x::ClippedPolygon)
Return the array of Point
objects that define the keyhole polygon.
points(x::Clipper.PolyNode)
Return the array of Point
objects that make up the contour of the PolyNode
DeviceLayout.Polygons.sweep_poly
— Functionsweep_poly(poly::Polygon, displacement::Point)
Return a Polygon
corresponding to the boundary formed by poly
swept by displacement
.
This is the result you would get by painting with a brush shaped like poly
and moving it along a line by displacement
.
DeviceLayout.Polygons.gridpoints_in_polygon
— Functiongridpoints_in_polygon(poly::AbstractArray{<:AbstractPolygon},
dx::Coordinate, dy::Coordinate; b=nothing)
Return a BitArray
for the gridpoints in b
with true
for gridpoints in poly
.
Only grid points in the bounding box b
will be considered; if b
is nothing
, then bounds(poly) is used. dx
and dy
are the distances between adjacent points on the rectangular grid. The grid points represented by the BitArray
start from the lower left point p0 = (m*dx, n*dy)
with m
and n
integers and p0
lying in b
.
All polygons should have the same orientation (clockwise or counterclockwise). A mix (for example to represent "holes") may not give the desired behavior on polygon or hole edges.
gridpoints_in_polygon(poly::AbstractArray{<:AbstractPolygon},
grid_x::AbstractArray, grid_y::AbstractArray)
Return a BitArray
with true
for points lying in some polygon in poly
.
The BitArray
values correspond to points (x, y)
with x ∈ grid_x
, y ∈ grid_y
, starting from the lower left.
All polygons should have the same orientation (clockwise or counterclockwise). A mix (for example to represent "holes") may not give the desired behavior on polygon or hole edges.