Paths
A Paths.Path
is an ordered collection of Paths.Node
s, each of which has a Paths.Segment
and a Paths.Style
. The nodes are linked to each other, so each node knows what the previous and next nodes are.
Because Path
is a subtype of GeometryStructure
, paths can be used with the transformation interface as well as the structure interface including bounds
and other operations.
Segments
Segments describe the curve a Paths.Node
follows. For example, Paths.Straight
or Paths.Turn
are used frequently. In general, each subtype of Segment
can represent a class of parametric functions t->Point(x(t),y(t))
.
This package assumes that the parametric functions are implemented such that $\sqrt{((dx/dt)^2 + (dy/dt)^2)} = 1$. In other words, t
ranges from zero to the path length of the segment.
Instances of these subtypes of Segment
specify a particular path in the plane. Instances of Turn
, for example, will capture an initial and final angle, a radius, and an origin. All circular turns may be parameterized with these variables.
Another useful Segment
subtype is Paths.BSpline
, which interpolates between two or more points with specified start and end tangents.
Styles
Each subtype of Style
describes how to render a segment. They define a one-dimensional cross-section that is swept along the Segment
and that can vary with arclength along the segment. You can create the most common styles using the constructors Paths.Trace
(a trace with some width) and Paths.CPW
(a coplanar waveguide style).
One can implement new styles by writing rendering methods (for GDSII, that would be to_polygons
) that dispatch on different pairs of segment and style types. In this way, the rendering code can be specialized for the task at hand, improving performance and shrinking generated file sizes (ideally).
Tapers
As a convenience, this package provides functions for the automatic tapering of both Paths.Trace
and Paths.CPW
via the Paths.Taper
constructor. Alternatively, one can specify the tapers concretely by calling their respective constructors.
The following example illustrates the use of automatic tapering. First, we construct a taper with two different traces surrounding it:
using DeviceLayout, FileIO;
p = Path(μm)
straight!(p, 10μm, Paths.Trace(2.0μm))
straight!(p, 10μm, Paths.Taper())
straight!(p, 10μm, Paths.Trace(4.0μm))
The taper is automatically chosen to be a Paths.Trace
, with appropriate initial (2.0 μm
) and final (4.0 μm
) widths. The next segment shows that we can even automatically taper between the current Paths.Trace
and a hard-coded taper (of concrete type Paths.TaperTrace
), matching to the dimensions at the beginning of the latter taper.
straight!(p, 10μm, Paths.Taper())
straight!(p, 10μm, Paths.TaperTrace(2.0μm, 1.0μm))
As a final example, Paths.Taper
can also be used in turn!
segments, and as a way to automatically transition from a Paths.Taper
to a Paths.CPW
, or vice-versa:
turn!(p, -π / 2, 10μm, Paths.Taper())
straight!(p, 10μm, Paths.Trace(2.0μm))
straight!(p, 10μm, Paths.Taper())
straight!(p, 10μm, Paths.CPW(2.0μm, 1.0μm))
c = Cell("tapers", nm)
render!(c, p, GDSMeta(0))
Corners
Sharp turns in a path can be accomplished with Paths.corner!
. Sharp turns pose a challenge to the path abstraction in that they have zero length, and when rendered effectively take up some length of the neighboring segments. Originally, the segment lengths were tweaked at render time to achieve the intended output. As other code began taking advantage of the path abstractions, the limitations of this approach became apparent.
Currently, corners are implemented such that the preceding Paths.Node
is split using Paths.split
near the corner when corner!
is used, and a short resulting section near the corner has the style changed to Paths.SimpleNoRender
. When this is followed by Paths.straight!
to create the next segment, a similar operation is done, to ensure the corner is not twice-rendered. This change was necessary to be able to use Intersect.intersect!
on paths with corners.
Attachments
attach!
is one of the most useful functions defined in this package.
When you call attach!
, you are defining a coordinate system local to somewhere along the target Path
, saying that a StructureReference
should be placed at the origin of that coordinate system (or slightly away from it if you want the cell to be one one side of the path or the other). The local coordinate system will rotate as the path changes orientations. The origin of the StructureReference
corresponds how the referenced cell should be displaced with respect to the origin of the local coordinate system. This differs from the usual meaning of the origin of a StructureReference
, which is how the referenced cell should be displaced with respect to the origin of a containing Cell
.
The same StructureReference
can be attached to multiple points along multiple paths. If the reference is modified (e.g. rotation, origin, magnification) before rendering to a Cell
, the changes should be reflected at all attachment points. The attachment of the cell reference is not a perfect abstraction: a CellReference
must ultimately live inside a Cell
, but an unrendered Path
does not necessarily live inside any cell. If the path is modified further before rendering, the attachment points will follow the path modifications, moving the origins of the local coordinate systems. The origin fields of the cell references do not change as the path is modified.
Attachments are implemented by introducing a Paths.DecoratedStyle
, which is kind of a meta-Style
: it remembers where to attach StructureReference
, but how the path itself is actually drawn is deferred to a different Style
object that it retains a reference to. One can repeat a DecoratedStyle
with one attachment to achieve a periodic placement of StructureReference
(like an ArrayReference
, but along the path). Or, one long segment with a DecoratedStyle
could have several attachments to achieve a similar effect.
When a Path
is rendered, it is turned into Polygons
living in some Cell
. The attachments remain CellReferences
, now living inside of a Cell
and not tied to an abstract path. The notion of local coordinate systems along the path no longer makes sense because the abstract path has been made concrete, and the polygons are living in the coordinate system of the containing cell. Each attachment to the former path now must have its origin referenced to the origin of the containing cell, not to local path coordinate systems. Additionally, the references may need to rotate according to how the path was locally oriented. As a result, even if the same CellReference
was attached multiple times to a path, now we need distinct CellReference
objects for each attachment, as well as for each time a corresponding DecoratedStyle
is rendered.
Suppose we want the ability to transform between coordinate systems, especially between the coordinate system of a referenced cell and the coordinate system of a parent cell. At first glance it would seem like we could simply define a transform function, taking the parent cell and the cell reference we are interested in. But how would we actually identify the particular cell reference we want? Looking in the tree of references for an attached CellReference
will not work: distinct CellReferences
needed to be made after the path was rendered, and so the particular CellReference
object initially attached is not actually in the Cell
containing the rendered path.
To overcome this problem, we make searching for the appropriate CellReference
easier. Suppose a path with attachments has been rendered to a Cell
, which is bound to symbol aaa
. A CellReference
referring to a cell named "bbb" was attached twice. To recall the second attachment: aaa["bbb",2]
(the index defaults to 1 if unspecified). We can go deeper if we want to refer to references inside that attachment: aaa["bbb",2]["ccc"]
. In this manner, it is easy to find the right CellReference
to use with transformation(::DeviceLayout.GeometryStructure, ::StructureReference)
.
Intersections
How to do the right thing when paths intersect is often tedious. Intersect.intersect!
provides a useful function to modify existing paths automatically to account for intersections according to intersection styles (Intersect.IntersectStyle
). Since this is done prior to rendering, further modification can be done easily. Both self-intersections and pairwise intersections can be handled for any reasonable number of paths.
For now, one intersection style is implemented, but the heavy-lifting to add more has been done already. Here's an example (consult API docs below for further information):
pa1 = Path(μm)
turn!(pa1, -360°, 100μm, Paths.CPW(10μm, 6μm))
pa2 = Path(Point(0, 100)μm, α0=-90°)
straight!(pa2, 400μm, Paths.CPW(10μm, 6μm))
turn!(pa2, 270°, 200μm)
straight!(pa2, 400μm)
intersect!(
Intersect.AirBridge(
scaffold_meta=GDSMeta(3, 0),
air_bridge_meta=GDSMeta(4, 0),
crossing_gap=2μm,
foot_gap=2μm,
foot_length=2μm,
extent_gap=2μm,
scaffold_gap=2μm
),
pa1,
pa2
)
c = Cell("test", nm)
render!(c, pa1, GDSMeta(0))
render!(c, pa2, GDSMeta(1))
save(
"intersect_circle.svg",
flatten(c);
layercolors=merge(DeviceLayout.Graphics.layercolors, Dict(1 => (0, 0, 0, 1)))
);
Here's another example:
pa = Path(μm, α0=90°)
straight!(pa, 130μm, Paths.Trace(2μm))
corner!(pa, 90°, Paths.SimpleTraceCorner())
let L = 5μm
for i = 1:50
straight!(pa, L)
corner!(pa, 90°, Paths.SimpleTraceCorner())
L += 5μm
end
end
straight!(pa, 5μm)
intersect!(
Intersect.AirBridge(
scaffold_meta=GDSMeta(3, 0),
air_bridge_meta=GDSMeta(4, 0),
crossing_gap=2μm,
foot_gap=2μm,
foot_length=2μm,
extent_gap=2μm,
scaffold_gap=2μm
),
pa
)
c = Cell("test", nm)
render!(c, pa, GDSMeta(1))
save(
"intersect_spiral.svg",
flatten(c);
layercolors=merge(DeviceLayout.Graphics.layercolors, Dict(1 => (0, 0, 0, 1)))
);
Path API
Path construction
DeviceLayout.Paths.Path
— Typemutable struct Path{T<:Coordinate} <: GeometryStructure{T}
Type for abstracting an arbitrary styled path in the plane. Iterating returns Paths.Node
objects.
Convenience constructors for Path{T}
object:
Path{T}(p0::Point=zero(Point{T}), α0::typeof(1.0°)=0.0°, metadata::Meta=UNDEF_META)
Path{T}(name::String, p0::Point=zero(Point{T}), α0::Float64=0.0, metadata::Meta=UNDEF_META)
Path(p0::Point=zero(Point{typeof(1.0UPREFERRED)}); α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Path(p0x::Coordinate, p0y::Coordinate; α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Path(u::CoordinateUnits; α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Path(v::Vector{Node{T}}; name=uniquename("path"), metadata=UNDEF_META) where {T}
Path interrogation
DeviceLayout.Paths.direction
— Functiondirection(s, t)
Return the angle at which some function t->Point(x(t),y(t))
is pointing.
DeviceLayout.Paths.pathlength
— Functionpathlength(p::Path)
pathlength(array::AbstractArray{Node{T}}) where {T}
pathlength(array::AbstractArray{T}) where {T<:Segment}
pathlength(node::Node)
Physical length of a path. Note that length
will return the number of segments in a path, not the physical length of the path.
DeviceLayout.Paths.p0
— Functionp0(s::Segment{T}) where {T}
Return the first point in a segment (calculated).
p0(p::Path)
First point of a path, returns p.p0
.
p0(r::Route)
First point of a route, returns r.p0
.
DeviceLayout.Paths.α0
— Functionα0(s::Segment)
Return the first angle in a segment (calculated).
α0(p::Path)
First angle of a path, returns p.α0
.
α0(r::Route)
First angle of a route, returns r.α0
.
DeviceLayout.Paths.p1
— Functionp1(s::Segment{T}) where {T}
Return the last point in a segment (calculated).
p1(p::Path)
Last point of a path.
p1(r::Route)
Last point of a route, returns r.p1
.
DeviceLayout.Paths.α1
— Functionα1(s::Segment)
Return the last angle in a segment (calculated).
α1(p::Path)
Last angle of a path.
α1(r::Route)
Last angle of a route, returns r.α1
.
DeviceLayout.Paths.style0
— Functionstyle0(p::Path)
Style of the first segment of a path.
DeviceLayout.Paths.style1
— Functionstyle1(p::Path)
Undecorated style of the last user-provided (non-virtual) segment of a path.
Throws an error if the path is empty.
DeviceLayout.Paths.discretestyle1
— Functiondiscretestyle1(p::Path)
Return the last-used discrete style in the path.
DeviceLayout.Paths.contstyle1
— Functioncontstyle1(p::Path)
Return the undecorated last user-provided (non-virtual) continuous style in the path.
Throws an error if the path is empty.
Path manipulation
DeviceLayout.Paths.setp0!
— Functionsetp0!(s::Straight, p::Point)
Set the p0 of a straight segment.
setp0!(s::Turn, p::Point)
Set the p0 of a turn.
setp0!(b::BSpline, p::Point)
Translate the interpolated segment so its initial point is p
.
DeviceLayout.Paths.setα0!
— Functionsetα0!(s::Straight, α0′)
Set the angle of a straight segment.
setα0!(s::Turn, α0′)
Set the starting angle of a turn.
setα0!(b::BSpline, α0′)
Set the starting angle of an interpolated segment.
Base.append!
— Methodappend!(p::Path, p′::Path; reconcile=true)
Given paths p
and p′
, path p′
is appended to path p
. The p0 and initial angle of the first segment from path p′
is modified to match the last point and last angle of path p
.
DeviceLayout.Paths.attach!
— Methodattach!(p::Path, c::GeometryReference, t::Coordinate;
i::Integer=length(p), location::Integer=0)
attach!(p::Path, c::GeometryReference, t;
i::Integer=length(p), location=zeros(Int, length(t)))
Attach c
along a path. The second method permits ranges or arrays of t
and location
to be specified (if the lengths do not match, location
is cycled).
By default, the attachment(s) occur at t ∈ [zero(pathlength(s)),pathlength(s)]
along the most recent path segment s
, but a different path segment index can be specified using i
. The reference is oriented with zero rotation if the path is pointing at 0°, otherwise it is rotated with the path.
The origin of the cell reference tells the method where to place the cell with respect to a coordinate system that rotates with the path. Suppose the path is a straight line with angle 0°. Then an origin of Point(0.,10.)
will put the cell at 10 above the path, or 10 to the left of the path if it turns left by 90°.
The location
option is for convenience. If location == 0
, nothing special happens. If location == -1
, then the point of attachment for the reference is on the leftmost edge of the waveguide (the rendered polygons; the path itself has no width). Likewise if location == 1
, the point of attachment is on the rightmost edge. This option does not automatically rotate the cell reference, apart from what is already done as described in the first paragraph. You can think of this option as setting a special origin for the coordinate system that rotates with the path. For instance, an origin for the cell reference of Point(0.,10.)
together with location == -1
will put the cell at 10 above the edge of a rendered (finite width) path with angle 0°.
DeviceLayout.Paths.bspline!
— Functionbspline!(p::Path{T}, nextpoints, α_end, sty::Style=contstyle1(p), endpoints_speed=2500μm)
Add a BSpline interpolation from the current endpoint of p
through nextpoints
.
The interpolation reaches nextpoints[end]
making the angle α_end
with the positive x-axis. The endpoints_speed
is "how fast" the interpolation leaves and enters its endpoints. Higher speed means that the start and end angles are approximately α1(p) and α_end over a longer distance.
DeviceLayout.Paths.corner!
— Functioncorner!(p::Path, α, sty::Style=discretestyle1(p))
Append a sharp turn or "corner" to path p
with angle α
.
The style chosen for this corner, if not specified, is the last DiscreteStyle
used in the path.
Base.intersect!
— Functionintersect!(sty::IntersectStyle, paths::Path...;
intersections=prepared_intersections(paths...))
Automatically modify paths to handle cases where they intersect.
Paths later in the argument list cross over paths earlier in the argument list. For self-intersection (path with itself), segments later in a path will cross over segments earlier in the same path (perhaps later this will be configurable by an option).
DeviceLayout.Paths.launch!
— Functionlaunch!(p::Path; kwargs...)
Add a coplanar-waveguide "launcher" structure to p
.
If p
is empty, start the path with a launcher; otherwise, terminate with a launcher.
This method exists mainly for use in demonstrations. The launcher design is not optimized for microwave properties.
Keywords:
extround = 5.0μm
: Rounding of the "back" of the pad and external ground plane "behind" the launchertrace0 = 300.0μm
: Trace width of the padtrace1 = 10.0μm
: Trace width of the launched CPWgap0 = 150.0μm
: Gap width of the padgap1 = 6.0μm
: Gap width of the final CPWflatlen = 250.0μm
: Length of the padtaperlen = 250.0μm
: Length of the taper between pad and launched CPW
DeviceLayout.Paths.meander!
— Functionmeander!(p::Path, len, straightlen, r, α)
Alternate between going straight with length straightlen
and turning with radius r
and angle α
. Each turn goes the opposite direction of the previous. The total length is len
. Useful for making resonators.
The straight and turn segments are combined into a CompoundSegment
and appended to the path p
.
meander!(p::Path, endpoint::Point, len, nseg::Int, r, sty::Paths.Style = style1(p); offset = 0)
Another meander method, this one extends Path p
from its current end-point to meet endpoint
, such that the resulting final total path length will be len
.
nseg
: the number of U-turns in the meander will be 2*nseg
DeviceLayout.Paths.overlay!
— Functionoverlay!(path::Path, oversty::Style, metadata::DeviceLayout.Meta; i::Int=length(path))
Apply the style oversty
in layer metadata
on top of the segment at path[i]
.
By default, the overlay is applied to the most recent segment.
Overlays generally count as "decorations". For example, they appear in refs(path)
and not elements(path)
. They are removed by undecorated(sty)
, and they are ignored when choosing the default style for continuing a Path
with methods like straight!
.
Overlay styles should not be generic Paths.Taper
s, since they can't see neighboring styles to resolve the taper style.
You can use overlay!
after attach!
, in which case the overlay is applied to the style underlying the DecoratedStyle
that holds the attachments.
DeviceLayout.Paths.reconcile!
— Functionreconcile!(path::Path, endpoint::Point, end_direction, rule::RouteRule, waypoints, waydirs;
initialize_waydirs = false)
Ensure that path
can be routed to endpoint
at end_direction
using rule, waypoints, waydirs
, or throw an error.
Does nothing for a generic RouteRule
. Subtypes of RouteRule
may implement specialized methods to do their own validation when route!
is called.
May insert inferred constraints to waypoints
and waydirs
to allow the path to be drawn leg-by-leg. For example, reconcile!
with rule::StraightAnd90
, no waypoints, and α1(path) == end_direction
will insert a waypoint halfway between p1(path)
and endpoint
, allowing two successive StraightAnd90
legs with opposite bends.
reconcile!(p::Path, n::Integer=1)
Reconcile all inconsistencies in a path starting from index n
. Used internally whenever segments are inserted into the path, but can be safely used by the user as well.
DeviceLayout.Paths.simplify
— Functionsimplify(p::Path, inds::UnitRange=firstindex(p):lastindex(p))
At inds
, segments of a path are turned into a CompoundSegment
and styles of a path are turned into a CompoundStyle
. The method returns a Paths.Node
, (segment, style)
.
- Indexing the path becomes more sane when you can combine several path segments into one logical element. A launcher would have several indices in a path unless you could simplify it.
- You don't need to think hard about boundaries between straights and turns when you want a continuous styling of a very long path.
DeviceLayout.Paths.simplify!
— Functionsimplify!(p::Path, inds::UnitRange=firstindex(p):lastindex(p))
In-place version of simplify
.
DeviceLayout.Paths.straight!
— Functionstraight!(p::Path, l::Coordinate, sty::Style=contstyle1(p))
Extend a path p
straight by length l
in the current direction. By default, we take the last continuous style in the path.
DeviceLayout.Paths.terminate!
— Functionterminate!(pa::Path{T}; gap=Paths.terminationlength(pa), rounding=zero(T), initial=false) where {T}
End a Paths.Path
with a termination.
If the preceding style is a CPW, this is a "short termination" if iszero(gap)
and is an "open termination" with a gap of gap
otherwise, defaulting to the gap of the preceding CPW.
Rounding of corners may be specified with radius given by rounding
. Rounding keeps the trace length constant by removing some length from the preceding segment and adding a rounded section of equivalent maximum length. (This produces a small quirk if you terminate a curved segment with rounding—the termination is "straight" starting at rounding
away from the end of the curve, meaning that the α0(pa)
)
If the preceding style is a trace, the termination only rounds the corners at the end of the segment or does nothing if iszero(rounding)
.
If initial
, the termination is applied at the beginning of the Path
.
DeviceLayout.Paths.turn!
— Functionturn!(p::Path, α, r::Coordinate, sty::Style=contstyle1(p))
Turn a path p
by angle α
with a turning radius r
in the current direction. Positive angle turns left. By default, we take the last continuous style in the path.
turn!(p::Path, str::String, r::Coordinate, sty::Style=contstyle1(p))
Turn a path p
with direction coded by string str
:
- "l": turn by 90° (left)
- "r": turn by -90° (right)
- "lrlrllrrll": do those turns in that order
By default, we take the last continuous style in the path.
Path intersection styles
DeviceLayout.Intersect.IntersectStyle
— TypeIntersectStyle{N}
Abstract type specifying "2-body interactions" for path intersection.
DeviceLayout.Intersect.AirBridge
— TypeAirBridge(; crossing_gap, foot_gap, foot_length, extent_gap, scaffold_gap,
scaffold_meta=SemanticMeta(:scaffold), air_bridge_meta=SemanticMeta(:air_bridge),
name="airbridge", unit=[nm or NoUnits])
Style for automatically leaping one path over another with scaffolded air bridges.
Parameters ("lengths" are along path direction, "extents" are transverse from the center)
name
: Prefix for uniqueCoordinateSystem
namescaffold_meta
: Scaffold layer metadataair_bridge_meta
: Air bridge layer metadatacrossing_gap
: Extra length beyond extent of path being crossed (on one side)foot_gap
: Extra length beyond original path termination before bridge foot startsfoot_length
: Length of bridge footextent_gap
: Gap between edge of bridge trace and edge of original path tracescaffold_gap
: Gap between edge of original trace and edge of scaffoldrounding
: Rounding radius for scaffold and air bridge rectanglesunit
: Coordinate system unit
Node API
Node construction
DeviceLayout.Paths.Node
— TypeNode(a::Segment{T}, b::Style) where {T}
Create a node with segment a
and style b
.
Node methods
DeviceLayout.Paths.previous
— Functionprevious(x::Node)
Return the node before x
in a doubly linked list.
DeviceLayout.Paths.next
— Functionnext(x::Node)
Return the node after x
in a doubly linked list.
DeviceLayout.Paths.segment
— Functionsegment(x::Node)
Return the segment associated with node x
.
Base.split
— Methodsplit(n::Node, x::Coordinate)
split(n::Node, x::AbstractVector{<:Coordinate}; issorted=false)
Splits a path node at position(s) x
along the segment, returning a path. If issorted
, don't sort x
first (otherwise required for this to work).
A useful idiom, splitting and splicing back into a path: splice!(path, i, split(path[i], x))
DeviceLayout.style
— Functionstyle(x::Node)
Return the style associated with node x
.
style(styled_ent::StyledEntity)
Return the GeometryEntityStyle
of styled_ent
.
DeviceLayout.Paths.setsegment!
— Functionsetsegment!(n::Node, s::Segment)
Set the segment associated with node n
to s
. If reconcile
, then modify fields as appropriate for internal consistency (possibly including other linked nodes).
DeviceLayout.Paths.setstyle!
— Functionsetstyle!(n::Node, s::Style; reconcile=true)
Set the style associated with node n
to s
. If reconcile
, then modify fields as appropriate for internal consistency.
Segment API
Abstract types
DeviceLayout.Paths.Segment
— Typeabstract type Segment{T<:Coordinate} end
Path segment in the plane. All Segment objects should have the implement the following methods:
pathlength
p0
α0
setp0!
setα0!
α1
Concrete types
DeviceLayout.Paths.Straight
— Typemutable struct Straight{T} <: ContinuousSegment{T}
A straight line segment is parameterized by its length. It begins at a point p0
with initial angle α0
.
DeviceLayout.Paths.Turn
— Typemutable struct Turn{T} <: ContinuousSegment{T}
A circular turn is parameterized by the turn angle α
and turning radius r
. It begins at a point p0
with initial angle α0
.
DeviceLayout.Paths.Corner
— Typemutable struct Corner{T} <: DiscreteSegment{T}
A corner, or sudden kink in a path. The only parameter is the angle α
of the corner. The corner begins at a point p0
with initial angle α0
. It will also end at p0
, since the corner has zero path length. However, during rendering, neighboring segments will be tweaked slightly so that the rendered path is properly centered about the path function (the rendered corner has a finite width).
DeviceLayout.Paths.CompoundSegment
— Typestruct CompoundSegment{T} <: ContinuousSegment{T}
Consider an array of segments as one contiguous segment. Useful e.g. for applying styles, uninterrupted over segment changes. The array of segments given to the constructor is copied and retained by the compound segment.
Note that Corner
s introduce a discontinuity in the derivative of the path function, and are not allowed in a CompoundSegment
.
DeviceLayout.Paths.BSpline
— Typemutable struct BSpline{T} <: ContinuousSegment{T}
Interpolate between points p
with start and end tangents t0
, t1
.
Computes the interpolated coordinate r(t)
as a function of a dimensionless parameter t
, using b-spline interpolation knots
spaced uniformly in t
. That is, r(0) == p[1]
and r(1) == p[end]
, and generally r((i-1)*tinc) == p[i]
where tinc
is the knot value spacing 1/(length(p)-1)
.
A BSpline
instance itself can be called as a parametric function of a length that ranges from zero to the total path length.
Style API
Style construction
DeviceLayout.Paths.Trace
— TypeTrace(width)
Trace(width::Coordinate)
Trace(width_start::Coordinate, width_end::Coordinate)
Constructor for Trace styles. Automatically chooses SimpleTrace
, GeneralTrace
, and TaperTrace
as appropriate.
DeviceLayout.Paths.CPW
— TypeCPW(trace::Coordinate, gap::Coordinate)
CPW(trace, gap::Coordinate)
CPW(trace::Coordinate, gap)
CPW(trace, gap)
CPW(trace_start::Coordinate, gap_start::Coordinate, trace_end::Coordinate, gap_end::Coordinate)
Constructors for CPW styles. Automatically chooses between SimpleCPW
, GeneralCPW
, or TaperCPW
styles as appropriate.
DeviceLayout.Paths.Taper
— TypeTaper()
Constructor for generic Taper style. Will automatically create a linearly tapered region between an initial CPW
or Trace
and an end CPW
or Trace
of different dimensions.
DeviceLayout.Paths.Strands
— TypeStrands(offset::Coordinate, width::Coordinate, spacing::Coordinate, num::Int)
Strands(offset, width::Coordinate, spacing::Coordinate, num::Int)
Strands(offset::Coordinate, width, spacing::Coordinate, num::Int)
Strands(offset::Coordinate, width::Coordinate, spacing, num::Int)
Strands(offset::Coordinate, width, spacing, num::Int)
Strands(offset, width::Coordinate, spacing, num::Int)
Strands(offset, width, spacing::Coordinate, num::Int)
Strands(offset, width, spacing, num::Int)
example for num = 2
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
<-><---><-><-----------|-----------><-><---><->
w s w offset w s w
Constructors for Strands styles. Automatically chooses between SimpleStrands
or GeneralStrands
styles as appropriate.
DeviceLayout.Paths.NoRender
— Typestruct NoRender <: Style
Style for suppressing rendering. When asked, it will claim to have zero width. Converts to a continuous or discrete style as needed by straight!
, turn!
, corner!
, etc.
Style manipulation
DeviceLayout.Paths.pin
— Functionpin(s::ContinuousStyle; start=nothing, stop=nothing)
Imagine having a styled segment of length L
split into two, the first segment having length l
and the second having length L-l
. In all but the simplest styles, the styles need to be modified in order to maintain the rendered appearances. A style appropriate for the segment of length l
(L-l
) is given by pin(s; stop=l)
(pin(s; start=l)
).
DeviceLayout.Paths.translate
— Functiontranslate(s::ContinuousStyle, x)
Creates a style s′
such that all properties f(s′, t) == f(s, t+x)
. Basically, advance the style forward by path length x
.
DeviceLayout.Paths.undecorated
— Functionundecorated(s::DecoratedStyle)
undecorated(s::Style)
Return the underlying, undecorated style if decorated; otherwise just return the style.
Abstract types
DeviceLayout.Paths.Style
— Typeabstract type Style end
How to render a given path segment.
DeviceLayout.Paths.ContinuousStyle
— Typeabstract type ContinuousStyle{CanStretch} <: Style end
Any style that applies to segments which have non-zero path length. For most styles, CanStretch == false
. An example of an exception is a linear taper, e.g. Paths.TaperTrace
, where you fix the starting and ending trace widths and let the segment length dictate the abruptness of the transition (hence, stretching the style). Concrete types inheriting from ContinuousStyle{true}
should have a length field as the last field of their structure.
DeviceLayout.Paths.DiscreteStyle
— Typeabstract type DiscreteStyle <: Style end
Any style that applies to segments which have zero path length.
Concrete types
DeviceLayout.Paths.SimpleNoRender
— Typestruct SimpleNoRender{T} <: ContinuousStyle{false}
SimpleNoRender(width::T; virtual=false)
A style that inhibits path rendering, but pretends to have a finite width for Paths.attach!
.
May be "virtual", in which case it is ignored when looking up the last style of a Path with laststyle
or contstyle1
, or when extending a Path with (for example) straight!
.
DeviceLayout.Paths.SimpleTrace
— Typestruct SimpleTrace{T <: Coordinate} <: Trace{false}
width::T
end
A single trace with fixed width as a function of path length.
DeviceLayout.Paths.GeneralTrace
— Typestruct GeneralTrace{T} <: Trace{false}
width::T
end
A single trace with variable width as a function of path length. width
is callable.
DeviceLayout.Paths.SimpleCPW
— Typestruct SimpleCPW{T <: Coordinate} <: CPW{false}
trace::T
gap::T
end
A CPW with fixed trace and gap as a function of path length.
DeviceLayout.Paths.GeneralCPW
— Typestruct GeneralCPW{S, T} <: CPW{false}
trace::S
gap::T
end
A CPW with variable trace and gap as a function of path length. trace
and gap
are callable.
DeviceLayout.Paths.TaperTrace
— Typestruct TaperTrace{T<:Coordinate} <: Trace{true}
width_start::T
width_end::T
length::T
end
A single trace with a linearly tapered width as a function of path length.
DeviceLayout.Paths.TaperCPW
— Typestruct TaperCPW{T<:Coordinate} <: CPW{true}
trace_start::T
gap_start::T
trace_end::T
gap_end::T
length::T
end
A CPW with a linearly tapered trace and gap as a function of path length.
DeviceLayout.Paths.SimpleStrands
— Typestruct SimpleStrands{T<:Coordinate} <: Strands{false}
offset::T
width::T
spacing::T
num::Int
end
example for num = 2
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
<-><---><-><-----------|-----------><-><---><->
w s w offset w s w
Strands with fixed center offset, width, and spacing as a function of path length.
DeviceLayout.Paths.GeneralStrands
— Typestruct GeneralStrands{S,T,U} <: Strands{false}
offset::S
width::T
spacing::U
num::Int
end
example for num = 2
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
||| ||| ||| |||
<-><---><-><-----------|-----------><-><---><->
w s w offset w s w
Strands with variable center offset, width, and spacing as a function of path length. offset
, width
, and spacing
are callable.
DeviceLayout.Paths.CompoundStyle
— Typestruct CompoundStyle{T<:FloatCoordinate} <: ContinuousStyle{false}
styles::Vector{Style}
grid::Vector{T}
end
Combines styles together, typically for use with a CompoundSegment
.
styles
: Array of styles making up the object. This is deep-copied by the outer constructor.grid
: An array oft
values needed for rendering the parameteric path.
DeviceLayout.Paths.DecoratedStyle
— Typemutable struct DecoratedStyle{T<:FloatCoordinate} <: ContinuousStyle{false}
s::Style
ts::Vector{Float64}
dirs::Vector{Int}
refs::Vector{GeometryReference}
end
Style with decorations, like structures periodically repeated along the path, etc.