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.AbstractPolygonType
abstract 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.

source

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.

Info

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.union2dFunction
union2d(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.

source
union2d(p)

Return the geometric union of p or all entities in p.

source
DeviceLayout.Polygons.difference2dFunction
difference2d(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.

source
DeviceLayout.Polygons.intersect2dFunction
intersect2d(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.

source
DeviceLayout.Polygons.clipFunction
clip(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.

source
DeviceLayout.Polygons.cliptreeFunction
cliptree(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 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.PolyFillTypeNegative
  • Clipper.PolyFillTypePositive
  • Clipper.PolyFillTypeEvenOdd
  • Clipper.PolyFillTypeNonZero

See the Clipper docs for further information.

source

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.

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.RoundedType
struct 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 have min_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 by min_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 where min_side_len and min_angle are satisfied will be rounded. If empty, all vertices will be selected. Otherwise, for each point in p0, the nearest point in the styled polygon will be selected. Note that for a ClippedPolygon, the same p0 will be used for every contour; for different rounding styles on different contours, use StyleDict.
  • inverse_selection: If true, the selection from p0 is inverted; that is, all corners will be rounded except those selected by p0.
source
DeviceLayout.Polygons.StyleDictType
struct 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.

source

Offsetting

DeviceLayout.offsetFunction
offset{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
source

Rectangle API

DeviceLayout.Rectangles.RectangleType
struct 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.

source
DeviceLayout.Rectangles.RectangleMethod
Rectangle(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.

source
DeviceLayout.Rectangles.isproperMethod
isproper(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.

source

Polygon API

DeviceLayout.Polygons.PolygonType
struct 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).

source
DeviceLayout.Polygons.perimeterFunction
perimeter(poly::AbstractPolygon)

The (Euclidean) perimeter of an AbstractPolygon.

source
perimeter(poly::ClippedPolygon)

The (Euclidean) perimeter of the outermost contour of a ClippedPolygon

source
perimeter(poly::Ellipse)

Approximate (Euclidean) perimeter of an Ellipse using Ramanujan's approximation formula https://arxiv.org/pdf/math/0506384.pdf

source
DeviceLayout.Polygons.pointsFunction
points(x::Polygon)

Return the array of Point objects defining the polygon.

source
points{T}(x::Rectangle{T})

Return the array of Point objects defining the rectangle.

source
points(x::ClippedPolygon)

Return the array of Point objects that define the keyhole polygon.

source
points(x::Clipper.PolyNode)

Return the array of Point objects that make up the contour of the PolyNode

source
DeviceLayout.Polygons.sweep_polyFunction
sweep_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.

source
DeviceLayout.Polygons.gridpoints_in_polygonFunction
gridpoints_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.

source
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.

source