pather fixes / type updates

This commit is contained in:
jan 2023-04-11 19:57:09 -07:00
parent f22e737e60
commit e5de33e1f1
4 changed files with 199 additions and 33 deletions

View File

@ -1,4 +1,4 @@
from typing import Self, Sequence, Mapping
from typing import Self, Sequence, Mapping, Literal, overload, Final, cast
import copy
import logging
@ -102,12 +102,6 @@ class Builder(PortList):
) -> None:
"""
# 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._dead = False
self.library = library
@ -165,8 +159,7 @@ class Builder(PortList):
Args:
source: A collection of ports (e.g. Pattern, Builder, or dict)
from which to create the interface.
library: Used for buildin functions; if not passed and the source
library: Library from which existing patterns should be referenced,
library: Library from which existing patterns should be referenced, TODO
and to which new ones should be added. If not provided,
the source's library will be used (if available).
in_prefix: Prepended to port names for newly-created ports with
@ -230,18 +223,47 @@ class Builder(PortList):
new = Builder(library=library, ports={**ports_in, **ports_out}, name=name)
return new
# @overload
# def plug(
# self,
# other: Abstract | str,
# map_in: dict[str, str],
# map_out: dict[str, str | None] | None,
# *,
# mirrored: tuple[bool, bool],
# inherit_name: bool,
# set_rotation: bool | None,
# append: bool,
# ) -> Self:
# pass
#
# @overload
# def plug(
# self,
# other: Pattern,
# map_in: dict[str, str],
# map_out: dict[str, str | None] | None = None,
# *,
# mirrored: tuple[bool, bool] = (False, False),
# inherit_name: bool = True,
# set_rotation: bool | None = None,
# append: bool = False,
# ) -> Self:
# pass
def plug(
self,
other: Abstract | str,
other: Abstract | str | Pattern,
map_in: dict[str, str],
map_out: dict[str, str | None] | None = None,
*,
mirrored: tuple[bool, bool] = (False, False),
inherit_name: bool = True,
set_rotation: bool | None = None,
append: bool = False,
) -> Self:
"""
Instantiate a device `library[name]` into the current device, connecting
Instantiate or append a pattern into the current device, connecting
the ports specified by `map_in` and renaming the unconnected
ports specified by `map_out`.
@ -327,23 +349,59 @@ class Builder(PortList):
del self.ports[ki]
map_out[vi] = None
if isinstance(other, Pattern):
assert append
self.place(other, offset=translation, rotation=rotation, pivot=pivot,
mirrored=mirrored, port_map=map_out, skip_port_check=True)
mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append)
else:
self.place(other, offset=translation, rotation=rotation, pivot=pivot,
mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append)
return self
@overload
def place(
self,
other: Abstract | str,
*,
offset: ArrayLike,
rotation: float,
pivot: ArrayLike,
mirrored: tuple[bool, bool],
port_map: dict[str, str | None] | None,
skip_port_check: bool,
append: bool,
) -> Self:
pass
@overload
def place(
self,
other: Pattern,
*,
offset: ArrayLike,
rotation: float,
pivot: ArrayLike,
mirrored: tuple[bool, bool],
port_map: dict[str, str | None] | None,
skip_port_check: bool,
append: Literal[True],
) -> Self:
pass
def place(
self,
other: Abstract | str | Pattern,
*,
offset: ArrayLike = (0, 0),
rotation: float = 0,
pivot: ArrayLike = (0, 0),
mirrored: tuple[bool, bool] = (False, False),
port_map: dict[str, str | None] | None = None,
skip_port_check: bool = False,
append: bool = False,
) -> Self:
"""
Instantiate the device `other` into the current device, adding its
Instantiate or append the device `other` into the current device, adding its
ports to those of the current device (but not connecting any ports).
Mirroring is applied before rotation; translation (`offset`) is applied last.
@ -375,7 +433,7 @@ class Builder(PortList):
Raises:
`PortError` if any ports specified in `map_in` or `map_out` do not
exist in `self.ports` or `library[name].ports`.
exist in `self.ports` or `other.ports`.
`PortError` if there are any duplicate names after `map_in` and `map_out`
are applied.
"""
@ -408,10 +466,26 @@ class Builder(PortList):
p.translate(offset)
self.ports[name] = p
sp = Ref(other.name, mirrored=mirrored)
sp.rotate_around(pivot, rotation)
sp.translate(offset)
self.pattern.refs.append(sp)
if append:
if isinstance(other, Pattern):
other_pat = other
elif isinstance(other, Abstract):
assert self.library is not None
other_pat = self.library[other.name]
else:
other_pat = self.library[name]
other_copy = other_pat.deepcopy()
other_copy.ports.clear()
other_copy.mirror2d(mirrored)
other_copy.rotate_around(pivot, rotation)
other_copy.translate_elements(offset)
self.pattern.append(other_copy)
else:
assert not isinstance(other, Pattern)
ref = Ref(other.name, mirrored=mirrored)
ref.rotate_around(pivot, rotation)
ref.translate(offset)
self.pattern.refs.append(ref)
return self
def translate(self, offset: ArrayLike) -> Self:

View File

@ -53,6 +53,7 @@ class FlatBuilder(PortList):
"""
# TODO documentation for FlatBuilder() constructor
"""
self._dead = False
if pattern is not None:
self.pattern = pattern
else:
@ -70,8 +71,6 @@ class FlatBuilder(PortList):
else:
self.tools = dict(tools)
self._dead = False
@classmethod
def interface(
cls,
@ -105,7 +104,6 @@ class FlatBuilder(PortList):
Args:
source: A collection of ports (e.g. Pattern, Builder, or dict)
from which to create the interface.
library: Used for buildin functions; if not passed and the source TODO
library: Library from which existing patterns should be referenced, TODO
and to which new ones should be added. If not provided,
the source's library will be used (if available).
@ -429,7 +427,7 @@ class FlatBuilder(PortList):
ccw: SupportsBool | None,
length: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
base_name: str = '_path',
**kwargs,
) -> Self:
@ -448,7 +446,7 @@ class FlatBuilder(PortList):
ccw: SupportsBool | None,
position: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
base_name: str = '_pathto',
**kwargs,
) -> Self:
@ -483,7 +481,7 @@ class FlatBuilder(PortList):
*,
spacing: float | ArrayLike | None = None,
set_rotation: float | None = None,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
force_container: bool = False,
base_name: str = '_mpath',
**kwargs,

View File

@ -218,7 +218,7 @@ class Pather(Builder):
ccw: SupportsBool | None,
length: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
base_name: str = '_path',
**kwargs,
) -> Self:
@ -239,7 +239,7 @@ class Pather(Builder):
ccw: SupportsBool | None,
position: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
base_name: str = '_pathto',
**kwargs,
) -> Self:
@ -274,7 +274,7 @@ class Pather(Builder):
*,
spacing: float | ArrayLike | None = None,
set_rotation: float | None = None,
tool_port_names: Sequence[str] = ('A', 'B'),
tool_port_names: tuple[str, str] = ('A', 'B'),
force_container: bool = False,
base_name: str = '_mpath',
**kwargs,

View File

@ -1,12 +1,18 @@
"""
Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides)
"""
from typing import TYPE_CHECKING, Sequence, Literal, Tuple
from typing import TYPE_CHECKING, Sequence, Literal, Callable
from abc import ABCMeta, abstractmethod
from ..utils import SupportsBool
import numpy
from ..utils import SupportsBool, rotation_matrix_2d
from ..ports import Port
from ..pattern import Pattern
from ..library import ILibrary
from ..abstract import Abstract
from ..library import ILibrary, Library
from ..error import BuildError
from .builder import Builder
render_step_t = (
@ -22,7 +28,7 @@ class Tool:
*,
in_ptype: str | None = None,
out_ptype: str | None = None,
port_names: Sequence[str] = ('A', 'B'),
port_names: tuple[str, str] = ('A', 'B'),
**kwargs,
) -> Pattern:
raise NotImplementedError(f'path() not implemented for {type(self)}')
@ -35,7 +41,7 @@ class Tool:
in_ptype: str | None = None,
out_ptype: str | None = None,
**kwargs,
) -> Tuple[float, str]:
) -> tuple[float, str]:
raise NotImplementedError(f'planL() not implemented for {type(self)}')
def planS(
@ -62,3 +68,91 @@ class Tool:
assert batch[0][-1] == self
raise NotImplementedError(f'render() not implemented for {type(self)}')
class BasicTool(Tool, metaclass=ABCMeta):
straight: tuple[Callable[[float], Pattern], str, str]
bend: tuple[Abstract, str, str] # Assumed to be clockwise
transitions: dict[str, tuple[Abstract, str, str]]
def path(
self,
ccw: SupportsBool | None,
length: float,
*,
in_ptype: str | None = None,
out_ptype: str | None = None,
port_names: tuple[str, str] = ('A', 'B'),
**kwargs,
) -> Pattern:
# TODO check all the math for L-shaped bends
straight_length = length
bend_run = 0
if ccw is not None:
bend, bport_in, bport_out = self.bend
brot = bend.ports[bport_in].rotation
assert brot is not None
bend_dxy = numpy.abs(
rotation_matrix_2d(-brot) @ (
bend.ports[bport_out].offset
- bend.ports[bport_in].offset
)
)
straight_length -= bend_dxy[0]
bend_run += bend_dxy[1]
else:
bend_dxy = numpy.zeros(2)
in_transition = self.transitions.get('unk' if in_ptype is None else in_ptype, None)
if in_transition is not None:
ipat, iport_theirs, iport_ours = in_transition
irot = ipat.ports[iport_theirs].rotation
assert irot is not None
itrans_dxy = rotation_matrix_2d(-irot) @ (
ipat.ports[iport_ours].offset
- ipat.ports[iport_theirs].offset
)
straight_length -= itrans_dxy[0]
bend_run += itrans_dxy[1]
else:
itrans_dxy = numpy.zeros(2)
out_transition = self.transitions.get('unk' if out_ptype is None else out_ptype, None)
if out_transition is not None:
opat, oport_theirs, oport_ours = out_transition
orot = opat.ports[oport_ours].rotation
assert orot is not None
otrans_dxy = rotation_matrix_2d(-orot) @ (
opat.ports[oport_theirs].offset
- opat.ports[oport_ours].offset
)
if ccw:
otrans_dxy[0] *= -1
straight_length -= otrans_dxy[1]
bend_run += otrans_dxy[0]
else:
otrans_dxy = numpy.zeros(2)
if straight_length < 0:
raise BuildError(f'Asked to draw path with total length {length:g}, shorter than required bends and tapers:\n'
f'bend: {bend_dxy[0]:g} in_taper: {abs(itrans_dxy[0])} out_taper: {otrans_dxy[1]}')
gen_straight, sport_in, sport_out = self.straight
tree = Library()
bb = Builder(library=tree, name='_path').add_port_pair(names=port_names)
if in_transition:
bb.plug(ipat, {port_names[1]: iport_theirs})
if not numpy.isclose(straight_length, 0):
straight = tree << {'_straight': gen_straight(straight_length)}
bb.plug(straight, {port_names[1]: sport_in})
if ccw is not None:
bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw)))
if out_transition:
bb.plug(opat, {port_names[1]: oport_ours})
return bb.pattern