Coordinate Systems
Coordinate systems (subtypes of AbstractCoordinateSystem
) group geometric objects into a single structure with a common origin and coordinate axes. They can contain references to other DeviceLayout.GeometryStructure
s, a GeometryEntity
list, and metadata for each entity.
AbstractCoordinateSystems
DeviceLayout.AbstractCoordinateSystem
— TypeAbstractCoordinateSystem{S<:Coordinate} <: GeometryStructure{S}
Abstract supertype for coordinate systems, including CoordinateSystem
and Cell
.
Also exists to avoid circular definitions involving the concrete AbstractCoordinateSystem
types and subtypes of GeometryReference
.
Because these are subtypes of GeometryStructure
, they can be used with the transformation interface as well as the structure interface including bounds
and other operations.
DeviceLayout.jl defines the concrete CoordinateSystem
as a backend-agnostic (or "native") representation that can be converted to other representations as necessary, and Cell
as the concrete representation corresponding to the GDSII format. There is also the subtype SchematicDrivenLayout.Schematic
, which composes a CoordinateSystem
with schematic-level information about component connectivity.
Referenced coordinate systems
Coordinate systems can be arrayed or referenced within other coordinate systems. These can be accessed in the array returned by refs(cs)
or by indexing the parent coordinate system or reference with the referenced structure's name, as in cs["referenced_cs"]["deeper_cs"]
.
The methods addref!
and addarr!
are provided for adding structure references and array references.
DeviceLayout.CoordinateSystems.addref!
— Functionaddref!(c1::AbstractCoordinateSystem, cr::GeometryReference)
Add the reference cr
to the list of references in c1
.
addref!(c1::AbstractCoordinateSystem{T},
c2::GeometryStructure,
origin=zero(Point{T});
kwargs...)
Add a reference to c2
to the list of references in c1
.
The reference to c2
has origin origin
; x-reflection, magnification factor, and rotation are set by keywords arguments.
Synonyms are accepted for these keywords:
- X-reflection:
:xrefl
,:xreflection
,:refl
,:reflect
,:xreflect
,:xmirror
,:mirror
- Magnification:
:mag
,:magnification
,:magnify
,:zoom
,:scale
- Rotation:
:rot
,:rotation
,:rotate
,:angle
DeviceLayout.CoordinateSystems.addarr!
— Functionaddarr!(
c1::AbstractCoordinateSystem{T},
c2::GeometryStructure,
origin::Point=zero(Point{T});
kwargs...
)
Add an ArrayReference
to c2
to the list of references in c1
.
The reference to c2
has origin origin
. Keyword arguments specify the column vector, row vector, number of columns, number of rows, x-reflection, magnification factor, and rotation.
Synonyms are accepted for these keywords:
- Column vector
dc::Point{T}
::deltacol
,:dcol
,:dc
,:vcol
,:colv
,:colvec
,:colvector
,:columnv
,:columnvec
,:columnvector
- Row vector:
:deltarow
,:drow
,:dr
,:vrow
,:rv
,:rowvec
,:rowvector
- Number of columns:
:nc
,:numcols
,:numcol
,:ncols
,:ncol
- Number of rows:
:nr
,:numrows
,:numrow
,:nrows
,:nrow
- X-reflection:
:xrefl
,:xreflection
,:refl
,:reflect
,:xreflect
,:xmirror
,:mirror
- Magnification:
:mag
,:magnification
,:magnify
,:zoom
,:scale
- Rotation:
:rot
,:rotation
,:rotate
,:angle
addarr!(
c1::AbstractCoordinateSystem,
c2::GeometryStructure,
c::AbstractRange,
r::AbstractRange;
kwargs...
)
Add an ArrayReference
to c2
to the list of references in c1
, based on ranges.
c
specifies column coordinates and r
for the rows. Pairs from c
and r
specify the origins of the repeated cells. The extrema of the ranges therefore do not specify the extrema of the resulting ArrayReference
's bounding box; some care is required.
Keyword arguments specify x-reflection, magnification factor, and rotation, with synonyms allowed:
- X-reflection:
:xrefl
,:xreflection
,:refl
,:reflect
,:xreflect
,:xmirror
,:mirror
- Magnification:
:mag
,:magnification
,:magnify
,:zoom
,:scale
- Rotation:
:rot
,:rotation
,:rotate
,:angle
Flattening
Sometimes it's also helpful use the flatten
operation to produce an equivalent coordinate system with no references—that is, with all its elements at the top level. CoordinateSystem
s and Cell
s can also be flattened in place with flatten!
.
DeviceLayout.CoordinateSystems.flatten!
— Methodflatten!(c::AbstractCoordinateSystem, depth::Integer=-1, metadata_filter=nothing, max_copy=Inf)
Recursively flatten references and arrays up to a hierarchical depth
, adding their elements to c
with appropriate transformations.
The references and arrays that were flattened are then discarded. Deeper references and arrays are brought upwards and are not discarded.
This function has no effect for depth == 0
, and unlimited depth by default.
Cells
Cell
s are the concrete AbstractCoordinateSystem
representation corresponding to the GDSII format. Accordingly, they hold Polygon
s with metadata of type GDSMeta
(added using render!
). They can also hold Text
objects, and they can be saved directly to a .gds
file.
DeviceLayout.Cells.Cell
— Typemutable struct Cell{S<:Coordinate}
A cell has a name and contains polygons and references to CellArray
or CellReference
objects. It also records the time of its own creation. As currently implemented it mirrors the notion of cells in GDSII files.
To add elements, use render!
. To add references, use addref!
or addarr!
. To add text, use text!
.
DeviceLayout.Cells.Cell
— MethodCell(name::AbstractString)
Convenience constructor for Cell{typeof(1.0UPREFERRED)}
.
DeviceLayout.UPREFERRED
is a constant set according to the unit
preference in Project.toml
or LocalPreferences.toml
. The default ("PreferNanometers"
) gives const UPREFERRED = DeviceLayout.nm
, with mixed-unit operations preferring conversion to nm
.
Unit preference does not affect the database scale for GDS export.
DeviceLayout.Cells.dbscale
— Methoddbscale(c::Cell)
Give the database scale for a cell. The database scale is the smallest increment of length that will be represented in the output CAD file. This is different from the working coordinate type T
of the Cell
.
The database scale defaults to 1nm
(1.0nm
if T <: FloatCoordinate
), but can be changed by updating c.dbscale
to a new Unitful.Length
quantity.
DeviceLayout.Cells.dbscale
— Methoddbscale(cell::Cell...)
Choose an appropriate database scale for a GDSII file given Cell
s of different types. The smallest database scale of all cells considered is returned.
DeviceLayout.Cells.CellArray
— Typeconst CellArray
Alias for ArrayReference
s to Cell
s.
DeviceLayout.Cells.CellReference
— Typeconst CellReference
Alias for StructureReference
s to Cell
s.
DeviceLayout.GDSMeta
— Typestruct GDSMeta <: DeviceLayout.Meta
layer::Int
datatype::Int
GDSMeta() = new(DEFAULT_LAYER, DEFAULT_DATATYPE)
GDSMeta(l) = new(l, DEFAULT_DATATYPE)
GDSMeta(l, d) = new(l, d)
end
Metadata associated with GDSII format. Default layer and datatype are 0.
DeviceLayout.render!
— Methodrender!(c::Cell, p::Polygon, meta::GDSMeta=GDSMeta())
Render a polygon p
to cell c
, defaulting to plain styling. If p
has more than 8190 (set by DeviceLayout's GDS_POLYGON_MAX
constant), then it is partitioned into smaller polygons which are then rendered. Environment variable ENV["GDS_POLYGON_MAX"]
will override this constant. The partitioning algorithm implements guillotine cutting, that goes through at least one existing vertex and in manhattan directions. Cuts are selected by ad hoc optimzation for "nice" partitions.
DeviceLayout.save
— Methodsave(::Union{AbstractString,IO}, cell0::Cell{T}, cell::Cell...)
save(f::File{format"GDS"}, cell0::Cell, cell::Cell...;
name="GDSIILIB", userunit=1μm, modify=now(), acc=now(),
verbose=false)
This bottom method is implicitly called when you use the convenient syntax of the top method: save("/path/to/my.gds", cells_i_want_to_save...)
Keyword arguments include:
name
: used for the internal library name of the GDSII file and probably inconsequential for modern workflows.userunit
: sets what 1.0 corresponds to when viewing this file in graphical GDS editors with inferior unit support.modify
: date of last modification.acc
: date of last accession. It would be unusual to have this differ fromnow()
.verbose
: monitor the output oftraverse!
andorder!
to see if something funny is happening while saving.
The type parameter S
of a Cell{S}
object determine the type of the coordinates of all polygons in a cell, including the units and whether integer or floating-point values are used. Currently, you cannot do a whole lot (particularly with regard to paths) if the cell has integer coordinates. However, they do have an inherent advantage because the coordinates are exact, and ultimately the GDSII file represents shapes with integer coordinates.
Separately, the Cell
has a "database scale" (the dbscale
field) applied when saving that defaults to 1nm
but can be changed.
For most cases, if you want to use units, Cell("my_cell_name", nm)
is a good way to construct a cell which will ultimately have all coordinates rounded to the nearest nm
when exported into GDSII. You can add polygons with whatever length units you want to such a cell, and the coordinates will be converted automatically to nm
.
If you don't want units, just construct the cell with a name only: Cell("my_cell_name")
will return a Cell{Float64}
object unless you have set a unit preference. In this case too, the ultimate database resolution is 1nm
; until exporting the cell into a GDSII file, the coordinates are interpreted to be in units of 1μm
.
When saving cells to disk, keep in mind that cells should have unique names. We don't have an automatic renaming scheme implemented to avoid clashes. To help with this, we provide a function uniquename
to generate unique names based on human-readable prefixes.
When saving cells to disk, there will be a tree of interdependencies and logically one would prefer to write the leaf nodes of the tree before any dependent cells. These functions are used to traverse the tree and then find the optimal ordering.
DeviceLayout.CoordinateSystems.traverse!
— Functiontraverse!(a::AbstractArray, c::GeometryStructure, level=1)
Given a coordinate system, recursively traverse its references for other coordinate systems and add to array a
some tuples: (level, c)
. level
corresponds to how deep the coordinate system was found, and c
is the found coordinate system.
DeviceLayout.CoordinateSystems.order!
— Functionorder!(a::AbstractArray)
Given an array of tuples like that coming out of traverse!
, we sort by the level
, strip the level out, and then retain unique entries. The aim of this function is to determine an optimal writing order when saving pattern data (although the GDSII spec does not require cells to be in a particular order, there may be performance ramifications).
For performance reasons, this function modifies a
but what you want is the returned result array.
CoordinateSystems
CoordinateSystem
s are "DeviceLayout-native" AbstractCoordinateSystem
s. Unlike Cell
s, they can hold any GeometryEntity
, not just Polygon
s, as well as references to any GeometryStructure
, not just Cell
s. The idea is to work with an exact or logical representation of geometric elements as long as possible, deferring decisions about output representations until rendering time. This allows things like calculating intersection points between Path
s in a hierarchy of CoordinateSystem
s and references, or rendering exact curves to a SolidModel
.
Because of this, we add entities to a CoordinateSystem
with place!
rather than render!
. The different verb is meant to convey that the entity is added as-is rather than potentially converted to some other representation. (For convenience, render!
still works with CoordinateSystem
s, but it's just an alias for place!
.)
DeviceLayout.CoordinateSystems.CoordinateSystem
— Typemutable struct CoordinateSystem{S<:Coordinate} <: AbstractCoordinateSystem{S}
name::String
elements::Vector{GeometryEntity{S}}
meta::Vector{Meta}
refs::Vector{GeometryReference}
create::DateTime
CoordinateSystem{S}(x, y, ym, z, t) where {S} = new{S}(x, y, ym, z, t)
CoordinateSystem{S}(x, y, ym, z) where {S} = new{S}(x, y, ym, z, now())
CoordinateSystem{S}(x, y, ym) where {S} = new{S}(x, y, ym, GeometryReference[], now())
CoordinateSystem{S}(x) where {S} =
new{S}(x, GeometryEntity{S}[], Meta[], GeometryReference[], now())
CoordinateSystem{S}() where {S} = begin
c = new{S}()
c.elements = GeometryEntity{S}[]
c.meta = Meta[]
c.refs = GeometryReference[]
c.create = now()
c
end
end
A CoordinateSystem
has a name and contains geometry entities (Polygons, Rectangles) and references to GeometryStructure
objects. It also records the time of its own creation.
To add elements, use place!
, or use render!
to be agnostic between CoordinateSystem
and Cell
. To add references, use addref!
or addarr!
.
DeviceLayout.CoordinateSystems.CoordinateSystemReference
— Typeconst CoordinateSystemReference
Alias for StructureReference
s to CoordinateSystem
s.
DeviceLayout.CoordinateSystems.CoordinateSystemArray
— Typeconst CoordinateSystemArray
Alias for ArrayReference
s to CoordinateSystem
s.
DeviceLayout.CoordinateSystems.place!
— Functionplace!(cs::CoordinateSystem, ent::GeometryEntity, metadata)
Place ent
in cs
with metadata metadata
.
place!(cs::CoordinateSystem, s::GeometryStructure)
Place a reference to s
in cs
.
place!(cs::CoordinateSystem, p::Path, metadata=p.metadata)
Place a reference to p
in cs
and set metadata to metadata
.
As with a Cell{S}
, a CoordinateSystem{S}
has a coordinate type parameter S
. Unlike Cell
s, a CoordinateSystem
is not tied to a database unit (a GDSII concept).
Semantic metadata and rendering to cells
Since CoordinateSystem
s are intended to be backend-agnostic, a useful pattern is to give coordinate objects "semantic" metadata, consisting of a layer name Symbol
as well as level
and index
attributes. This metadata can then be processed with designer-provided methods when rendering to an output format.
DeviceLayout.SemanticMeta
— Typestruct SemanticMeta <: DeviceLayout.Meta
layer::Symbol
index::Int = 1
level::Int = 1
end
SemanticMeta(layer::String; kwargs...)
SemanticMeta(meta::Meta; kwargs...)
DeviceLayout-native representation of an object's layer information and attributes.
Semantic metadata refers to the meaning of an element without reference to a fixed encoding. For example, “this polygon is in the negative of the ground plane” is semantic, while “this polygon is in GDS layer 1, datatype 2” is not. The semantic metadata is used in the final render
step, where a layout is converted from a CoordinateSystem
to a representation corresponding to a particular output format (e.g., a Cell
for GDSII). A call to render!(cell::Cell{S}, cs::CoordinateSystem; map_meta = identity, kwargs...)
will use the map_meta
function to map each GeometryEntity
's metadata to GDSMeta
.
The level
and index
fields do not have a strict interpretation imposed by DeviceLayout. (In this sense they are similar to GDS datatype
.) The suggested use is as follows:
index
distinguishes numbered instances within a layer, for example in greyscale lithography or port numberinglevel
distinguishes instances of a layer occurring in different contexts, such as in a 3D stack where equivalent layers may be present in multiple levels
DeviceLayout.layer
— Functionlayer(m::Meta)
The layer specified by m
, as a Symbol
.
For example, layer(GDSMeta(1, 2))
is :GDS1_2
, and layername(SemanticMeta(:base))
is :base
.
DeviceLayout.layerindex
— Functionlayerindex(m::Meta)
The index
specified by metadata m
. Defaults to 1
for metadata types without an index
.
DeviceLayout.layername
— Functionlayername(m::Meta)
The layer specified by m
, as a String
.
For example, layer(GDSMeta(1, 2))
is "GDS1_2"
, and layername(SemanticMeta(:base))
is "base"
.
DeviceLayout.level
— Functionlevel(m::Meta)
The level
specified by metadata s
. Defaults to 1
for metadata types without a level
.
This means that a CoordinateSystem
can be rendered to a Cell
for output to a GDS format. During rendering, an entity::GeometryEntity
with metadata SemanticMeta(:ground_plane)
would be mapped to to_polygons(entity)
with GDS layer number and datatype (for example, GDSMeta(1,0)
) corresponding to the ground plane according to a mapping function map_meta
provided to render!
.
DeviceLayout.Cells.Cell
— MethodCell(cs::CoordinateSystem{S}) = Cell{S}(cs)
Cell(cs::CoordinateSystem, unit::CoordinateUnits) = Cell{typeof(1.0unit)}(cs)
Cell{S}(cs::CoordinateSystem) where {S}
Construct a Cell
from a CoordinateSystem
by rendering its contents, reproducing the reference hierarchy.
DeviceLayout.render!
— Methodrender!(cell::Cell{S}, cs::GeometryStructure;
memoized_cells=Dict{CoordinateSystem, Cell}(),
map_meta = identity,
kwargs...) where {S}
Render a geometry structure (e.g., CoordinateSystem
) to a cell.
Passes each element and its metadata (mapped by map_meta
if a method is supplied) to render!(::Cell, element, ::Meta)
, traversing the references such that if a CoordinateSystem is referred to in multiple places, it will become a single cell referred to in multiple places.
Rendering a GeometryEntity
to a Cell
uses the optional keyword arguments
map_meta
, a function that takes aMeta
object and returns anotherMeta
object (ornothing
, in which case rendering is skipped)
Usage note: calling this function with non-empty dictionary memoized_cells = Dict{CoordinateSystem, Cell}(cs => cell)
is effectively a manual override that forces cs
to render as cell
.
Note that Cell
s inherit the names of rendered CoordinateSystem
s, so the original coordinate systems ought to have unique names (for example using uniquename
).