[docs / examples] Update docs and examples
This commit is contained in:
parent
8100d8095a
commit
fd2698c503
7 changed files with 132 additions and 107 deletions
|
|
@ -1,6 +1,12 @@
|
|||
masque Tutorial
|
||||
===============
|
||||
|
||||
These examples are meant to be read roughly in order.
|
||||
|
||||
- Start with `basic_shapes.py` for the core `Pattern` / GDS concepts.
|
||||
- Then read `devices.py` and `library.py` for hierarchical composition and libraries.
|
||||
- Read the `pather*` tutorials separately when you want routing helpers.
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
|
|
@ -8,11 +14,13 @@ Contents
|
|||
* Draw basic geometry
|
||||
* Export to GDS
|
||||
- [devices](devices.py)
|
||||
* Build hierarchical photonic-crystal example devices
|
||||
* Reference other patterns
|
||||
* Add ports to a pattern
|
||||
* Snap ports together to build a circuit
|
||||
* Use `Builder` to snap ports together into a circuit
|
||||
* Check for dangling references
|
||||
- [library](library.py)
|
||||
* Continue from `devices.py` using a lazy library
|
||||
* Create a `LazyLibrary`, which loads / generates patterns only when they are first used
|
||||
* Explore alternate ways of specifying a pattern for `.plug()` and `.place()`
|
||||
* Design a pattern which is meant to plug into an existing pattern (via `.interface()`)
|
||||
|
|
@ -28,7 +36,8 @@ Contents
|
|||
* Advanced port manipulation and connections
|
||||
|
||||
|
||||
Additionaly, [pcgen](pcgen.py) is a utility module for generating photonic crystal lattices.
|
||||
Additionally, [pcgen](pcgen.py) is a utility module used by `devices.py` for generating
|
||||
photonic-crystal lattices; it is support code rather than a step-by-step tutorial.
|
||||
|
||||
|
||||
Running
|
||||
|
|
@ -40,3 +49,6 @@ cd examples/tutorial
|
|||
python3 basic_shapes.py
|
||||
klayout -e basic_shapes.gds
|
||||
```
|
||||
|
||||
Some tutorials depend on outputs from earlier ones. In particular, `library.py`
|
||||
expects `circuit.gds`, which is generated by `devices.py`.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
"""
|
||||
Tutorial: building hierarchical devices with `Pattern`, `Port`, and `Builder`.
|
||||
|
||||
This file uses photonic-crystal components as the concrete example, so some of
|
||||
the geometry-generation code is domain-specific. The tutorial value is in the
|
||||
Masque patterns around it: creating reusable cells, annotating ports, composing
|
||||
hierarchy with references, and snapping ports together to build a larger circuit.
|
||||
"""
|
||||
from collections.abc import Sequence, Mapping
|
||||
|
||||
import numpy
|
||||
|
|
@ -64,9 +72,9 @@ def perturbed_l3(
|
|||
Provided sequence should have same length as `shifts_a`.
|
||||
xy_size: `(x, y)` number of mirror periods in each direction; total size is
|
||||
`2 * n + 1` holes in each direction. Default (10, 10).
|
||||
perturbed_radius: radius of holes perturbed to form an upwards-driected beam
|
||||
perturbed_radius: radius of holes perturbed to form an upwards-directed beam
|
||||
(multiplicative factor). Default 1.1.
|
||||
trench width: Width of the undercut trenches. Default 1200.
|
||||
trench_width: Width of the undercut trenches. Default 1200.
|
||||
|
||||
Returns:
|
||||
`Pattern` object representing the L3 design.
|
||||
|
|
@ -79,14 +87,15 @@ def perturbed_l3(
|
|||
shifts_a=shifts_a,
|
||||
shifts_r=shifts_r)
|
||||
|
||||
# Build L3 cavity, using references to the provided hole pattern
|
||||
# Build the cavity by instancing the supplied `hole` pattern many times.
|
||||
# Using references keeps the pattern compact even though it contains many holes.
|
||||
pat = Pattern()
|
||||
pat.refs[hole] += [
|
||||
Ref(scale=r, offset=(lattice_constant * x,
|
||||
lattice_constant * y))
|
||||
for x, y, r in xyr]
|
||||
|
||||
# Add rectangular undercut aids
|
||||
# Add rectangular undercut aids based on the referenced hole extents.
|
||||
min_xy, max_xy = pat.get_bounds_nonempty(hole_lib)
|
||||
trench_dx = max_xy[0] - min_xy[0]
|
||||
|
||||
|
|
@ -95,7 +104,7 @@ def perturbed_l3(
|
|||
Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width),
|
||||
]
|
||||
|
||||
# Ports are at outer extents of the device (with y=0)
|
||||
# Define the interface in Masque terms: two ports at the left/right extents.
|
||||
extent = lattice_constant * xy_size[0]
|
||||
pat.ports = dict(
|
||||
input=Port((-extent, 0), rotation=0, ptype='pcwg'),
|
||||
|
|
@ -125,17 +134,17 @@ def waveguide(
|
|||
Returns:
|
||||
`Pattern` object representing the waveguide.
|
||||
"""
|
||||
# Generate hole locations
|
||||
# Generate the normalized lattice locations for the line defect.
|
||||
xy = pcgen.waveguide(length=length, num_mirror=mirror_periods)
|
||||
|
||||
# Build the pattern
|
||||
# Build the pattern by placing repeated references to the same hole cell.
|
||||
pat = Pattern()
|
||||
pat.refs[hole] += [
|
||||
Ref(offset=(lattice_constant * x,
|
||||
lattice_constant * y))
|
||||
for x, y in xy]
|
||||
|
||||
# Ports are at outer edges, with y=0
|
||||
# Publish the device interface as two ports at the outer edges.
|
||||
extent = lattice_constant * length / 2
|
||||
pat.ports = dict(
|
||||
left=Port((-extent, 0), rotation=0, ptype='pcwg'),
|
||||
|
|
@ -164,17 +173,17 @@ def bend(
|
|||
`Pattern` object representing the waveguide bend.
|
||||
Ports are named 'left' (input) and 'right' (output).
|
||||
"""
|
||||
# Generate hole locations
|
||||
# Generate the normalized lattice locations for the bend.
|
||||
xy = pcgen.wgbend(num_mirror=mirror_periods)
|
||||
|
||||
# Build the pattern
|
||||
pat= Pattern()
|
||||
# Build the pattern by instancing the shared hole cell.
|
||||
pat = Pattern()
|
||||
pat.refs[hole] += [
|
||||
Ref(offset=(lattice_constant * x,
|
||||
lattice_constant * y))
|
||||
for x, y in xy]
|
||||
|
||||
# Figure out port locations.
|
||||
# Publish the bend interface as two ports.
|
||||
extent = lattice_constant * mirror_periods
|
||||
pat.ports = dict(
|
||||
left=Port((-extent, 0), rotation=0, ptype='pcwg'),
|
||||
|
|
@ -203,17 +212,17 @@ def y_splitter(
|
|||
`Pattern` object representing the y-splitter.
|
||||
Ports are named 'in', 'top', and 'bottom'.
|
||||
"""
|
||||
# Generate hole locations
|
||||
# Generate the normalized lattice locations for the splitter.
|
||||
xy = pcgen.y_splitter(num_mirror=mirror_periods)
|
||||
|
||||
# Build pattern
|
||||
# Build the pattern by instancing the shared hole cell.
|
||||
pat = Pattern()
|
||||
pat.refs[hole] += [
|
||||
Ref(offset=(lattice_constant * x,
|
||||
lattice_constant * y))
|
||||
for x, y in xy]
|
||||
|
||||
# Determine port locations
|
||||
# Publish the splitter interface as one input and two outputs.
|
||||
extent = lattice_constant * mirror_periods
|
||||
pat.ports = {
|
||||
'in': Port((-extent, 0), rotation=0, ptype='pcwg'),
|
||||
|
|
@ -227,13 +236,13 @@ def y_splitter(
|
|||
|
||||
|
||||
def main(interactive: bool = True) -> None:
|
||||
# Generate some basic hole patterns
|
||||
# First make a couple of reusable primitive cells.
|
||||
shape_lib = {
|
||||
'smile': basic_shapes.smile(RADIUS),
|
||||
'hole': basic_shapes.hole(RADIUS),
|
||||
}
|
||||
|
||||
# Build some devices
|
||||
# Then build a small library of higher-level devices from those primitives.
|
||||
a = LATTICE_CONSTANT
|
||||
|
||||
devices = {}
|
||||
|
|
@ -245,22 +254,23 @@ def main(interactive: bool = True) -> None:
|
|||
devices['ysplit'] = y_splitter(lattice_constant=a, hole='hole', mirror_periods=5)
|
||||
devices['l3cav'] = perturbed_l3(lattice_constant=a, hole='smile', hole_lib=shape_lib, xy_size=(4, 10)) # uses smile :)
|
||||
|
||||
# Turn our dict of devices into a Library.
|
||||
# This provides some convenience functions in the future!
|
||||
# Turn the device mapping into a `Library`.
|
||||
# That gives us convenience helpers for hierarchy inspection and abstract views.
|
||||
lib = Library(devices)
|
||||
|
||||
#
|
||||
# Build a circuit
|
||||
#
|
||||
# Create a `Builder`, and add the circuit to our library as "my_circuit".
|
||||
# Create a `Builder`, and register the resulting top cell as "my_circuit".
|
||||
circ = Builder(library=lib, name='my_circuit')
|
||||
|
||||
# Start by placing a waveguide. Call its ports "in" and "signal".
|
||||
# Start by placing a waveguide and renaming its ports to match the circuit-level
|
||||
# names we want to use while assembling the design.
|
||||
circ.place('wg10', offset=(0, 0), port_map={'left': 'in', 'right': 'signal'})
|
||||
|
||||
# Extend the signal path by attaching the "left" port of a waveguide.
|
||||
# Since there is only one other port ("right") on the waveguide we
|
||||
# are attaching (wg10), it automatically inherits the name "signal".
|
||||
# Extend the signal path by attaching another waveguide.
|
||||
# Because `wg10` only has one unattached port left after the plug, Masque can
|
||||
# infer that it should keep the name `signal`.
|
||||
circ.plug('wg10', {'signal': 'left'})
|
||||
|
||||
# We could have done the following instead:
|
||||
|
|
@ -268,8 +278,8 @@ def main(interactive: bool = True) -> None:
|
|||
# lib['my_circuit'] = circ_pat
|
||||
# circ_pat.place(lib.abstract('wg10'), ...)
|
||||
# circ_pat.plug(lib.abstract('wg10'), ...)
|
||||
# but `Builder` lets us omit some of the repetition of `lib.abstract(...)`, and uses similar
|
||||
# syntax to `Pather` and `RenderPather`, which add wire/waveguide routing functionality.
|
||||
# but `Builder` removes some repeated `lib.abstract(...)` boilerplate and keeps
|
||||
# the assembly code focused on port-level intent.
|
||||
|
||||
# Attach a y-splitter to the signal path.
|
||||
# Since the y-splitter has 3 ports total, we can't auto-inherit the
|
||||
|
|
@ -281,13 +291,10 @@ def main(interactive: bool = True) -> None:
|
|||
circ.plug('wg05', {'signal1': 'left'})
|
||||
circ.plug('wg05', {'signal2': 'left'})
|
||||
|
||||
# Add a bend to both ports.
|
||||
# Our bend's ports "left" and "right" refer to the original counterclockwise
|
||||
# orientation. We want the bends to turn in opposite directions, so we attach
|
||||
# the "right" port to "signal1" to bend clockwise, and the "left" port
|
||||
# to "signal2" to bend counterclockwise.
|
||||
# We could also use `mirrored=(True, False)` to mirror one of the devices
|
||||
# and then use same device port on both paths.
|
||||
# Add a bend to both branches.
|
||||
# Our bend primitive is defined with a specific orientation, so choosing which
|
||||
# port to plug determines whether the path turns clockwise or counterclockwise.
|
||||
# We could also mirror one instance instead of using opposite ports.
|
||||
circ.plug('bend0', {'signal1': 'right'})
|
||||
circ.plug('bend0', {'signal2': 'left'})
|
||||
|
||||
|
|
@ -296,29 +303,26 @@ def main(interactive: bool = True) -> None:
|
|||
circ.plug('l3cav', {'signal1': 'input'})
|
||||
circ.plug('wg10', {'signal1': 'left'})
|
||||
|
||||
# "signal2" just gets a single of equivalent length
|
||||
# `signal2` gets a single waveguide of equivalent overall length.
|
||||
circ.plug('wg28', {'signal2': 'left'})
|
||||
|
||||
# Now we bend both waveguides back towards each other
|
||||
# Now bend both branches back towards each other.
|
||||
circ.plug('bend0', {'signal1': 'right'})
|
||||
circ.plug('bend0', {'signal2': 'left'})
|
||||
circ.plug('wg05', {'signal1': 'left'})
|
||||
circ.plug('wg05', {'signal2': 'left'})
|
||||
|
||||
# To join the waveguides, we attach a second y-junction.
|
||||
# We plug "signal1" into the "bot" port, and "signal2" into the "top" port.
|
||||
# The remaining port gets named "signal_out".
|
||||
# This operation would raise an exception if the ports did not line up
|
||||
# correctly (i.e. they required different rotations or translations of the
|
||||
# y-junction device).
|
||||
# To join the branches, attach a second y-junction.
|
||||
# This succeeds only if both chosen ports agree on the same translation and
|
||||
# rotation for the inserted device; otherwise Masque raises an exception.
|
||||
circ.plug('ysplit', {'signal1': 'bot', 'signal2': 'top'}, {'in': 'signal_out'})
|
||||
|
||||
# Finally, add some more waveguide to "signal_out".
|
||||
circ.plug('wg10', {'signal_out': 'left'})
|
||||
|
||||
# We can also add text labels for our circuit's ports.
|
||||
# They will appear at the uppermost hierarchy level, while the individual
|
||||
# device ports will appear further down, in their respective cells.
|
||||
# Bake the top-level port metadata into labels so it survives GDS export.
|
||||
# These labels appear on the circuit cell; individual child devices keep their
|
||||
# own port labels in their own cells.
|
||||
ports_to_data(circ.pattern)
|
||||
|
||||
# Check if we forgot to include any patterns... ooops!
|
||||
|
|
@ -330,12 +334,12 @@ def main(interactive: bool = True) -> None:
|
|||
lib.add(shape_lib)
|
||||
assert not lib.dangling_refs()
|
||||
|
||||
# We can visualize the design. Usually it's easier to just view the GDS.
|
||||
# We can visualize the design directly, though opening the written GDS is often easier.
|
||||
if interactive:
|
||||
print('Visualizing... this step may be slow')
|
||||
circ.pattern.visualize(lib)
|
||||
|
||||
#Write out to GDS, only keeping patterns referenced by our circuit (including itself)
|
||||
# Write out only the subtree reachable from our top cell.
|
||||
subtree = lib.subtree('my_circuit') # don't include wg90, which we don't use
|
||||
check_valid_names(subtree.keys())
|
||||
writefile(subtree, 'circuit.gds', **GDS_OPTS)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
"""
|
||||
Tutorial: using `LazyLibrary` and `Builder.interface()`.
|
||||
|
||||
This example assumes you have already read `devices.py` and generated the
|
||||
`circuit.gds` file it writes. The goal here is not the photonic-crystal geometry
|
||||
itself, but rather how Masque lets you mix lazily loaded GDS content with
|
||||
python-generated devices inside one library.
|
||||
"""
|
||||
from typing import Any
|
||||
from pprint import pformat
|
||||
|
||||
|
|
@ -12,8 +20,9 @@ from basic_shapes import GDS_OPTS
|
|||
|
||||
|
||||
def main() -> None:
|
||||
# Define a `LazyLibrary`, which provides lazy evaluation for generating
|
||||
# patterns and lazy-loading of GDS contents.
|
||||
# A `LazyLibrary` delays work until a pattern is actually needed.
|
||||
# That applies both to GDS cells we load from disk and to python callables
|
||||
# that generate patterns on demand.
|
||||
lib = LazyLibrary()
|
||||
|
||||
#
|
||||
|
|
@ -23,9 +32,9 @@ def main() -> None:
|
|||
# Scan circuit.gds and prepare to lazy-load its contents
|
||||
gds_lib, _properties = load_libraryfile('circuit.gds', postprocess=data_to_ports)
|
||||
|
||||
# Add it into the device library by providing a way to read port info
|
||||
# This maintains the lazy evaluation from above, so no patterns
|
||||
# are actually read yet.
|
||||
# Add those cells into our lazy library.
|
||||
# Nothing is read yet; we are only registering how to fetch and postprocess
|
||||
# each pattern when it is first requested.
|
||||
lib.add(gds_lib)
|
||||
|
||||
print('Patterns loaded from GDS into library:\n' + pformat(list(lib.keys())))
|
||||
|
|
@ -40,8 +49,8 @@ def main() -> None:
|
|||
hole = 'triangle',
|
||||
)
|
||||
|
||||
# Triangle-based variants. These are defined here, but they won't run until they're
|
||||
# retrieved from the library.
|
||||
# Triangle-based variants. These lambdas are only recipes for building the
|
||||
# patterns; they do not execute until someone asks for the cell.
|
||||
lib['tri_wg10'] = lambda: devices.waveguide(length=10, mirror_periods=5, **opts)
|
||||
lib['tri_wg05'] = lambda: devices.waveguide(length=5, mirror_periods=5, **opts)
|
||||
lib['tri_wg28'] = lambda: devices.waveguide(length=28, mirror_periods=5, **opts)
|
||||
|
|
@ -53,22 +62,22 @@ def main() -> None:
|
|||
# Build a mixed waveguide with an L3 cavity in the middle
|
||||
#
|
||||
|
||||
# Immediately start building from an instance of the L3 cavity
|
||||
# Start a new design by copying the ports from an existing library cell.
|
||||
# This gives `circ2` the same external interface as `tri_l3cav`.
|
||||
circ2 = Builder(library=lib, ports='tri_l3cav')
|
||||
|
||||
# First way to get abstracts is `lib.abstract(name)`
|
||||
# We can use this syntax directly with `Pattern.plug()` and `Pattern.place()` as well as through `Builder`.
|
||||
# First way to specify what we are plugging in: request an explicit abstract.
|
||||
# This works with `Pattern` methods directly as well as with `Builder`.
|
||||
circ2.plug(lib.abstract('wg10'), {'input': 'right'})
|
||||
|
||||
# Second way to get abstracts is to use an AbstractView
|
||||
# This also works directly with `Pattern.plug()` / `Pattern.place()`.
|
||||
# Second way: use an `AbstractView`, which behaves like a mapping of names
|
||||
# to abstracts.
|
||||
abstracts = lib.abstract_view()
|
||||
circ2.plug(abstracts['wg10'], {'output': 'left'})
|
||||
|
||||
# Third way to specify an abstract works by automatically getting
|
||||
# it from the library already within the Builder object.
|
||||
# This wouldn't work if we only had a `Pattern` (not a `Builder`).
|
||||
# Just pass the pattern name!
|
||||
# Third way: let `Builder` resolve a pattern name through its own library.
|
||||
# This shorthand is convenient, but it is specific to helpers that already
|
||||
# carry a library reference.
|
||||
circ2.plug('tri_wg10', {'input': 'right'})
|
||||
circ2.plug('tri_wg10', {'output': 'left'})
|
||||
|
||||
|
|
@ -77,13 +86,15 @@ def main() -> None:
|
|||
|
||||
|
||||
#
|
||||
# Build a device that could plug into our mixed_wg_cav and joins the two ports
|
||||
# Build a second device that is explicitly designed to mate with `circ2`.
|
||||
#
|
||||
|
||||
# We'll be designing against an existing device's interface...
|
||||
# `Builder.interface()` makes a new pattern whose ports mirror an existing
|
||||
# design's external interface. That is useful when you want to design an
|
||||
# adapter, continuation, or mating structure.
|
||||
circ3 = Builder.interface(source=circ2)
|
||||
|
||||
# ... that lets us continue from where we left off.
|
||||
# Continue routing outward from those inherited ports.
|
||||
circ3.plug('tri_bend0', {'input': 'right'})
|
||||
circ3.plug('tri_bend0', {'input': 'left'}, mirrored=True) # mirror since no tri y-symmetry
|
||||
circ3.plug('tri_bend0', {'input': 'right'})
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ def main() -> None:
|
|||
# If we wanted to place our via manually, we could add `pather.plug('m1_via', {'GND': 'top'})` here
|
||||
# and achieve the same result without having to define any transitions in M1_tool.
|
||||
# Note that even though we have changed the tool used for GND, the via doesn't get placed until
|
||||
# the next time we draw a path on GND (the pather.mpath() statement below).
|
||||
# the next time we route GND (the `pather.ccw()` call below).
|
||||
pather.retool(M1_tool, keys='GND')
|
||||
|
||||
# Bundle together GND and VCC, and path the bundle forward and counterclockwise.
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ def main() -> None:
|
|||
# and remembers the selected port(s). This allows method chaining.
|
||||
|
||||
# Route VCC: 6um South, then West to x=0.
|
||||
# (Note: since the port points North into the pad, path() moves South by default)
|
||||
# (Note: since the port points North into the pad, trace() moves South by default)
|
||||
(rpather.at('VCC')
|
||||
.path(ccw=False, length=6_000) # Move South, turn West (Clockwise)
|
||||
.path_to(ccw=None, x=0) # Continue West to x=0
|
||||
.trace(False, length=6_000) # Move South, turn West (Clockwise)
|
||||
.trace_to(None, x=0) # Continue West to x=0
|
||||
)
|
||||
|
||||
# Route GND: 5um South, then West to match VCC's x-coordinate.
|
||||
rpather.at('GND').path(ccw=False, length=5_000).path_to(ccw=None, x=rpather['VCC'].x)
|
||||
rpather.at('GND').trace(False, length=5_000).trace_to(None, x=rpather['VCC'].x)
|
||||
|
||||
|
||||
#
|
||||
|
|
@ -49,45 +49,45 @@ def main() -> None:
|
|||
.retool(M1_tool) # this only retools the 'GND' port
|
||||
)
|
||||
|
||||
# We can also pass multiple ports to .at(), and then use .mpath() on them.
|
||||
# We can also pass multiple ports to .at(), and then route them together.
|
||||
# Here we bundle them, turn South, and retool both to M1 (VCC gets an auto-via).
|
||||
(rpather.at(['GND', 'VCC'])
|
||||
.mpath(ccw=True, xmax=-10_000, spacing=5_000) # Move West to -10k, turn South
|
||||
.retool(M1_tool) # Retools both GND and VCC
|
||||
.mpath(ccw=True, emax=50_000, spacing=1_200) # Turn East, moves 50um extension
|
||||
.mpath(ccw=False, emin=1_000, spacing=1_200) # U-turn back South
|
||||
.mpath(ccw=False, emin=2_000, spacing=4_500) # U-turn back West
|
||||
.trace(True, xmax=-10_000, spacing=5_000) # Move West to -10k, turn South
|
||||
.retool(M1_tool) # Retools both GND and VCC
|
||||
.trace(True, emax=50_000, spacing=1_200) # Turn East, moves 50um extension
|
||||
.trace(False, emin=1_000, spacing=1_200) # U-turn back South
|
||||
.trace(False, emin=2_000, spacing=4_500) # U-turn back West
|
||||
)
|
||||
|
||||
# Retool VCC back to M2 and move both to x=-28k
|
||||
rpather.at('VCC').retool(M2_tool)
|
||||
rpather.at(['GND', 'VCC']).mpath(ccw=None, xmin=-28_000)
|
||||
rpather.at(['GND', 'VCC']).trace(None, xmin=-28_000)
|
||||
|
||||
# Final segments to -50k
|
||||
rpather.at('VCC').path_to(ccw=None, x=-50_000, out_ptype='m1wire')
|
||||
rpather.at('VCC').trace_to(None, x=-50_000, out_ptype='m1wire')
|
||||
with rpather.at('GND').toolctx(M2_tool):
|
||||
rpather.at('GND').path_to(ccw=None, x=-40_000)
|
||||
rpather.at('GND').path_to(ccw=None, x=-50_000)
|
||||
rpather.at('GND').trace_to(None, x=-40_000)
|
||||
rpather.at('GND').trace_to(None, x=-50_000)
|
||||
|
||||
|
||||
#
|
||||
# Branching with save_copy and into_copy
|
||||
# Branching with mark and fork
|
||||
#
|
||||
# .save_copy(new_name) creates a port copy and keeps the original selected.
|
||||
# .into_copy(new_name) creates a port copy and selects the new one.
|
||||
# .mark(new_name) creates a port copy and keeps the original selected.
|
||||
# .fork(new_name) creates a port copy and selects the new one.
|
||||
|
||||
# Create a tap on GND
|
||||
(rpather.at('GND')
|
||||
.path(ccw=None, length=5_000) # Move GND further West
|
||||
.save_copy('GND_TAP') # Mark this location for a later branch
|
||||
.pathS(length=10_000, jog=-10_000) # Continue GND with an S-bend
|
||||
.trace(None, length=5_000) # Move GND further West
|
||||
.mark('GND_TAP') # Mark this location for a later branch
|
||||
.jog(offset=-10_000, length=10_000) # Continue GND with an S-bend
|
||||
)
|
||||
|
||||
# Branch VCC and follow the new branch
|
||||
(rpather.at('VCC')
|
||||
.path(ccw=None, length=5_000)
|
||||
.into_copy('VCC_BRANCH') # We are now manipulating 'VCC_BRANCH'
|
||||
.path(ccw=True, length=5_000) # VCC_BRANCH turns South
|
||||
.trace(None, length=5_000)
|
||||
.fork('VCC_BRANCH') # We are now manipulating 'VCC_BRANCH'
|
||||
.trace(True, length=5_000) # VCC_BRANCH turns South
|
||||
)
|
||||
# The original 'VCC' port remains at x=-55k, y=VCC.y
|
||||
|
||||
|
|
@ -99,27 +99,25 @@ def main() -> None:
|
|||
# Route the GND_TAP we saved earlier.
|
||||
(rpather.at('GND_TAP')
|
||||
.retool(M1_tool)
|
||||
.path(ccw=True, length=10_000) # Turn South
|
||||
.rename_to('GND_FEED') # Give it a more descriptive name
|
||||
.trace(True, length=10_000) # Turn South
|
||||
.rename('GND_FEED') # Give it a more descriptive name
|
||||
.retool(M1_tool) # Re-apply tool to the new name
|
||||
)
|
||||
|
||||
# We can manage the active set of ports in a PortPather
|
||||
pp = rpather.at(['VCC_BRANCH', 'GND_FEED'])
|
||||
pp.add_port('GND') # Now tracking 3 ports
|
||||
pp.drop_port('VCC_BRANCH') # Now tracking 2 ports: GND_FEED, GND
|
||||
pp.path_each(ccw=None, length=5_000) # Move both 5um forward (length > transition size)
|
||||
pp.select('GND') # Now tracking 3 ports
|
||||
pp.deselect('VCC_BRANCH') # Now tracking 2 ports: GND_FEED, GND
|
||||
pp.trace(None, each=5_000) # Move both 5um forward (length > transition size)
|
||||
|
||||
# We can also delete ports from the pather entirely
|
||||
rpather.at('VCC').delete() # VCC is gone (we have VCC_BRANCH instead)
|
||||
|
||||
|
||||
#
|
||||
# Advanced Connections: path_into and path_from
|
||||
# Advanced Connections: trace_into
|
||||
#
|
||||
|
||||
# path_into routes FROM the selected port TO a target port.
|
||||
# path_from routes TO the selected port FROM a source port.
|
||||
# trace_into routes FROM the selected port TO a target port.
|
||||
|
||||
# Create a destination component
|
||||
dest_ports = {
|
||||
|
|
@ -133,10 +131,10 @@ def main() -> None:
|
|||
|
||||
# Connect GND_FEED to DEST_A
|
||||
# Since GND_FEED is moving South and DEST_A faces West, a single bend will suffice.
|
||||
rpather.at('GND_FEED').path_into('DEST_A')
|
||||
rpather.at('GND_FEED').trace_into('DEST_A')
|
||||
|
||||
# Connect VCC_BRANCH to DEST_B using path_from
|
||||
rpather.at('DEST_B').path_from('VCC_BRANCH')
|
||||
# Connect VCC_BRANCH to DEST_B
|
||||
rpather.at('VCC_BRANCH').trace_into('DEST_B')
|
||||
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -276,8 +276,8 @@ class Tool:
|
|||
Args:
|
||||
length: The total distance from input to output, along the input's axis only.
|
||||
jog: The total offset from the input to output, along the perpendicular axis.
|
||||
A positive number implies a rightwards shift (i.e. clockwise bend followed
|
||||
by a counterclockwise bend)
|
||||
A positive number implies a leftward shift (i.e. counterclockwise bend followed
|
||||
by a clockwise bend)
|
||||
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
|
||||
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def ell(
|
|||
ccw: Turn direction. `True` means counterclockwise, `False` means clockwise,
|
||||
and `None` means no bend. If `None`, spacing must remain `None` or `0` (default),
|
||||
Otherwise, spacing must be set to a non-`None` value.
|
||||
bound_method: Method used for determining the travel distance; see diagram above.
|
||||
bound_type: Method used for determining the travel distance; see diagram above.
|
||||
Valid values are:
|
||||
- 'min_extension' or 'emin':
|
||||
The total extension value for the furthest-out port (B in the diagram).
|
||||
|
|
@ -64,7 +64,7 @@ def ell(
|
|||
the x- and y- axes. If specifying a position, it is projected onto
|
||||
the extension direction.
|
||||
|
||||
bound_value: Value associated with `bound_type`, see above.
|
||||
bound: Value associated with `bound_type`, see above.
|
||||
spacing: Distance between adjacent channels. Can be scalar, resulting in evenly
|
||||
spaced channels, or a vector with length one less than `ports`, allowing
|
||||
non-uniform spacing.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue