Lots of progress on tutorials
This commit is contained in:
parent
c31d7dfa2c
commit
f4537a0feb
16 changed files with 579 additions and 510 deletions
|
|
@ -1,27 +1,38 @@
|
|||
from typing import Dict, Union, Optional
|
||||
from typing import MutableMapping, TYPE_CHECKING
|
||||
from typing import Dict
|
||||
#from typing import Union, Optional, MutableMapping, TYPE_CHECKING
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from .pattern import Pattern
|
||||
#from .pattern import Pattern
|
||||
from .ports import PortList, Port
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .builder import Builder, Tool
|
||||
from .library import MutableLibrary
|
||||
#if TYPE_CHECKING:
|
||||
# from .builder import Builder, Tool
|
||||
# from .library import MutableLibrary
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AA = TypeVar('AA', bound='Abstract')
|
||||
|
||||
|
||||
class Abstract(PortList):
|
||||
__slots__ = ('name', 'ports')
|
||||
__slots__ = ('name', '_ports')
|
||||
|
||||
name: str
|
||||
""" Name of the pattern this device references """
|
||||
|
||||
ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to snap instances together"""
|
||||
_ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to instances together"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self._ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self._ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -31,22 +42,22 @@ class Abstract(PortList):
|
|||
self.name = name
|
||||
self.ports = copy.deepcopy(ports)
|
||||
|
||||
def build(
|
||||
self,
|
||||
library: 'MutableLibrary',
|
||||
tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None,
|
||||
) -> 'Builder':
|
||||
"""
|
||||
Begin building a new device around an instance of the current device
|
||||
(rather than modifying the current device).
|
||||
|
||||
Returns:
|
||||
The new `Builder` object.
|
||||
"""
|
||||
pat = Pattern(ports=self.ports)
|
||||
pat.ref(self.name)
|
||||
new = Builder(library=library, pattern=pat, tools=tools) # TODO should Abstract have tools?
|
||||
return new
|
||||
# def build(
|
||||
# self,
|
||||
# library: 'MutableLibrary',
|
||||
# tools: Union[None, 'Tool', MutableMapping[Optional[str], 'Tool']] = None,
|
||||
# ) -> 'Builder':
|
||||
# """
|
||||
# Begin building a new device around an instance of the current device
|
||||
# (rather than modifying the current device).
|
||||
#
|
||||
# Returns:
|
||||
# The new `Builder` object.
|
||||
# """
|
||||
# pat = Pattern(ports=self.ports)
|
||||
# pat.ref(self.name)
|
||||
# new = Builder(library=library, pattern=pat, tools=tools) # TODO should Abstract have tools?
|
||||
# return new
|
||||
|
||||
# TODO do we want to store a Ref instead of just a name? then we can translate/rotate/mirror...
|
||||
|
||||
|
|
@ -56,3 +67,164 @@ class Abstract(PortList):
|
|||
s += f'\n\t{name}: {port}'
|
||||
s += ']>'
|
||||
return s
|
||||
|
||||
def translate_ports(self: AA, offset: ArrayLike) -> AA:
|
||||
"""
|
||||
Translates all ports by the given offset.
|
||||
|
||||
Args:
|
||||
offset: (x, y) to translate by
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.translate(offset)
|
||||
return self
|
||||
|
||||
def scale_by(self: AA, c: float) -> AA:
|
||||
"""
|
||||
Scale this Abstract by the given value
|
||||
(all port offsets are scaled)
|
||||
|
||||
Args:
|
||||
c: factor to scale by
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset *= c
|
||||
return self
|
||||
|
||||
def rotate_around(self: AA, pivot: ArrayLike, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate the Abstract around the a location.
|
||||
|
||||
Args:
|
||||
pivot: (x, y) location to rotate around
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
pivot = numpy.array(pivot)
|
||||
self.translate_ports(-pivot)
|
||||
self.rotate_ports(rotation)
|
||||
self.rotate_port_offsets(rotation)
|
||||
self.translate_ports(+pivot)
|
||||
return self
|
||||
|
||||
def rotate_port_offsets(self: AA, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate the offsets of all ports around (0, 0)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset = rotation_matrix_2d(rotation) @ port.offset
|
||||
return self
|
||||
|
||||
def rotate_ports(self: AA, rotation: float) -> AA:
|
||||
"""
|
||||
Rotate each port around its offset (i.e. in place)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.rotate(rotation)
|
||||
return self
|
||||
|
||||
def mirror_port_offsets(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
||||
|
||||
Args:
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.offset[across_axis - 1] *= -1
|
||||
return self
|
||||
|
||||
def mirror_ports(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror each port's rotation across an axis, relative to its
|
||||
offset
|
||||
|
||||
Args:
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for port in self.ports.values():
|
||||
port.mirror(across_axis)
|
||||
return self
|
||||
|
||||
def mirror(self: AA, across_axis: int) -> AA:
|
||||
"""
|
||||
Mirror the Pattern across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.mirror_ports(across_axis)
|
||||
self.mirror_port_offsets(across_axis)
|
||||
return self
|
||||
|
||||
def apply_ref_transform(self: AA, ref: Ref) -> AA:
|
||||
"""
|
||||
Apply the transform from a `Ref` to the ports of this `Abstract`.
|
||||
This changes the port locations to where they would be in the Ref's parent pattern.
|
||||
|
||||
Args:
|
||||
ref: The ref whose transform should be applied.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
mirror_across_x, angle = normalize_mirror(ref.mirrored)
|
||||
if mirrored_across_x:
|
||||
self.mirror(across_axis=0)
|
||||
self.rotate_ports(angle + ref.rotation)
|
||||
self.rotate_port_offsets(angle + ref.rotation)
|
||||
self.translate_ports(ref.offset)
|
||||
return self
|
||||
|
||||
def undo_ref_transform(self: AA, ref: Ref) -> AA:
|
||||
"""
|
||||
Apply the inverse transform from a `Ref` to the ports of this `Abstract`.
|
||||
This changes the port locations to where they would be in the Ref's target (from the parent).
|
||||
|
||||
Args:
|
||||
ref: The ref whose (inverse) transform should be applied.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
# TODO test undo_ref_transform
|
||||
"""
|
||||
mirror_across_x, angle = normalize_mirror(ref.mirrored)
|
||||
self.translate_ports(-ref.offset)
|
||||
self.rotate_port_offsets(-angle - ref.rotation)
|
||||
self.rotate_ports(-angle - ref.rotation)
|
||||
if mirrored_across_x:
|
||||
self.mirror(across_axis=0)
|
||||
return self
|
||||
|
|
|
|||
|
|
@ -97,19 +97,30 @@ class Builder(PortList):
|
|||
_dead: bool
|
||||
""" If True, plug()/place() are skipped (for debugging)"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self.pattern.ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self.pattern.ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library: MutableLibrary,
|
||||
*,
|
||||
pattern: Optional[Pattern] = None,
|
||||
ports: Optional[Mapping[str, Port]] = None,
|
||||
ports: Union[None, str, Mapping[str, Port]] = None,
|
||||
tools: Union[None, Tool, MutableMapping[Optional[str], Tool]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
If `ports` is `None`, two default ports ('A' and 'B') are created.
|
||||
Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0
|
||||
(attached devices will be placed to the left) and 'B' has rotation
|
||||
pi (attached devices will be placed to the right).
|
||||
# TODO documentation for Builder() constructor
|
||||
|
||||
# TODO MOVE THE BELOW DOCS to PortList
|
||||
# If `ports` is `None`, two default ports ('A' and 'B') are created.
|
||||
# Both are placed at (0, 0) and have default `ptype`, but 'A' has rotation 0
|
||||
# (attached devices will be placed to the left) and 'B' has rotation
|
||||
# pi (attached devices will be placed to the right).
|
||||
"""
|
||||
self.library = library
|
||||
if pattern is not None:
|
||||
|
|
@ -120,7 +131,10 @@ class Builder(PortList):
|
|||
if ports is not None:
|
||||
if self.pattern.ports:
|
||||
raise BuildError('Ports supplied for pattern with pre-existing ports!')
|
||||
self.pattern.ports.update(copy.deepcopy(ports))
|
||||
if isinstance(ports, str):
|
||||
ports = library.abstract(ports).ports
|
||||
|
||||
self.pattern.ports.update(copy.deepcopy(dict(ports)))
|
||||
|
||||
if tools is None:
|
||||
self.tools = {}
|
||||
|
|
@ -509,7 +523,7 @@ class Builder(PortList):
|
|||
in_ptype = self.pattern[portspec].ptype
|
||||
pat = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
|
||||
name = self.library.get_name(base_name)
|
||||
self.library._set(name, pat)
|
||||
self.library.set_const(name, pat)
|
||||
return self.plug(Abstract(name, pat.ports), {portspec: tool_port_names[0]})
|
||||
|
||||
def path_to(
|
||||
|
|
@ -592,7 +606,7 @@ class Builder(PortList):
|
|||
for port_name, length in extensions.items():
|
||||
bld.path(port_name, ccw, length, tool_port_names=tool_port_names)
|
||||
name = self.library.get_name(base_name)
|
||||
self.library._set(name, bld.pattern)
|
||||
self.library.set_const(name, bld.pattern)
|
||||
return self.plug(Abstract(name, bld.pattern.ports), {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'?
|
||||
|
||||
# TODO def path_join() and def bus_join()?
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ Functions for writing port data into a Pattern (`dev2pat`) and retrieving it (`p
|
|||
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, Optional, Mapping
|
||||
from typing import Sequence, Optional, Mapping, Tuple, Dict
|
||||
import logging
|
||||
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from ..pattern import Pattern
|
||||
from ..label import Label
|
||||
|
|
@ -50,13 +51,16 @@ def dev2pat(pattern: Pattern, layer: layer_t) -> Pattern:
|
|||
|
||||
|
||||
def pat2dev(
|
||||
library: Mapping[str, Pattern],
|
||||
top: str,
|
||||
layers: Sequence[layer_t],
|
||||
library: Mapping[str, Pattern],
|
||||
pattern: Pattern,
|
||||
name: Optional[str] = None,
|
||||
max_depth: int = 999_999,
|
||||
skip_subcells: bool = True,
|
||||
) -> Pattern:
|
||||
"""
|
||||
# TODO fixup documentation in port_utils
|
||||
# TODO move port_utils to utils.file?
|
||||
Examine `pattern` for labels specifying port info, and use that info
|
||||
to fill out its `ports` attribute.
|
||||
|
||||
|
|
@ -64,8 +68,8 @@ def pat2dev(
|
|||
'name:ptype angle_deg'
|
||||
|
||||
Args:
|
||||
pattern: Pattern object to scan for labels.
|
||||
layers: Search for labels on all the given layers.
|
||||
pattern: Pattern object to scan for labels.
|
||||
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,
|
||||
|
|
@ -73,52 +77,51 @@ def pat2dev(
|
|||
to contain their own port info without interfering with supercells'
|
||||
port data.
|
||||
Default True.
|
||||
blacklist: If a cell name appears in the blacklist, do not ea
|
||||
|
||||
Returns:
|
||||
The updated `pattern`. Port labels are not removed.
|
||||
"""
|
||||
print(f'TODO pat2dev {name}')
|
||||
if pattern.ports:
|
||||
logger.warning(f'Pattern {name if name else pattern} already had ports, skipping pat2dev')
|
||||
return pattern
|
||||
|
||||
if not isinstance(library, Library):
|
||||
library = WrapROLibrary(library)
|
||||
|
||||
ports = {}
|
||||
annotated_cells = set()
|
||||
pat2dev_flat(layers, pattern, name)
|
||||
if (skip_subcells and pattern.ports) or max_depth == 0:
|
||||
return pattern
|
||||
|
||||
def find_ports_each(pat, hierarchy, transform, memo) -> Pattern:
|
||||
if len(hierarchy) > max_depth:
|
||||
if max_depth >= 999_999:
|
||||
logger.warning(f'pat2dev reached max depth ({max_depth})')
|
||||
return pat
|
||||
# Load ports for all subpatterns
|
||||
for target in set(rr.target for rr in pat.refs):
|
||||
pp = pat2dev(
|
||||
layers=layers,
|
||||
library=library,
|
||||
pattern=library[target],
|
||||
name=target,
|
||||
max_depth=max_depth-1,
|
||||
skip_subcells=skip_subcells,
|
||||
blacklist=blacklist + {name},
|
||||
)
|
||||
found_ports |= bool(pp.ports)
|
||||
|
||||
if skip_subcells and any(parent in annotated_cells for parent in hierarchy):
|
||||
return pat
|
||||
for ref in pat.refs:
|
||||
aa = library.abstract(ref.target)
|
||||
if not aa.ports:
|
||||
continue
|
||||
|
||||
cell_name = hierarchy[-1]
|
||||
pat2dev_flat(pat, cell_name)
|
||||
aa.apply_ref_transform(ref)
|
||||
|
||||
if skip_subcells:
|
||||
annotated_cells.add(cell_name)
|
||||
|
||||
mirr_factor = numpy.array((1, -1)) ** transform[3]
|
||||
rot_matrix = rotation_matrix_2d(transform[2])
|
||||
for name, port in pat.ports.items():
|
||||
port.offset = transform[:2] + rot_matrix @ (port.offset * mirr_factor)
|
||||
port.rotation = port.rotation * mirr_factor[0] * mirr_factor[1] + transform[2]
|
||||
ports[name] = port
|
||||
|
||||
return pat
|
||||
|
||||
# update `ports`
|
||||
library.dfs(top=top, visit_before=find_ports_each, transform=True)
|
||||
|
||||
pattern = library[top]
|
||||
pattern.check_ports(other_names=ports.keys())
|
||||
pattern.ports.update(ports)
|
||||
pattern.check_ports(other_names=aa.ports.keys())
|
||||
pattern.ports.update(aa.ports)
|
||||
return pattern
|
||||
|
||||
|
||||
def pat2dev_flat(
|
||||
pattern: Pattern,
|
||||
layers: Sequence[layer_t],
|
||||
pattern: Pattern,
|
||||
cell_name: Optional[str] = None,
|
||||
) -> Pattern:
|
||||
"""
|
||||
|
|
@ -131,8 +134,8 @@ def pat2dev_flat(
|
|||
The pattern is assumed to be flat (have no `refs`) and have no pre-existing ports.
|
||||
|
||||
Args:
|
||||
pattern: Pattern object to scan for labels.
|
||||
layers: Search for labels on all the given layers.
|
||||
pattern: Pattern object to scan for labels.
|
||||
cell_name: optional, used for warning message only
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ from ..utils import rotation_matrix_2d
|
|||
from ..error import BuildError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..ports import Port, PortList
|
||||
from ..ports import Port
|
||||
|
||||
|
||||
def ell(
|
||||
ports: Union[Mapping[str, 'Port'], 'PortList'],
|
||||
ports: Mapping[str, 'Port'],
|
||||
ccw: Optional[bool],
|
||||
bound_type: str,
|
||||
bound: Union[float, ArrayLike],
|
||||
|
|
@ -83,9 +83,6 @@ def ell(
|
|||
if not ports:
|
||||
raise BuildError('Empty port list passed to `ell()`')
|
||||
|
||||
if isinstance(ports, PortList):
|
||||
ports = PortList.ports
|
||||
|
||||
if ccw is None:
|
||||
if spacing is not None and not numpy.isclose(spacing, 0):
|
||||
raise BuildError('Spacing must be 0 or None when ccw=None')
|
||||
|
|
|
|||
|
|
@ -6,11 +6,8 @@ Notes:
|
|||
* ezdxf sets creation time, write time, $VERSIONGUID, and $FINGERPRINTGUID
|
||||
to unique values, so byte-for-byte reproducibility is not achievable for now
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable, Mapping, TextIO
|
||||
import re
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Mapping, TextIO
|
||||
import io
|
||||
import base64
|
||||
import struct
|
||||
import logging
|
||||
import pathlib
|
||||
import gzip
|
||||
|
|
@ -60,7 +57,7 @@ def write(
|
|||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ Notes:
|
|||
* GDS creation/modification/access times are set to 1900-01-01 for reproducibility.
|
||||
* Gzip modification time is set to 0 (start of current epoch, usually 1970-01-01)
|
||||
"""
|
||||
from typing import List, Any, Dict, Tuple, Callable, Union, Iterable
|
||||
from typing import BinaryIO, Mapping, cast
|
||||
from typing import List, Dict, Tuple, Callable, Union, Iterable, Mapping
|
||||
from typing import BinaryIO, cast, Optional, Any
|
||||
import io
|
||||
import mmap
|
||||
import logging
|
||||
|
|
@ -39,7 +39,7 @@ from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
|
|||
from ..shapes import Polygon, Path
|
||||
from ..repetition import Grid
|
||||
from ..utils import layer_t, normalize_mirror, annotations_t
|
||||
from ..library import LazyLibrary, WrapLibrary, MutableLibrary
|
||||
from ..library import LazyLibrary, WrapLibrary, MutableLibrary, Library
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -84,7 +84,7 @@ def write(
|
|||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.gdsii.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon` or `masque.shapes.Path`
|
||||
|
||||
|
|
@ -188,6 +188,7 @@ def read(
|
|||
raw_mode: bool = True,
|
||||
) -> Tuple[Dict[str, Pattern], Dict[str, Any]]:
|
||||
"""
|
||||
# TODO check GDSII file for cycles!
|
||||
Read a gdsii file and translate it into a dict of Pattern objects. GDSII structures are
|
||||
translated into Pattern objects; boundaries are translated into polygons, and srefs and arefs
|
||||
are translated into Ref objects.
|
||||
|
|
@ -511,6 +512,7 @@ def load_library(
|
|||
stream: BinaryIO,
|
||||
*,
|
||||
full_load: bool = False,
|
||||
postprocess: Optional[Callable[[Library, str, Pattern], Pattern]] = None
|
||||
) -> Tuple[LazyLibrary, Dict[str, Any]]:
|
||||
"""
|
||||
Scan a GDSII stream to determine what structures are present, and create
|
||||
|
|
@ -526,6 +528,8 @@ def load_library(
|
|||
full_load: If True, force all structures to be read immediately rather
|
||||
than as-needed. Since data is read sequentially from the file, this
|
||||
will be faster than using the resulting library's `precache` method.
|
||||
postprocess: If given, this function is used to post-process each
|
||||
pattern *upon first load only*.
|
||||
|
||||
Returns:
|
||||
LazyLibrary object, allowing for deferred load of structures.
|
||||
|
|
@ -548,9 +552,14 @@ def load_library(
|
|||
for name_bytes, pos in structs.items():
|
||||
name = name_bytes.decode('ASCII')
|
||||
|
||||
def mkstruct(pos: int = pos) -> Pattern:
|
||||
def mkstruct(pos: int = pos, name: str = name) -> Pattern:
|
||||
logger.error(f'mkstruct {name} @ {pos:x}')
|
||||
stream.seek(pos)
|
||||
return read_elements(stream, raw_mode=True)
|
||||
pat = read_elements(stream, raw_mode=True)
|
||||
if postprocess is not None:
|
||||
pat = postprocess(lib, name, pat)
|
||||
logger.error(f'mkstruct post {name} @ {pos:x}')
|
||||
return pat
|
||||
|
||||
lib[name] = mkstruct
|
||||
|
||||
|
|
@ -562,6 +571,7 @@ def load_libraryfile(
|
|||
*,
|
||||
use_mmap: bool = True,
|
||||
full_load: bool = False,
|
||||
postprocess: Optional[Callable[[Library, str], Pattern]] = None
|
||||
) -> Tuple[LazyLibrary, Dict[str, Any]]:
|
||||
"""
|
||||
Wrapper for `load_library()` that takes a filename or path instead of a stream.
|
||||
|
|
@ -578,6 +588,7 @@ def load_libraryfile(
|
|||
is decompressed into a python `bytes` object in memory
|
||||
and reopened as an `io.BytesIO` stream.
|
||||
full_load: If `True`, immediately loads all data. See `load_library`.
|
||||
postprocess: Passed to `load_library`
|
||||
|
||||
Returns:
|
||||
LazyLibrary object, allowing for deferred load of structures.
|
||||
|
|
@ -599,7 +610,7 @@ def load_libraryfile(
|
|||
stream = mmap.mmap(base_stream.fileno(), 0, access=mmap.ACCESS_READ) # type: ignore
|
||||
else:
|
||||
stream = open(path, mode='rb')
|
||||
return load_library(stream, full_load=full_load)
|
||||
return load_library(stream, full_load=full_load, postprocess=postprocess)
|
||||
|
||||
|
||||
def check_valid_names(
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ def build(
|
|||
|
||||
Other functions you may want to call:
|
||||
- `masque.file.oasis.check_valid_names(library.keys())` to check for invalid names
|
||||
- `library.dangling_references()` to check for references to missing patterns
|
||||
- `library.dangling_refs()` to check for references to missing patterns
|
||||
- `pattern.polygonize()` for any patterns with shapes other
|
||||
than `masque.shapes.Polygon`, `masque.shapes.Path`, or `masque.shapes.Circle`
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
def __repr__(self) -> str:
|
||||
return '<Library with keys\n' + pformat(list(self.keys())) + '>'
|
||||
|
||||
def dangling_references(
|
||||
def dangling_refs(
|
||||
self,
|
||||
tops: Union[None, str, Sequence[str]] = None,
|
||||
) -> Set[Optional[str]]:
|
||||
|
|
@ -304,11 +304,11 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
|
||||
def dfs(
|
||||
self: L,
|
||||
top: str,
|
||||
pattern: 'Pattern',
|
||||
visit_before: Optional[visitor_function_t] = None,
|
||||
visit_after: Optional[visitor_function_t] = None,
|
||||
*,
|
||||
hierarchy: Tuple[str, ...] = (),
|
||||
hierarchy: Tuple[Optional[str], ...] = (None,),
|
||||
transform: Union[ArrayLike, bool, None] = False,
|
||||
memo: Optional[Dict] = None,
|
||||
) -> L:
|
||||
|
|
@ -317,23 +317,23 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
Performs a depth-first traversal of a pattern and its referenced patterns.
|
||||
At each pattern in the tree, the following sequence is called:
|
||||
```
|
||||
hierarchy += (top,)
|
||||
current_pattern = visit_before(current_pattern, **vist_args)
|
||||
for sp in current_pattern.refs]
|
||||
self.dfs(sp.target, visit_before, visit_after,
|
||||
hierarchy, updated_transform, memo)
|
||||
hierarchy + (sp.target,), updated_transform, memo)
|
||||
current_pattern = visit_after(current_pattern, **visit_args)
|
||||
```
|
||||
where `visit_args` are
|
||||
`hierarchy`: (top_pattern, L1_pattern, L2_pattern, ..., parent_pattern, current_pattern)
|
||||
tuple of all parent-and-higher pattern names
|
||||
`hierarchy`: (top_pattern_or_None, L1_pattern, L2_pattern, ..., parent_pattern)
|
||||
tuple of all parent-and-higher pattern names. Top pattern name may be
|
||||
`None` if not provided in first call to .dfs()
|
||||
`transform`: numpy.ndarray containing cumulative
|
||||
[x_offset, y_offset, rotation (rad), mirror_x (0 or 1)]
|
||||
for the instance being visited
|
||||
`memo`: Arbitrary dict (not altered except by `visit_before()` and `visit_after()`)
|
||||
|
||||
Args:
|
||||
top: Name of the pattern to start at (root node of the tree).
|
||||
pattern: Pattern object to start at ("top"/root node of the tree).
|
||||
visit_before: Function to call before traversing refs.
|
||||
Should accept a `Pattern` and `**visit_args`, and return the (possibly modified)
|
||||
pattern. Default `None` (not called).
|
||||
|
|
@ -345,8 +345,8 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
`True` or `None` is interpreted as `[0, 0, 0, 0]`.
|
||||
memo: Arbitrary dict for use by `visit_*()` functions. Default `None` (empty dict).
|
||||
hierarchy: Tuple of patterns specifying the hierarchy above the current pattern.
|
||||
Appended to the start of the generated `visit_args['hierarchy']`.
|
||||
Default is an empty tuple.
|
||||
Default is (None,), which will be used as a placeholder for the top pattern's
|
||||
name if not overridden.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
|
@ -359,16 +359,12 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
elif transform is not False:
|
||||
transform = numpy.array(transform)
|
||||
|
||||
if top in hierarchy:
|
||||
raise LibraryError('.dfs() called on pattern with circular reference')
|
||||
original_pattern = pattern
|
||||
|
||||
hierarchy += (top,)
|
||||
|
||||
pat = self[top]
|
||||
if visit_before is not None:
|
||||
pat = visit_before(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pattern = visit_before(pattern, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
|
||||
for ref in pat.refs:
|
||||
for ref in pattern.refs:
|
||||
if transform is not False:
|
||||
sign = numpy.ones(2)
|
||||
if transform[3]:
|
||||
|
|
@ -376,32 +372,38 @@ class Library(Mapping[str, 'Pattern'], metaclass=ABCMeta):
|
|||
xy = numpy.dot(rotation_matrix_2d(transform[2]), ref.offset * sign)
|
||||
mirror_x, angle = normalize_mirror(ref.mirrored)
|
||||
angle += ref.rotation
|
||||
sp_transform = transform + (xy[0], xy[1], angle, mirror_x)
|
||||
sp_transform[3] %= 2
|
||||
ref_transform = transform + (xy[0], xy[1], angle, mirror_x)
|
||||
ref_transform[3] %= 2
|
||||
else:
|
||||
sp_transform = False
|
||||
ref_transform = False
|
||||
|
||||
if ref.target is None:
|
||||
continue
|
||||
if ref.target in hierarchy:
|
||||
raise LibraryError(f'.dfs() called on pattern with circular reference to "{ref.target}"')
|
||||
|
||||
self.dfs(
|
||||
top=ref.target,
|
||||
pattern=self[ref.target],
|
||||
visit_before=visit_before,
|
||||
visit_after=visit_after,
|
||||
transform=sp_transform,
|
||||
hierarchy=hierarchy + (ref.target,),
|
||||
transform=ref_transform,
|
||||
memo=memo,
|
||||
hierarchy=hierarchy,
|
||||
)
|
||||
|
||||
if visit_after is not None:
|
||||
pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
pattern = visit_after(pattern, hierarchy=hierarchy, memo=memo, transform=transform)
|
||||
|
||||
if self[top] is not pat:
|
||||
if isinstance(self, MutableLibrary):
|
||||
self._set(top, pat)
|
||||
else:
|
||||
if pattern is not original_pattern:
|
||||
name = hierarchy[-1]
|
||||
if not isintance(self, MutableLibrary):
|
||||
raise LibraryError('visit_* functions returned a new `Pattern` object'
|
||||
' but the library is immutable')
|
||||
if name is None:
|
||||
raise LibraryError('visit_* functions returned a new `Pattern` object'
|
||||
' but no top-level name was provided in `hierarchy`')
|
||||
|
||||
self.set_const(name, pattern)
|
||||
|
||||
return self
|
||||
|
||||
|
|
@ -424,7 +426,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -564,7 +566,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
|||
del pat.shapes[i]
|
||||
|
||||
for ll, pp in shape_pats.items():
|
||||
self._set(label2name(ll), pp)
|
||||
self.set_const(label2name(ll), pp)
|
||||
|
||||
return self
|
||||
|
||||
|
|
@ -599,7 +601,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
|||
continue
|
||||
|
||||
name = name_func(pat, shape)
|
||||
self._set(name, Pattern(shapes=[shape]))
|
||||
self.set_const(name, Pattern(shapes=[shape]))
|
||||
pat.ref(name, repetition=shape.repetition)
|
||||
shape.repetition = None
|
||||
pat.shapes = new_shapes
|
||||
|
|
@ -610,7 +612,7 @@ class MutableLibrary(Library, Generic[VVV], metaclass=ABCMeta):
|
|||
new_labels.append(label)
|
||||
continue
|
||||
name = name_func(pat, label)
|
||||
self._set(name, Pattern(labels=[label]))
|
||||
self.set_const(name, Pattern(labels=[label]))
|
||||
pat.ref(name, repetition=label.repetition)
|
||||
label.repetition = None
|
||||
pat.labels = new_labels
|
||||
|
|
@ -692,7 +694,7 @@ class WrapLibrary(MutableLibrary):
|
|||
def __delitem__(self, key: str) -> None:
|
||||
del self.mapping[key]
|
||||
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
self[key] = value
|
||||
|
||||
def _merge(self, other: Mapping[str, 'Pattern'], key: str) -> None:
|
||||
|
|
@ -744,7 +746,7 @@ class LazyLibrary(MutableLibrary):
|
|||
def __len__(self) -> int:
|
||||
return len(self.dict)
|
||||
|
||||
def _set(self, key: str, value: 'Pattern') -> None:
|
||||
def set_const(self, key: str, value: 'Pattern') -> None:
|
||||
self[key] = lambda: value
|
||||
|
||||
def _merge(self, other: Mapping[str, 'Pattern'], key: str) -> None:
|
||||
|
|
@ -753,7 +755,7 @@ class LazyLibrary(MutableLibrary):
|
|||
if key in other.cache:
|
||||
self.cache[key] = other.cache[key]
|
||||
else:
|
||||
self._set(key, other[key])
|
||||
self.set_const(key, other[key])
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<LazyLibrary with keys\n' + pformat(list(self.keys())) + '>'
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
(via Ref). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
||||
"""
|
||||
__slots__ = (
|
||||
'shapes', 'labels', 'refs', 'ports',
|
||||
'shapes', 'labels', 'refs', '_ports',
|
||||
# inherited
|
||||
'_offset', '_annotations'
|
||||
'_offset', '_annotations',
|
||||
)
|
||||
|
||||
shapes: List[Shape]
|
||||
|
|
@ -49,9 +49,17 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
(i.e. multiple instances of the same object).
|
||||
"""
|
||||
|
||||
ports: Dict[str, Port]
|
||||
_ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to snap to other Pattern instances"""
|
||||
|
||||
@property
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
return self._ports
|
||||
|
||||
@ports.setter
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
self._ports = value
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
|
|
@ -331,7 +339,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
|
||||
def translate_elements(self: P, offset: ArrayLike) -> P:
|
||||
"""
|
||||
Translates all shapes, label, and refs by the given offset.
|
||||
Translates all shapes, label, refs, and ports by the given offset.
|
||||
|
||||
Args:
|
||||
offset: (x, y) to translate by
|
||||
|
|
@ -339,7 +347,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
cast(Positionable, entry).translate(offset)
|
||||
return self
|
||||
|
||||
|
|
@ -360,7 +368,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
def scale_by(self: P, c: float) -> P:
|
||||
"""
|
||||
Scale this Pattern by the given value
|
||||
(all shapes and refs and their offsets are scaled)
|
||||
(all shapes and refs and their offsets are scaled,
|
||||
as are all label and port offsets)
|
||||
|
||||
Args:
|
||||
c: factor to scale by
|
||||
|
|
@ -407,7 +416,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
|
||||
def rotate_element_centers(self: P, rotation: float) -> P:
|
||||
"""
|
||||
Rotate the offsets of all shapes, labels, and refs around (0, 0)
|
||||
Rotate the offsets of all shapes, labels, refs, and ports around (0, 0)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
|
@ -415,14 +424,14 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
old_offset = cast(Positionable, entry).offset
|
||||
cast(Positionable, entry).offset = numpy.dot(rotation_matrix_2d(rotation), old_offset)
|
||||
return self
|
||||
|
||||
def rotate_elements(self: P, rotation: float) -> P:
|
||||
"""
|
||||
Rotate each shape and refs around its center (offset)
|
||||
Rotate each shape, ref, and port around its origin (offset)
|
||||
|
||||
Args:
|
||||
rotation: Angle to rotate by (counter-clockwise, radians)
|
||||
|
|
@ -430,54 +439,54 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable, metaclass=AutoSlots):
|
|||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs):
|
||||
for entry in chain(self.shapes, self.refs, self.ports.values()):
|
||||
cast(Rotatable, entry).rotate(rotation)
|
||||
return self
|
||||
|
||||
def mirror_element_centers(self: P, axis: int) -> P:
|
||||
def mirror_element_centers(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports):
|
||||
cast(Positionable, entry).offset[axis - 1] *= -1
|
||||
for entry in chain(self.shapes, self.refs, self.labels, self.ports.values()):
|
||||
cast(Positionable, entry).offset[across_axis - 1] *= -1
|
||||
return self
|
||||
|
||||
def mirror_elements(self: P, axis: int) -> P:
|
||||
def mirror_elements(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror each shape and refs across an axis, relative to its
|
||||
offset
|
||||
Mirror each shape, ref, and pattern across an axis, relative
|
||||
to its offset
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
for entry in chain(self.shapes, self.refs):
|
||||
cast(Mirrorable, entry).mirror(axis)
|
||||
for entry in chain(self.shapes, self.refs, self.ports.values()):
|
||||
cast(Mirrorable, entry).mirror(across_axis)
|
||||
return self
|
||||
|
||||
def mirror(self: P, axis: int) -> P:
|
||||
def mirror(self: P, across_axis: int) -> P:
|
||||
"""
|
||||
Mirror the Pattern across an axis
|
||||
|
||||
Args:
|
||||
axis: Axis to mirror across
|
||||
across_axis: Axis to mirror across
|
||||
(0: mirror across x axis, 1: mirror across y axis)
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
self.mirror_elements(axis)
|
||||
self.mirror_element_centers(axis)
|
||||
self.mirror_elements(across_axis)
|
||||
self.mirror_element_centers(across_axis)
|
||||
return self
|
||||
|
||||
def copy(self: P) -> P:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import warnings
|
|||
import traceback
|
||||
import logging
|
||||
from collections import Counter
|
||||
from abc import ABCMeta
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy
|
||||
from numpy import pi
|
||||
|
|
@ -103,10 +103,18 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, met
|
|||
|
||||
|
||||
class PortList(metaclass=ABCMeta):
|
||||
__slots__ = () # For use with AutoSlots
|
||||
__slots__ = () # Allow subclasses to use __slots__
|
||||
|
||||
ports: Dict[str, Port]
|
||||
""" Uniquely-named ports which can be used to snap to other Device instances"""
|
||||
@property
|
||||
@abstractmethod
|
||||
def ports(self) -> Dict[str, Port]:
|
||||
""" Uniquely-named ports which can be used to snap to other Device instances"""
|
||||
pass
|
||||
|
||||
@ports.setter
|
||||
@abstractmethod
|
||||
def ports(self, value: Dict[str, Port]) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def __getitem__(self, key: str) -> Port:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue