Compare commits

...

2 commits

Author SHA1 Message Date
jan
59e996e680 [tutorial] include a repetition and update docs 2026-02-15 20:05:38 -08:00
jan
abf236a046 [mirror / flip_across] improve documentation 2026-02-15 19:46:47 -08:00
7 changed files with 74 additions and 18 deletions

View file

@ -18,11 +18,14 @@ Contents
* Design a pattern which is meant to plug into an existing pattern (via `.interface()`)
- [pather](pather.py)
* Use `Pather` to route individual wires and wire bundles
* Use `BasicTool` to generate paths
* Use `BasicTool` to automatically transition between path types
- [renderpather](rendpather.py)
* Use `AutoTool` to generate paths
* Use `AutoTool` to automatically transition between path types
- [renderpather](renderpather.py)
* Use `RenderPather` and `PathTool` to build a layout similar to the one in [pather](pather.py),
but using `Path` shapes instead of `Polygon`s.
- [port_pather](port_pather.py)
* Use `PortPather` and the `.at()` syntax for more concise routing
* Advanced port manipulation and connections
Additionaly, [pcgen](pcgen.py) is a utility module for generating photonic crystal lattices.

View file

@ -2,9 +2,8 @@
import numpy
from numpy import pi
from masque import (
layer_t, Pattern, Circle, Arc, Polygon,
)
from masque import layer_t, Pattern, Circle, Arc, Polygon, Ref
from masque.repetition import Grid
import masque.file.gdsii
@ -37,6 +36,45 @@ def hole(
return pat
def hole_array(
radius: float,
num_x: int = 5,
num_y: int = 3,
pitch: float = 2000,
layer: layer_t = (1, 0),
) -> Pattern:
"""
Generate an array of circular holes using `Repetition`.
Args:
radius: Circle radius.
num_x, num_y: Number of holes in x and y.
pitch: Center-to-center spacing.
layer: Layer to draw the holes on.
Returns:
Pattern containing a grid of holes.
"""
# First, make a pattern for a single hole
hpat = hole(radius, layer)
# Now, create a pattern that references it multiple times using a Grid
pat = Pattern()
pat.refs['hole'] = [
Ref(
offset=(0, 0),
repetition=Grid(a_vector=(pitch, 0), a_count=num_x,
b_vector=(0, pitch), b_count=num_y)
)]
# We can also add transformed references (rotation, mirroring, etc.)
pat.refs['hole'].append(
Ref(offset=(0, -pitch), rotation=pi / 4, mirrored=True)
)
return pat, hpat
def triangle(
radius: float,
layer: layer_t = (1, 0),
@ -58,9 +96,7 @@ def triangle(
]) * radius
pat = Pattern()
pat.shapes[layer].extend([
Polygon(offset=(0, 0), vertices=vertices),
])
pat.polygon(layer, vertices=vertices)
return pat
@ -109,9 +145,13 @@ def main() -> None:
lib['smile'] = smile(1000)
lib['triangle'] = triangle(1000)
# Use a Grid to make many holes efficiently
lib['grid'], lib['hole'] = hole_array(1000)
masque.file.gdsii.writefile(lib, 'basic_shapes.gds', **GDS_OPTS)
lib['triangle'].visualize()
lib['grid'].visualize(lib)
if __name__ == '__main__':

View file

@ -239,7 +239,7 @@ def main() -> None:
pather.path_to('GND', None, x=pather['VCC'].offset[0])
# Now, start using M1_tool for GND.
# Since we have defined an M2-to-M1 transition for BasicPather, we don't need to place one ourselves.
# Since we have defined an M2-to-M1 transition for Pather, we don't need to place one ourselves.
# 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

View file

@ -12,7 +12,7 @@ from pather import M1_WIDTH, V1_WIDTH, M2_WIDTH, map_layer, make_pad, make_via
def main() -> None:
#
# To illustrate the advantages of using `RenderPather`, we use `PathTool` instead
# of `BasicTool`. `PathTool` lacks some sophistication (e.g. no automatic transitions)
# of `AutoTool`. `PathTool` lacks some sophistication (e.g. no automatic transitions)
# but when used with `RenderPather`, it can consolidate multiple routing steps into
# a single `Path` shape.
#
@ -34,7 +34,7 @@ def main() -> None:
ptype_top = 'm2wire',
)
# `PathTool` is more limited than `BasicTool`. It only generates one type of shape
# `PathTool` is more limited than `AutoTool`. It only generates one type of shape
# (`Path`), so it only needs to know what layer to draw on, what width to draw with,
# and what port type to present.
M1_ptool = PathTool(layer='M1', width=M1_WIDTH, ptype='m1wire')
@ -77,6 +77,7 @@ def main() -> None:
# to account for it.
v1pat = library['v1_via']
via_size = abs(v1pat.ports['top'].x - v1pat.ports['bottom'].x)
# alternatively, via_size = v1pat.ports['top'].measure_travel(v1pat.ports['bottom'])[0][0]
# would take into account the port orientations if we didn't already know they're along x
rpather.path_to('VCC', None, -50_000 + via_size)

View file

@ -104,10 +104,12 @@ class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotabl
def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self:
"""
Mirror the object across a line.
Flip the label across a line in the pattern's coordinate system.
This operation mirrors the label's offset relative to the pattern's origin.
Args:
axis: Axis to mirror across. 0 mirrors across x=0. 1 mirrors across y=0.
axis: Axis to mirror across. 0 mirrors across y=0. 1 mirrors across x=0.
x: Vertical line x=val to mirror across.
y: Horizontal line y=val to mirror across.

View file

@ -108,7 +108,9 @@ class Port(PivotableImpl, PositionableImpl, Mirrorable, Flippable, Copyable):
def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self:
"""
Mirror the object across a line.
Mirror the object across a line in the container's coordinate system.
Note this operation is performed relative to the pattern's origin and modifies the port's offset.
Args:
axis: Axis to mirror across. 0 mirrors across y=0. 1 mirrors across x=0.

View file

@ -18,7 +18,11 @@ class Mirrorable(metaclass=ABCMeta):
@abstractmethod
def mirror(self, axis: int = 0) -> Self:
"""
Mirror the entity across an axis through its origin, ignoring its offset.
Mirror the entity across an axis through its origin.
This operation is performed relative to the object's internal origin (ignoring
its offset). For objects like `Polygon` and `Path` where the offset is forced
to (0, 0), this is equivalent to mirroring in the container's coordinate system.
Args:
axis: Axis to mirror across (0: x-axis, 1: y-axis).
@ -70,10 +74,14 @@ class Flippable(Positionable, metaclass=ABCMeta):
@abstractmethod
def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self:
"""
Mirror the object across a line.
Mirror the object across a line in the container's coordinate system.
Unlike `mirror()`, this operation is performed relative to the container's origin
(e.g. the `Pattern` origin, in the case of shapes) and takes the object's offset
into account.
Args:
axis: Axis to mirror across. 0 mirrors across x=0. 1 mirrors across y=0.
axis: Axis to mirror across. 0 mirrors across y=0. 1 mirrors across x=0.
x: Vertical line x=val to mirror across.
y: Horizontal line y=val to mirror across.