move dev2pat and pat2dev into masque.builder.port_utils
This commit is contained in:
parent
36f6edac21
commit
2b8195ad3e
@ -5,9 +5,8 @@ from numpy import pi
|
|||||||
|
|
||||||
from masque import layer_t, Pattern, SubPattern, Label
|
from masque import layer_t, Pattern, SubPattern, Label
|
||||||
from masque.shapes import Polygon
|
from masque.shapes import Polygon
|
||||||
from masque.builder import Device, Port
|
from masque.builder import Device, Port, port_utils
|
||||||
from masque.file.gdsii import writefile
|
from masque.file.gdsii import writefile
|
||||||
from masque.utils import rotation_matrix_2d
|
|
||||||
|
|
||||||
import pcgen
|
import pcgen
|
||||||
import basic_shapes
|
import basic_shapes
|
||||||
@ -18,6 +17,22 @@ LATTICE_CONSTANT = 512
|
|||||||
RADIUS = LATTICE_CONSTANT / 2 * 0.75
|
RADIUS = LATTICE_CONSTANT / 2 * 0.75
|
||||||
|
|
||||||
|
|
||||||
|
def dev2pat(dev: Device) -> Pattern:
|
||||||
|
"""
|
||||||
|
Bake port information into the device.
|
||||||
|
This places a label at each port location on layer (3, 0) with text content
|
||||||
|
'name:ptype angle_deg'
|
||||||
|
"""
|
||||||
|
return port_utils.dev2pat(dev, layer=(3, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def pat2dev(pat: Pattern) -> Device:
|
||||||
|
"""
|
||||||
|
Scans the Pattern to determine port locations. Same format as `dev2pat`
|
||||||
|
"""
|
||||||
|
return port_utils.pat2dev(pat, layers=[(3, 0)])
|
||||||
|
|
||||||
|
|
||||||
def perturbed_l3(
|
def perturbed_l3(
|
||||||
lattice_constant: float,
|
lattice_constant: float,
|
||||||
hole: Pattern,
|
hole: Pattern,
|
||||||
@ -201,78 +216,6 @@ def y_splitter(
|
|||||||
return Device(pat, ports)
|
return Device(pat, ports)
|
||||||
|
|
||||||
|
|
||||||
def dev2pat(device: Device, layer: layer_t = (3, 0)) -> Pattern:
|
|
||||||
"""
|
|
||||||
Place a text label at each port location, specifying the port data.
|
|
||||||
|
|
||||||
This can be used to debug port locations or to automatically generate ports
|
|
||||||
when reading in a GDS file.
|
|
||||||
|
|
||||||
NOTE that `device` is modified by this function, and `device.pattern` is returned.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
device: The device which is to have its ports labeled. MODIFIED in-place.
|
|
||||||
layer: The layer on which the labels will be placed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
`device.pattern`
|
|
||||||
"""
|
|
||||||
for name, port in device.ports.items():
|
|
||||||
if port.rotation is None:
|
|
||||||
angle_deg = numpy.inf
|
|
||||||
else:
|
|
||||||
angle_deg = numpy.rad2deg(port.rotation)
|
|
||||||
device.pattern.labels += [
|
|
||||||
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
|
|
||||||
]
|
|
||||||
return device.pattern
|
|
||||||
|
|
||||||
|
|
||||||
def pat2dev(
|
|
||||||
pattern: Pattern,
|
|
||||||
layers: Sequence[layer_t] = ((3, 0),),
|
|
||||||
max_depth: int = 999_999,
|
|
||||||
skip_subcells: bool = True,
|
|
||||||
) -> Device:
|
|
||||||
ports = {} # Note: could do a list here, if they're not unique
|
|
||||||
annotated_cells = set()
|
|
||||||
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
|
||||||
if len(hierarchy) > max_depth - 1:
|
|
||||||
return pat
|
|
||||||
|
|
||||||
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
|
|
||||||
return pat
|
|
||||||
|
|
||||||
labels = [ll for ll in pat.labels if ll.layer in layers]
|
|
||||||
|
|
||||||
if len(labels) == 0:
|
|
||||||
return pat
|
|
||||||
|
|
||||||
if skip_subcells:
|
|
||||||
annotated_cells.add(pat)
|
|
||||||
|
|
||||||
mirr_factor = numpy.array((1, -1)) ** transform[3]
|
|
||||||
rot_matrix = rotation_matrix_2d(transform[2])
|
|
||||||
for label in labels:
|
|
||||||
name, property_string = label.string.split(':')
|
|
||||||
properties = property_string.split(' ')
|
|
||||||
ptype = properties[0]
|
|
||||||
angle_deg = float(properties[1]) if len(ptype) else 0
|
|
||||||
|
|
||||||
xy_global = transform[:2] + rot_matrix @ (label.offset * mirr_factor)
|
|
||||||
angle = numpy.deg2rad(angle_deg) * mirr_factor[0] * mirr_factor[1] + transform[2]
|
|
||||||
|
|
||||||
if name in ports:
|
|
||||||
raise Exception('Duplicate port name in pattern!')
|
|
||||||
|
|
||||||
ports[name] = Port(offset=xy_global, rotation=angle, ptype=ptype)
|
|
||||||
|
|
||||||
return pat
|
|
||||||
|
|
||||||
pattern.dfs(visit_before=find_ports_each, transform=True)
|
|
||||||
return Device(pattern, ports)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(interactive: bool = True):
|
def main(interactive: bool = True):
|
||||||
# Generate some basic hole patterns
|
# Generate some basic hole patterns
|
||||||
|
@ -11,6 +11,7 @@ from masque.file.gdsii import writefile, load_libraryfile
|
|||||||
import pcgen
|
import pcgen
|
||||||
import basic_shapes
|
import basic_shapes
|
||||||
import devices
|
import devices
|
||||||
|
from devices import pat2dev, dev2pat
|
||||||
from basic_shapes import GDS_OPTS
|
from basic_shapes import GDS_OPTS
|
||||||
|
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ def main() -> None:
|
|||||||
# Add it into the device library by providing a way to read port info
|
# Add it into the device library by providing a way to read port info
|
||||||
# This maintains the lazy evaluation from above, so no patterns
|
# This maintains the lazy evaluation from above, so no patterns
|
||||||
# are actually read yet.
|
# are actually read yet.
|
||||||
device_lib.add_library(pattern_lib, pat2dev=devices.pat2dev)
|
device_lib.add_library(pattern_lib, pat2dev=pat2dev)
|
||||||
|
|
||||||
print('Devices loaded from GDS into library:\n' + pformat(list(device_lib.keys())))
|
print('Devices loaded from GDS into library:\n' + pformat(list(device_lib.keys())))
|
||||||
|
|
||||||
@ -43,10 +44,10 @@ def main() -> None:
|
|||||||
|
|
||||||
# Convenience function for adding devices
|
# Convenience function for adding devices
|
||||||
# This is roughly equivalent to
|
# This is roughly equivalent to
|
||||||
# `device_lib[name] = lambda: devices.dev2pat(fn())`
|
# `device_lib[name] = lambda: dev2pat(fn())`
|
||||||
# but it also guarantees that the resulting pattern is named `name`.
|
# but it also guarantees that the resulting pattern is named `name`.
|
||||||
def add(name: str, fn: Callable[[], Device]) -> None:
|
def add(name: str, fn: Callable[[], Device]) -> None:
|
||||||
device_lib.add_device(name=name, fn=fn, dev2pat=devices.dev2pat)
|
device_lib.add_device(name=name, fn=fn, dev2pat=dev2pat)
|
||||||
|
|
||||||
# Triangle-based variants. These are defined here, but they won't run until they're
|
# Triangle-based variants. These are defined here, but they won't run until they're
|
||||||
# retrieved from the library.
|
# retrieved from the library.
|
||||||
|
112
masque/builder/port_utils.py
Normal file
112
masque/builder/port_utils.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
"""
|
||||||
|
Functions for writing port data into a Pattern (`dev2pat`) and retrieving it (`pat2dev`).
|
||||||
|
|
||||||
|
These use the format 'name:ptype angle_deg' written into labels, which are placed at
|
||||||
|
the port locations. This particular approach is just a sensible default; feel free to
|
||||||
|
to write equivalent functions for your own format or alternate storage methods.
|
||||||
|
"""
|
||||||
|
from typing import Sequence
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from ..pattern import Pattern
|
||||||
|
from ..label import Label
|
||||||
|
from ..utils import rotation_matrix_2d, layer_t
|
||||||
|
from .devices import Device, Port
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def dev2pat(device: Device, layer: layer_t) -> Pattern:
|
||||||
|
"""
|
||||||
|
Place a text label at each port location, specifying the port data in the format
|
||||||
|
'name:ptype angle_deg'
|
||||||
|
|
||||||
|
This can be used to debug port locations or to automatically generate ports
|
||||||
|
when reading in a GDS file.
|
||||||
|
|
||||||
|
NOTE that `device` is modified by this function, and `device.pattern` is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device: The device which is to have its ports labeled. MODIFIED in-place.
|
||||||
|
layer: The layer on which the labels will be placed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`device.pattern`
|
||||||
|
"""
|
||||||
|
for name, port in device.ports.items():
|
||||||
|
if port.rotation is None:
|
||||||
|
angle_deg = numpy.inf
|
||||||
|
else:
|
||||||
|
angle_deg = numpy.rad2deg(port.rotation)
|
||||||
|
device.pattern.labels += [
|
||||||
|
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
|
||||||
|
]
|
||||||
|
return device.pattern
|
||||||
|
|
||||||
|
|
||||||
|
def pat2dev(
|
||||||
|
pattern: Pattern,
|
||||||
|
layers: Sequence[layer_t],
|
||||||
|
max_depth: int = 999_999,
|
||||||
|
skip_subcells: bool = True,
|
||||||
|
) -> Device:
|
||||||
|
"""
|
||||||
|
Examine `pattern` for labels specifying port info, and use that info
|
||||||
|
to build a `Device` object.
|
||||||
|
|
||||||
|
Labels are assumed to be placed at the port locations, and have the format
|
||||||
|
'name:ptype angle_deg'
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pattern: Pattern object to scan for labels.
|
||||||
|
layers: Search for labels on all the given layers.
|
||||||
|
max_depth: Maximum hierarcy depth to search. Default 999_999.
|
||||||
|
Reduce this to 0 to avoid ever searching subcells.
|
||||||
|
skip_subcells: If port labels are found at a given hierarcy level,
|
||||||
|
do not continue searching at deeper levels. This allows subcells
|
||||||
|
to contain their own port info (and thus become their own Devices).
|
||||||
|
Default True.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The constructed Device object. Port labels are not removed from the pattern.
|
||||||
|
"""
|
||||||
|
ports = {} # Note: could do a list here, if they're not unique
|
||||||
|
annotated_cells = set()
|
||||||
|
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
||||||
|
if len(hierarchy) > max_depth - 1:
|
||||||
|
return pat
|
||||||
|
|
||||||
|
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
|
||||||
|
return pat
|
||||||
|
|
||||||
|
labels = [ll for ll in pat.labels if ll.layer in layers]
|
||||||
|
|
||||||
|
if len(labels) == 0:
|
||||||
|
return pat
|
||||||
|
|
||||||
|
if skip_subcells:
|
||||||
|
annotated_cells.add(pat)
|
||||||
|
|
||||||
|
mirr_factor = numpy.array((1, -1)) ** transform[3]
|
||||||
|
rot_matrix = rotation_matrix_2d(transform[2])
|
||||||
|
for label in labels:
|
||||||
|
name, property_string = label.string.split(':')
|
||||||
|
properties = property_string.split(' ')
|
||||||
|
ptype = properties[0]
|
||||||
|
angle_deg = float(properties[1]) if len(ptype) else 0
|
||||||
|
|
||||||
|
xy_global = transform[:2] + rot_matrix @ (label.offset * mirr_factor)
|
||||||
|
angle = numpy.deg2rad(angle_deg) * mirr_factor[0] * mirr_factor[1] + transform[2]
|
||||||
|
|
||||||
|
if name in ports:
|
||||||
|
logger.info(f'Duplicate port {name} in pattern {pattern.name}')
|
||||||
|
|
||||||
|
ports[name] = Port(offset=xy_global, rotation=angle, ptype=ptype)
|
||||||
|
|
||||||
|
return pat
|
||||||
|
|
||||||
|
pattern.dfs(visit_before=find_ports_each, transform=True)
|
||||||
|
return Device(pattern, ports)
|
Loading…
Reference in New Issue
Block a user