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} endAnything you could call a polygon regardless of the underlying representation. Currently only Rectangle, Polygon, and ClippedPolygon 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 Polygons. 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.ClipTypeDifferenceClipper.ClipTypeIntersectionClipper.ClipTypeUnionClipper.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.PolyFillTypeNegativeClipper.PolyFillTypePositiveClipper.PolyFillTypeEvenOddClipper.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.ClipTypeDifferenceClipper.ClipTypeIntersectionClipper.ClipTypeUnionClipper.ClipTypeXor
Note that these are types; you should not follow them with (). The second and third arguments are AbstractPolygons or vectors thereof.
Keyword arguments
pfs and pfc specify polygon fill rules for the s and c arguments, respectively. These arguments may include:
Clipper.PolyFillTypeNegativeClipper.PolyFillTypePositiveClipper.PolyFillTypeEvenOddClipper.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}}
endCollection of polygons defined by a call to Clipper.
Styles
In addition to other generic entity styles like NoRender, AbstractPolygons can be paired with the Rounded style. ClippedPolygons 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
endRounded 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_lenandmin_angleare 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 samep0will be used for every contour; for different rounding styles on different contours, useStyleDict.inverse_selection: If true, the selection fromp0is 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
endStyle 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.JoinTypeMiterClipper.JoinTypeRoundClipper.JoinTypeSquare
and also an end type:
Clipper.EndTypeClosedPolygonClipper.EndTypeClosedLineClipper.EndTypeOpenSquareClipper.EndTypeOpenRoundClipper.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
endA 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)
endPolygon 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.
DeviceLayout.Polygons.unfold — Functionunfold(v::Vector{Point{T}}, direction; through_pt=nothing) where {T}
unfold(v::Vector{Point{T}}, p0, p1) where {T}Return a vector of twice the length of v, where the first half is v and the second half is v in reverse order and reflected about an axis.
This can be used to construct polygons that have a mirror symmetry. The symmetry axis can be defined in either of two ways: as a line with a given direction passing through point through_pt (defaults to origin), or by two points p0, p1. direction can be passed either as an angle or as a Point representing a vector.
As a trivial example, to draw a centered square:
uy = Point(0μm, 1μm) # could also be passed as 90°
pts = [Point(-1μm, -1μm), Point(-1μm, 1μm)]
square = Polygon(unfold(pts, uy))