You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
masque/masque/builder/port_utils.py

123 lines
4.2 KiB
Python

"""
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.
"""
1 year ago
from typing import Sequence, Optional, Mapping
import logging
import numpy
from ..pattern import Pattern
from ..label import Label
from ..utils import rotation_matrix_2d, layer_t
from ..ports import Port
logger = logging.getLogger(__name__)
def dev2pat(pattern: Pattern, 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 `pattern` is modified by this function
Args:
pattern: The pattern which is to have its ports labeled. MODIFIED in-place.
layer: The layer on which the labels will be placed.
Returns:
`pattern`
"""
for name, port in pattern.ports.items():
if port.rotation is None:
angle_deg = numpy.inf
else:
angle_deg = numpy.rad2deg(port.rotation)
pattern.labels += [
Label(string=f'{name}:{port.ptype} {angle_deg:g}', layer=layer, offset=port.offset)
]
return pattern
def pat2dev(
pattern: Pattern,
layers: Sequence[layer_t],
1 year ago
library: Optional[Mapping[str, Pattern]] = None,
max_depth: int = 999_999,
skip_subcells: bool = True,
) -> Pattern:
"""
Examine `pattern` for labels specifying port info, and use that info
to fill out its `ports` attribute.
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 without interfering with supercells'
port data.
Default True.
Returns:
The updated `pattern`. Port labels are not removed.
"""
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:
1 year ago
logger.info(f'Duplicate port {name} in pattern {pattern.name}') # TODO DFS should include name?
ports[name] = Port(offset=xy_global, rotation=angle, ptype=ptype)
return pat
# TODO TODO TODO
if skip_subcells and ports := find_ports_each(pattern, ...):
# TODO Could do this with just the `pattern` itself
pass
else
# TODO need `name` and `library` here
ports = library.dfs(name, visit_before=find_ports_each, transform=True) #TODO: don't check Library if there are ports in top level
pattern.check_ports(other_ports=ports)
pattern.ports.update(ports)
return pattern