[Pather/RenderPather/PortPather] Rework pathing verbs *BREAKING CHANGE*
This commit is contained in:
parent
16875e9cd6
commit
babbe78daa
5 changed files with 468 additions and 416 deletions
|
|
@ -255,7 +255,7 @@ class Pather(Builder, PatherMixin):
|
|||
return s
|
||||
|
||||
|
||||
def path(
|
||||
def _path(
|
||||
self,
|
||||
portspec: str,
|
||||
ccw: SupportsBool | None,
|
||||
|
|
@ -296,7 +296,7 @@ class Pather(Builder, PatherMixin):
|
|||
LibraryError if no valid name could be picked for the pattern.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.warning('Skipping geometry for path() since device is dead')
|
||||
logger.warning('Skipping geometry for _path() since device is dead')
|
||||
|
||||
tool_port_names = ('A', 'B')
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ class Pather(Builder, PatherMixin):
|
|||
self.plug(tname, {portspec: tool_port_names[0], **output})
|
||||
return self
|
||||
|
||||
def pathS(
|
||||
def _pathS(
|
||||
self,
|
||||
portspec: str,
|
||||
length: float,
|
||||
|
|
@ -346,20 +346,17 @@ class Pather(Builder, PatherMixin):
|
|||
) -> Self:
|
||||
"""
|
||||
Create an S-shaped "wire"/"waveguide" and `plug` it into the port `portspec`, with the aim
|
||||
of traveling exactly `length` distance with an offset `jog` along the other axis (+ve jog is
|
||||
left of direction of travel).
|
||||
of traveling exactly `length` distance.
|
||||
|
||||
The output port will have the same orientation as the source port (`portspec`).
|
||||
|
||||
This function attempts to use `tool.planS()`, but falls back to `tool.planL()` if the former
|
||||
raises a NotImplementedError.
|
||||
The wire will travel `length` distance along the port's axis, and exactly `jog`
|
||||
distance in the perpendicular direction. The output port will have an orientation
|
||||
identical to the input port.
|
||||
|
||||
Args:
|
||||
portspec: The name of the port into which the wire will be plugged.
|
||||
jog: Total manhattan distance perpendicular to the direction of travel.
|
||||
Positive values are to the left of the direction of travel.
|
||||
length: The total manhattan distance from input to output, along the input's axis only.
|
||||
(There may be a tool-dependent offset along the other axis.)
|
||||
length: The total distance from input to output, along the input's axis only.
|
||||
jog: Total distance perpendicular to the direction of travel. Positive values
|
||||
are to the left of the direction of travel.
|
||||
plug_into: If not None, attempts to plug the wire's output port into the provided
|
||||
port on `self`.
|
||||
|
||||
|
|
@ -377,7 +374,7 @@ class Pather(Builder, PatherMixin):
|
|||
LibraryError if no valid name could be picked for the pattern.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.warning('Skipping geometry for pathS() since device is dead')
|
||||
logger.warning('Skipping geometry for _pathS() since device is dead')
|
||||
|
||||
tool_port_names = ('A', 'B')
|
||||
|
||||
|
|
@ -398,8 +395,8 @@ class Pather(Builder, PatherMixin):
|
|||
(_, jog1), _ = t_pat1[tool_port_names[0]].measure_travel(t_pat1[tool_port_names[1]])
|
||||
|
||||
kwargs_plug = kwargs | {'plug_into': plug_into}
|
||||
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
|
||||
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
|
||||
self._path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
|
||||
self._path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
|
||||
except (BuildError, NotImplementedError):
|
||||
if not self._dead:
|
||||
raise
|
||||
|
|
@ -433,4 +430,3 @@ class Pather(Builder, PatherMixin):
|
|||
output = {}
|
||||
self.plug(tname, {portspec: tool_port_names[0], **output})
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from typing import Self, overload
|
||||
from collections.abc import Sequence, Iterator, Iterable
|
||||
from collections.abc import Sequence, Iterator, Iterable, Mapping
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from abc import abstractmethod, ABCMeta
|
||||
|
|
@ -37,8 +37,323 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
(e.g wires or waveguides) to be plugged into this device.
|
||||
"""
|
||||
|
||||
def trace(
|
||||
self,
|
||||
portspec: str | Sequence[str],
|
||||
ccw: SupportsBool | None,
|
||||
length: float | None = None,
|
||||
*,
|
||||
spacing: float | ArrayLike | None = None,
|
||||
**bounds,
|
||||
) -> Self:
|
||||
"""
|
||||
Create a "wire"/"waveguide" extending from the port(s) `portspec`.
|
||||
|
||||
Args:
|
||||
portspec: The name(s) of the port(s) into which the wire(s) will be plugged.
|
||||
ccw: If `None`, the output should be along the same axis as the input.
|
||||
Otherwise, cast to bool and turn counterclockwise if True
|
||||
and clockwise otherwise.
|
||||
length: The total distance from input to output, along the input's axis only.
|
||||
Length is only allowed with a single port.
|
||||
spacing: Center-to-center distance between output ports along the input port's axis.
|
||||
Only used when routing multiple ports with a bend.
|
||||
bounds: Boundary constraints for the trace.
|
||||
- each: results in each port being extended by `each` distance.
|
||||
- emin, emax, pmin, pmax, xmin, xmax, ymin, ymax: bundle routing via `ell()`.
|
||||
- set_rotation: explicit rotation for ports without one.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
if isinstance(portspec, str):
|
||||
portspec = [portspec]
|
||||
|
||||
if length is not None:
|
||||
if len(portspec) > 1:
|
||||
raise BuildError('length is only allowed with a single port in trace()')
|
||||
if bounds:
|
||||
raise BuildError('length and bounds are mutually exclusive in trace()')
|
||||
return self._path(portspec[0], ccw, length)
|
||||
|
||||
if 'each' in bounds:
|
||||
each = bounds.pop('each')
|
||||
if bounds:
|
||||
raise BuildError('each and other bounds are mutually exclusive in trace()')
|
||||
for port in portspec:
|
||||
self._path(port, ccw, each)
|
||||
return self
|
||||
|
||||
# Bundle routing (formerly mpath logic)
|
||||
bound_types = set()
|
||||
if 'bound_type' in bounds:
|
||||
bound_types.add(bounds.pop('bound_type'))
|
||||
bound = bounds.pop('bound')
|
||||
for bt in ('emin', 'emax', 'pmin', 'pmax', 'xmin', 'xmax', 'ymin', 'ymax', 'min_past_furthest'):
|
||||
if bt in bounds:
|
||||
bound_types.add(bt)
|
||||
bound = bounds.pop(bt)
|
||||
|
||||
if not bound_types:
|
||||
raise BuildError('No bound type specified for trace()')
|
||||
if len(bound_types) > 1:
|
||||
raise BuildError(f'Too many bound types specified: {bound_types}')
|
||||
bound_type = tuple(bound_types)[0]
|
||||
|
||||
ports = self.pattern[tuple(portspec)]
|
||||
set_rotation = bounds.pop('set_rotation', None)
|
||||
|
||||
extensions = ell(ports, ccw, spacing=spacing, bound=bound, bound_type=bound_type, set_rotation=set_rotation)
|
||||
|
||||
for port_name, ext_len in extensions.items():
|
||||
self._path(port_name, ccw, ext_len, **bounds)
|
||||
return self
|
||||
|
||||
def trace_to(
|
||||
self,
|
||||
portspec: str | Sequence[str],
|
||||
ccw: SupportsBool | None,
|
||||
*,
|
||||
spacing: float | ArrayLike | None = None,
|
||||
**bounds,
|
||||
) -> Self:
|
||||
"""
|
||||
Create a "wire"/"waveguide" extending from the port(s) `portspec` to a target position.
|
||||
|
||||
Args:
|
||||
portspec: The name(s) of the port(s) into which the wire(s) will be plugged.
|
||||
ccw: If `None`, the output should be along the same axis as the input.
|
||||
Otherwise, cast to bool and turn counterclockwise if True
|
||||
and clockwise otherwise.
|
||||
spacing: Center-to-center distance between output ports along the input port's axis.
|
||||
Only used when routing multiple ports with a bend.
|
||||
bounds: Boundary constraints for the target position.
|
||||
- p, x, y, pos, position: Coordinate of the target position. Error if used with multiple ports.
|
||||
- pmin, pmax, xmin, xmax, ymin, ymax, emin, emax: bundle routing via `ell()`.
|
||||
|
||||
Returns:
|
||||
self
|
||||
"""
|
||||
if isinstance(portspec, str):
|
||||
portspec = [portspec]
|
||||
|
||||
pos_bounds = {kk: bounds[kk] for kk in ('p', 'x', 'y', 'pos', 'position') if kk in bounds}
|
||||
if pos_bounds:
|
||||
if len(portspec) > 1:
|
||||
raise BuildError(f'{tuple(pos_bounds.keys())} bounds are only allowed with a single port in trace_to()')
|
||||
if len(pos_bounds) > 1:
|
||||
raise BuildError(f'Too many position bounds: {tuple(pos_bounds.keys())}')
|
||||
|
||||
k, v = next(iter(pos_bounds.items()))
|
||||
k = 'position' if k in ('p', 'pos') else k
|
||||
|
||||
# Logic hoisted from path_to()
|
||||
port_name = portspec[0]
|
||||
port = self.pattern[port_name]
|
||||
if port.rotation is None:
|
||||
raise PortError(f'Port {port_name} has no rotation and cannot be used for trace_to()')
|
||||
|
||||
if not numpy.isclose(port.rotation % (pi / 2), 0):
|
||||
raise BuildError('trace_to was asked to route from non-manhattan port')
|
||||
|
||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||
if is_horizontal:
|
||||
if k == 'y':
|
||||
raise BuildError('Asked to trace to y-coordinate, but port is horizontal')
|
||||
target = v
|
||||
else:
|
||||
if k == 'x':
|
||||
raise BuildError('Asked to trace to x-coordinate, but port is vertical')
|
||||
target = v
|
||||
|
||||
x0, y0 = port.offset
|
||||
if is_horizontal:
|
||||
if numpy.sign(numpy.cos(port.rotation)) == numpy.sign(target - x0):
|
||||
raise BuildError(f'trace_to routing to behind source port: x0={x0:g} to {target:g}')
|
||||
length = numpy.abs(target - x0)
|
||||
else:
|
||||
if numpy.sign(numpy.sin(port.rotation)) == numpy.sign(target - y0):
|
||||
raise BuildError(f'trace_to routing to behind source port: y0={y0:g} to {target:g}')
|
||||
length = numpy.abs(target - y0)
|
||||
|
||||
other_bounds = {bk: bv for bk, bv in bounds.items() if bk not in pos_bounds and bk != 'length'}
|
||||
if 'length' in bounds and bounds['length'] is not None:
|
||||
raise BuildError('Cannot specify both relative length and absolute position in trace_to()')
|
||||
|
||||
return self._path(port_name, ccw, length, **other_bounds)
|
||||
|
||||
# Bundle routing (delegate to trace which handles ell)
|
||||
return self.trace(portspec, ccw, spacing=spacing, **bounds)
|
||||
|
||||
def straight(self, portspec: str | Sequence[str], length: float | None = None, **bounds) -> Self:
|
||||
""" Straight extension. Replaces `path(ccw=None)` and `path_to(ccw=None)` """
|
||||
return self.trace_to(portspec, None, length=length, **bounds)
|
||||
|
||||
def bend(self, portspec: str | Sequence[str], ccw: SupportsBool, length: float | None = None, **bounds) -> Self:
|
||||
""" Bend extension. Replaces `path(ccw=True/False)` and `path_to(ccw=True/False)` """
|
||||
return self.trace_to(portspec, ccw, length=length, **bounds)
|
||||
|
||||
def ccw(self, portspec: str | Sequence[str], length: float | None = None, **bounds) -> Self:
|
||||
""" Counter-clockwise bend extension. """
|
||||
return self.bend(portspec, True, length, **bounds)
|
||||
|
||||
def cw(self, portspec: str | Sequence[str], length: float | None = None, **bounds) -> Self:
|
||||
""" Clockwise bend extension. """
|
||||
return self.bend(portspec, False, length, **bounds)
|
||||
|
||||
def jog(self, portspec: str | Sequence[str], offset: float, length: float | None = None, **bounds) -> Self:
|
||||
""" Jog extension. Replaces `pathS`. """
|
||||
if isinstance(portspec, str):
|
||||
portspec = [portspec]
|
||||
|
||||
for port in portspec:
|
||||
l_actual = length
|
||||
if l_actual is None:
|
||||
# TODO: use bounds to determine length?
|
||||
raise BuildError('jog() currently requires a length')
|
||||
self._pathS(port, l_actual, offset, **bounds)
|
||||
return self
|
||||
|
||||
def trace_into(
|
||||
self,
|
||||
portspec_src: str,
|
||||
portspec_dst: str,
|
||||
*,
|
||||
out_ptype: str | None = None,
|
||||
plug_destination: bool = True,
|
||||
thru: str | None = None,
|
||||
**kwargs,
|
||||
) -> Self:
|
||||
"""
|
||||
Create a "wire"/"waveguide" traveling between the ports `portspec_src` and
|
||||
`portspec_dst`, and `plug` it into both (or just the source port).
|
||||
|
||||
Only unambiguous scenarios are allowed:
|
||||
- Straight connector between facing ports
|
||||
- Single 90 degree bend
|
||||
- Jog between facing ports
|
||||
(jog is done as late as possible, i.e. only 2 L-shaped segments are used)
|
||||
|
||||
By default, the destination's `pytpe` will be used as the `out_ptype` for the
|
||||
wire, and the `portspec_dst` will be plugged (i.e. removed).
|
||||
|
||||
Args:
|
||||
portspec_src: The name of the starting port into which the wire will be plugged.
|
||||
portspec_dst: The name of the destination port.
|
||||
out_ptype: Passed to the pathing tool in order to specify the desired port type
|
||||
to be generated at the destination end. If `None` (default), the destination
|
||||
port's `ptype` will be used.
|
||||
thru: If not `None`, the port by this name will be renamed to `portspec_src`.
|
||||
This can be used when routing a signal through a pre-placed 2-port device.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
Raises:
|
||||
PortError if either port does not have a specified rotation.
|
||||
BuildError if an invalid port config is encountered:
|
||||
- Non-manhattan ports
|
||||
- U-bend
|
||||
- Destination too close to (or behind) source
|
||||
"""
|
||||
if self._dead:
|
||||
logger.error('Skipping trace_into() since device is dead')
|
||||
return self
|
||||
|
||||
port_src = self.pattern[portspec_src]
|
||||
port_dst = self.pattern[portspec_dst]
|
||||
|
||||
if out_ptype is None:
|
||||
out_ptype = port_dst.ptype
|
||||
|
||||
if port_src.rotation is None:
|
||||
raise PortError(f'Port {portspec_src} has no rotation and cannot be used for trace_into()')
|
||||
if port_dst.rotation is None:
|
||||
raise PortError(f'Port {portspec_dst} has no rotation and cannot be used for trace_into()')
|
||||
|
||||
if not numpy.isclose(port_src.rotation % (pi / 2), 0):
|
||||
raise BuildError('trace_into was asked to route from non-manhattan port')
|
||||
if not numpy.isclose(port_dst.rotation % (pi / 2), 0):
|
||||
raise BuildError('trace_into was asked to route to non-manhattan port')
|
||||
|
||||
src_is_horizontal = numpy.isclose(port_src.rotation % pi, 0)
|
||||
dst_is_horizontal = numpy.isclose(port_dst.rotation % pi, 0)
|
||||
xs, ys = port_src.offset
|
||||
xd, yd = port_dst.offset
|
||||
|
||||
angle = (port_dst.rotation - port_src.rotation) % (2 * pi)
|
||||
|
||||
dst_extra_args = {'out_ptype': out_ptype}
|
||||
if plug_destination:
|
||||
dst_extra_args['plug_into'] = portspec_dst
|
||||
|
||||
src_args = {**kwargs}
|
||||
dst_args = {**src_args, **dst_extra_args}
|
||||
if src_is_horizontal and not dst_is_horizontal:
|
||||
# single bend should suffice
|
||||
self.trace_to(portspec_src, angle > pi, x=xd, **src_args)
|
||||
self.trace_to(portspec_src, None, y=yd, **dst_args)
|
||||
elif dst_is_horizontal and not src_is_horizontal:
|
||||
# single bend should suffice
|
||||
self.trace_to(portspec_src, angle > pi, y=yd, **src_args)
|
||||
self.trace_to(portspec_src, None, x=xd, **dst_args)
|
||||
elif numpy.isclose(angle, pi):
|
||||
if src_is_horizontal and ys == yd:
|
||||
# straight connector
|
||||
self.trace_to(portspec_src, None, x=xd, **dst_args)
|
||||
elif not src_is_horizontal and xs == xd:
|
||||
# straight connector
|
||||
self.trace_to(portspec_src, None, y=yd, **dst_args)
|
||||
else:
|
||||
# S-bend
|
||||
(travel, jog), _ = port_src.measure_travel(port_dst)
|
||||
self.jog(portspec_src, -jog, -travel, **dst_args)
|
||||
elif numpy.isclose(angle, 0):
|
||||
raise BuildError("Don't know how to route a U-bend yet (TODO)!")
|
||||
else:
|
||||
raise BuildError(f"Don't know how to route ports with relative angle {angle}")
|
||||
|
||||
if thru is not None:
|
||||
self.rename_ports({thru: portspec_src})
|
||||
|
||||
return self
|
||||
|
||||
def _uturn_fallback(
|
||||
self,
|
||||
tool: Tool,
|
||||
portspec: str,
|
||||
jog: float,
|
||||
length: float,
|
||||
in_ptype: str | None,
|
||||
plug_into: str | None,
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""
|
||||
Attempt to perform a U-turn using two L-bends.
|
||||
Returns True if successful, False if planL failed.
|
||||
"""
|
||||
# Fall back to drawing two L-bends
|
||||
ccw = jog > 0
|
||||
kwargs_no_out = kwargs | {'out_ptype': None}
|
||||
try:
|
||||
# First, find R by planning a minimal L-bend.
|
||||
# Use a large length to ensure we don't hit tool-specific minimum length constraints.
|
||||
dummy_port, _ = tool.planL(ccw, 1e9, in_ptype=in_ptype, **kwargs_no_out)
|
||||
R = abs(dummy_port.y)
|
||||
|
||||
L1 = length + R
|
||||
L2 = abs(jog) - R
|
||||
|
||||
kwargs_plug = kwargs | {'plug_into': plug_into}
|
||||
self._path(portspec, ccw, L1, **kwargs_no_out)
|
||||
self._path(portspec, ccw, L2, **kwargs_plug)
|
||||
except (BuildError, NotImplementedError):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def path(
|
||||
def _path(
|
||||
self,
|
||||
portspec: str,
|
||||
ccw: SupportsBool | None,
|
||||
|
|
@ -50,7 +365,7 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pathS(
|
||||
def _pathS(
|
||||
self,
|
||||
portspec: str,
|
||||
length: float,
|
||||
|
|
@ -61,6 +376,16 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
) -> Self:
|
||||
pass
|
||||
|
||||
def path(self, *args, **kwargs) -> Self:
|
||||
import warnings
|
||||
warnings.warn("path() is deprecated; use trace(), straight(), or bend() instead", DeprecationWarning, stacklevel=2)
|
||||
return self._path(*args, **kwargs)
|
||||
|
||||
def pathS(self, *args, **kwargs) -> Self:
|
||||
import warnings
|
||||
warnings.warn("pathS() is deprecated; use jog() instead", DeprecationWarning, stacklevel=2)
|
||||
return self._pathS(*args, **kwargs)
|
||||
|
||||
@abstractmethod
|
||||
def plug(
|
||||
self,
|
||||
|
|
@ -76,6 +401,11 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
) -> Self:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def plugged(self, connections: dict[str, str]) -> Self:
|
||||
""" Manual connection acknowledgment. """
|
||||
pass
|
||||
|
||||
def retool(
|
||||
self,
|
||||
tool: Tool,
|
||||
|
|
@ -143,88 +473,13 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
**kwargs,
|
||||
) -> Self:
|
||||
"""
|
||||
Build a "wire"/"waveguide" extending from the port `portspec`, with the aim
|
||||
of ending exactly at a target position.
|
||||
|
||||
The wire will travel so that the output port will be placed at exactly the target
|
||||
position along the input port's axis. There can be an unspecified (tool-dependent)
|
||||
offset in the perpendicular direction. The output port will be rotated (or not)
|
||||
based on the `ccw` parameter.
|
||||
|
||||
If using `RenderPather`, `RenderPather.render` must be called after all paths have been fully planned.
|
||||
|
||||
Args:
|
||||
portspec: The name of the port into which the wire will be plugged.
|
||||
ccw: If `None`, the output should be along the same axis as the input.
|
||||
Otherwise, cast to bool and turn counterclockwise if True
|
||||
and clockwise otherwise.
|
||||
position: The final port position, along the input's axis only.
|
||||
(There may be a tool-dependent offset along the other axis.)
|
||||
Only one of `position`, `x`, and `y` may be specified.
|
||||
x: The final port position along the x axis.
|
||||
`portspec` must refer to a horizontal port if `x` is passed, otherwise a
|
||||
BuildError will be raised.
|
||||
y: The final port position along the y axis.
|
||||
`portspec` must refer to a vertical port if `y` is passed, otherwise a
|
||||
BuildError will be raised.
|
||||
plug_into: If not None, attempts to plug the wire's output port into the provided
|
||||
port on `self`.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
Raises:
|
||||
BuildError if `position`, `x`, or `y` is too close to fit the bend (if a bend
|
||||
is present).
|
||||
BuildError if `x` or `y` is specified but does not match the axis of `portspec`.
|
||||
BuildError if more than one of `x`, `y`, and `position` is specified.
|
||||
[DEPRECATED] use trace_to() instead.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.error('Skipping path_to() since device is dead')
|
||||
return self
|
||||
import warnings
|
||||
warnings.warn("path_to() is deprecated; use trace_to() instead", DeprecationWarning, stacklevel=2)
|
||||
|
||||
pos_count = sum(vv is not None for vv in (position, x, y))
|
||||
if pos_count > 1:
|
||||
raise BuildError('Only one of `position`, `x`, and `y` may be specified at once')
|
||||
if pos_count < 1:
|
||||
raise BuildError('One of `position`, `x`, and `y` must be specified')
|
||||
|
||||
port = self.pattern[portspec]
|
||||
if port.rotation is None:
|
||||
raise PortError(f'Port {portspec} has no rotation and cannot be used for path_to()')
|
||||
|
||||
if not numpy.isclose(port.rotation % (pi / 2), 0):
|
||||
raise BuildError('path_to was asked to route from non-manhattan port')
|
||||
|
||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||
if is_horizontal:
|
||||
if y is not None:
|
||||
raise BuildError('Asked to path to y-coordinate, but port is horizontal')
|
||||
if position is None:
|
||||
position = x
|
||||
else:
|
||||
if x is not None:
|
||||
raise BuildError('Asked to path to x-coordinate, but port is vertical')
|
||||
if position is None:
|
||||
position = y
|
||||
|
||||
x0, y0 = port.offset
|
||||
if is_horizontal:
|
||||
if numpy.sign(numpy.cos(port.rotation)) == numpy.sign(position - x0):
|
||||
raise BuildError(f'path_to routing to behind source port: x0={x0:g} to {position:g}')
|
||||
length = numpy.abs(position - x0)
|
||||
else:
|
||||
if numpy.sign(numpy.sin(port.rotation)) == numpy.sign(position - y0):
|
||||
raise BuildError(f'path_to routing to behind source port: y0={y0:g} to {position:g}')
|
||||
length = numpy.abs(position - y0)
|
||||
|
||||
return self.path(
|
||||
portspec,
|
||||
ccw,
|
||||
length,
|
||||
plug_into = plug_into,
|
||||
**kwargs,
|
||||
)
|
||||
bounds = {kk: vv for kk, vv in (('position', position), ('x', x), ('y', y)) if vv is not None}
|
||||
return self.trace_to(portspec, ccw, plug_into=plug_into, **bounds, **kwargs)
|
||||
|
||||
def path_into(
|
||||
self,
|
||||
|
|
@ -237,100 +492,19 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
**kwargs,
|
||||
) -> Self:
|
||||
"""
|
||||
Create a "wire"/"waveguide" traveling between the ports `portspec_src` and
|
||||
`portspec_dst`, and `plug` it into both (or just the source port).
|
||||
|
||||
Only unambiguous scenarios are allowed:
|
||||
- Straight connector between facing ports
|
||||
- Single 90 degree bend
|
||||
- Jog between facing ports
|
||||
(jog is done as late as possible, i.e. only 2 L-shaped segments are used)
|
||||
|
||||
By default, the destination's `pytpe` will be used as the `out_ptype` for the
|
||||
wire, and the `portspec_dst` will be plugged (i.e. removed).
|
||||
|
||||
If using `RenderPather`, `RenderPather.render` must be called after all paths have been fully planned.
|
||||
|
||||
Args:
|
||||
portspec_src: The name of the starting port into which the wire will be plugged.
|
||||
portspec_dst: The name of the destination port.
|
||||
out_ptype: Passed to the pathing tool in order to specify the desired port type
|
||||
to be generated at the destination end. If `None` (default), the destination
|
||||
port's `ptype` will be used.
|
||||
thru: If not `None`, the port by this name will be rename to `portspec_src`.
|
||||
This can be used when routing a signal through a pre-placed 2-port device.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
Raises:
|
||||
PortError if either port does not have a specified rotation.
|
||||
BuildError if and invalid port config is encountered:
|
||||
- Non-manhattan ports
|
||||
- U-bend
|
||||
- Destination too close to (or behind) source
|
||||
[DEPRECATED] use trace_into() instead.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.error('Skipping path_into() since device is dead')
|
||||
return self
|
||||
import warnings
|
||||
warnings.warn("path_into() is deprecated; use trace_into() instead", DeprecationWarning, stacklevel=2)
|
||||
|
||||
port_src = self.pattern[portspec_src]
|
||||
port_dst = self.pattern[portspec_dst]
|
||||
|
||||
if out_ptype is None:
|
||||
out_ptype = port_dst.ptype
|
||||
|
||||
if port_src.rotation is None:
|
||||
raise PortError(f'Port {portspec_src} has no rotation and cannot be used for path_into()')
|
||||
if port_dst.rotation is None:
|
||||
raise PortError(f'Port {portspec_dst} has no rotation and cannot be used for path_into()')
|
||||
|
||||
if not numpy.isclose(port_src.rotation % (pi / 2), 0):
|
||||
raise BuildError('path_into was asked to route from non-manhattan port')
|
||||
if not numpy.isclose(port_dst.rotation % (pi / 2), 0):
|
||||
raise BuildError('path_into was asked to route to non-manhattan port')
|
||||
|
||||
src_is_horizontal = numpy.isclose(port_src.rotation % pi, 0)
|
||||
dst_is_horizontal = numpy.isclose(port_dst.rotation % pi, 0)
|
||||
xs, ys = port_src.offset
|
||||
xd, yd = port_dst.offset
|
||||
|
||||
angle = (port_dst.rotation - port_src.rotation) % (2 * pi)
|
||||
|
||||
dst_extra_args = {'out_ptype': out_ptype}
|
||||
if plug_destination:
|
||||
dst_extra_args['plug_into'] = portspec_dst
|
||||
|
||||
src_args = {**kwargs}
|
||||
dst_args = {**src_args, **dst_extra_args}
|
||||
if src_is_horizontal and not dst_is_horizontal:
|
||||
# single bend should suffice
|
||||
self.path_to(portspec_src, angle > pi, x=xd, **src_args)
|
||||
self.path_to(portspec_src, None, y=yd, **dst_args)
|
||||
elif dst_is_horizontal and not src_is_horizontal:
|
||||
# single bend should suffice
|
||||
self.path_to(portspec_src, angle > pi, y=yd, **src_args)
|
||||
self.path_to(portspec_src, None, x=xd, **dst_args)
|
||||
elif numpy.isclose(angle, pi):
|
||||
if src_is_horizontal and ys == yd:
|
||||
# straight connector
|
||||
self.path_to(portspec_src, None, x=xd, **dst_args)
|
||||
elif not src_is_horizontal and xs == xd:
|
||||
# straight connector
|
||||
self.path_to(portspec_src, None, y=yd, **dst_args)
|
||||
else:
|
||||
# S-bend, delegate to implementations
|
||||
(travel, jog), _ = port_src.measure_travel(port_dst)
|
||||
self.pathS(portspec_src, -travel, -jog, **dst_args)
|
||||
elif numpy.isclose(angle, 0):
|
||||
raise BuildError('Don\'t know how to route a U-bend yet (TODO)!')
|
||||
else:
|
||||
raise BuildError(f'Don\'t know how to route ports with relative angle {angle}')
|
||||
|
||||
if thru is not None:
|
||||
self.rename_ports({thru: portspec_src})
|
||||
|
||||
return self
|
||||
return self.trace_into(
|
||||
portspec_src,
|
||||
portspec_dst,
|
||||
out_ptype = out_ptype,
|
||||
plug_destination = plug_destination,
|
||||
thru = thru,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def mpath(
|
||||
self,
|
||||
|
|
@ -342,109 +516,12 @@ class PatherMixin(PortList, metaclass=ABCMeta):
|
|||
**kwargs,
|
||||
) -> Self:
|
||||
"""
|
||||
`mpath` is a superset of `path` and `path_to` which can act on bundles or buses
|
||||
of "wires or "waveguides".
|
||||
|
||||
The wires will travel so that the output ports will be placed at well-defined
|
||||
locations along the axis of their input ports, but may have arbitrary (tool-
|
||||
dependent) offsets in the perpendicular direction.
|
||||
|
||||
If `ccw` is not `None`, the wire bundle will turn 90 degres in either the
|
||||
clockwise (`ccw=False`) or counter-clockwise (`ccw=True`) direction. Within the
|
||||
bundle, the center-to-center wire spacings after the turn are set by `spacing`,
|
||||
which is required when `ccw` is not `None`. The final position of bundle as a
|
||||
whole can be set in a number of ways:
|
||||
|
||||
=A>---------------------------V turn direction: `ccw=False`
|
||||
=B>-------------V |
|
||||
=C>-----------------------V |
|
||||
=D=>----------------V |
|
||||
|
|
||||
|
||||
x---x---x---x `spacing` (can be scalar or array)
|
||||
|
||||
<--------------> `emin=`
|
||||
<------> `bound_type='min_past_furthest', bound=`
|
||||
<--------------------------------> `emax=`
|
||||
x `pmin=`
|
||||
x `pmax=`
|
||||
|
||||
- `emin=`, equivalent to `bound_type='min_extension', bound=`
|
||||
The total extension value for the furthest-out port (B in the diagram).
|
||||
- `emax=`, equivalent to `bound_type='max_extension', bound=`:
|
||||
The total extension value for the closest-in port (C in the diagram).
|
||||
- `pmin=`, equivalent to `xmin=`, `ymin=`, or `bound_type='min_position', bound=`:
|
||||
The coordinate of the innermost bend (D's bend).
|
||||
The x/y versions throw an error if they do not match the port axis (for debug)
|
||||
- `pmax=`, `xmax=`, `ymax=`, or `bound_type='max_position', bound=`:
|
||||
The coordinate of the outermost bend (A's bend).
|
||||
The x/y versions throw an error if they do not match the port axis (for debug)
|
||||
- `bound_type='min_past_furthest', bound=`:
|
||||
The distance between furthest out-port (B) and the innermost bend (D's bend).
|
||||
|
||||
If `ccw=None`, final output positions (along the input axis) of all wires will be
|
||||
identical (i.e. wires will all be cut off evenly). In this case, `spacing=None` is
|
||||
required. In this case, `emin=` and `emax=` are equivalent to each other, and
|
||||
`pmin=`, `pmax=`, `xmin=`, etc. are also equivalent to each other.
|
||||
|
||||
If using `RenderPather`, `RenderPather.render` must be called after all paths have been fully planned.
|
||||
|
||||
Args:
|
||||
portspec: The names of the ports which are to be routed.
|
||||
ccw: If `None`, the outputs should be along the same axis as the inputs.
|
||||
Otherwise, cast to bool and turn 90 degrees counterclockwise if `True`
|
||||
and clockwise otherwise.
|
||||
spacing: Center-to-center distance between output ports along the input port's axis.
|
||||
Must be provided if (and only if) `ccw` is not `None`.
|
||||
set_rotation: If the provided ports have `rotation=None`, this can be used
|
||||
to set a rotation for them.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
||||
Raises:
|
||||
BuildError if the implied length for any wire is too close to fit the bend
|
||||
(if a bend is requested).
|
||||
BuildError if `xmin`/`xmax` or `ymin`/`ymax` is specified but does not
|
||||
match the axis of `portspec`.
|
||||
BuildError if an incorrect bound type or spacing is specified.
|
||||
[DEPRECATED] use trace() or trace_to() instead.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.error('Skipping mpath() since device is dead')
|
||||
return self
|
||||
import warnings
|
||||
warnings.warn("mpath() is deprecated; use trace() or trace_to() instead", DeprecationWarning, stacklevel=2)
|
||||
|
||||
bound_types = set()
|
||||
if 'bound_type' in kwargs:
|
||||
bound_types.add(kwargs.pop('bound_type'))
|
||||
bound = kwargs.pop('bound')
|
||||
for bt in ('emin', 'emax', 'pmin', 'pmax', 'xmin', 'xmax', 'ymin', 'ymax', 'min_past_furthest'):
|
||||
if bt in kwargs:
|
||||
bound_types.add(bt)
|
||||
bound = kwargs.pop(bt)
|
||||
|
||||
if not bound_types:
|
||||
raise BuildError('No bound type specified for mpath')
|
||||
if len(bound_types) > 1:
|
||||
raise BuildError(f'Too many bound types specified for mpath: {bound_types}')
|
||||
bound_type = tuple(bound_types)[0]
|
||||
|
||||
if isinstance(portspec, str):
|
||||
portspec = [portspec]
|
||||
ports = self.pattern[tuple(portspec)]
|
||||
|
||||
extensions = ell(ports, ccw, spacing=spacing, bound=bound, bound_type=bound_type, set_rotation=set_rotation)
|
||||
|
||||
#if container:
|
||||
# assert not getattr(self, 'render'), 'Containers not implemented for RenderPather'
|
||||
# bld = self.interface(source=ports, library=self.library, tools=self.tools)
|
||||
# for port_name, length in extensions.items():
|
||||
# bld.path(port_name, ccw, length, **kwargs)
|
||||
# self.library[container] = bld.pattern
|
||||
# self.plug(Abstract(container, bld.pattern.ports), {sp: 'in_' + sp for sp in ports}) # TODO safe to use 'in_'?
|
||||
#else:
|
||||
for port_name, length in extensions.items():
|
||||
self.path(port_name, ccw, length, **kwargs)
|
||||
return self
|
||||
return self.trace(portspec, ccw, spacing=spacing, set_rotation=set_rotation, **kwargs)
|
||||
|
||||
# TODO def bus_join()?
|
||||
|
||||
|
|
@ -488,61 +565,42 @@ class PortPather:
|
|||
with self.pather.toolctx(tool, keys=self.ports):
|
||||
yield self
|
||||
|
||||
def path(self, *args, **kwargs) -> Self:
|
||||
def trace(self, ccw: SupportsBool | None, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.trace(self.ports, ccw, length, **kwargs)
|
||||
return self
|
||||
|
||||
def trace_to(self, ccw: SupportsBool | None, **kwargs) -> Self:
|
||||
self.pather.trace_to(self.ports, ccw, **kwargs)
|
||||
return self
|
||||
|
||||
def straight(self, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.straight(self.ports, length, **kwargs)
|
||||
return self
|
||||
|
||||
def bend(self, ccw: SupportsBool, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.bend(self.ports, ccw, length, **kwargs)
|
||||
return self
|
||||
|
||||
def ccw(self, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.ccw(self.ports, length, **kwargs)
|
||||
return self
|
||||
|
||||
def cw(self, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.cw(self.ports, length, **kwargs)
|
||||
return self
|
||||
|
||||
def jog(self, offset: float, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.jog(self.ports, offset, length, **kwargs)
|
||||
return self
|
||||
|
||||
def uturn(self, offset: float, length: float | None = None, **kwargs) -> Self:
|
||||
self.pather.uturn(self.ports, offset, length, **kwargs)
|
||||
return self
|
||||
|
||||
def trace_into(self, target_port: str, **kwargs) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
logger.warning('Use path_each() when pathing multiple ports independently')
|
||||
for port in self.ports:
|
||||
self.pather.path(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def path_each(self, *args, **kwargs) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather.path(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def pathS(self, *args, **kwargs) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
logger.warning('Use pathS_each() when pathing multiple ports independently')
|
||||
for port in self.ports:
|
||||
self.pather.pathS(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def pathS_each(self, *args, **kwargs) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather.pathS(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def path_to(self, *args, **kwargs) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
logger.warning('Use path_each_to() when pathing multiple ports independently')
|
||||
for port in self.ports:
|
||||
self.pather.path_to(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def path_each_to(self, *args, **kwargs) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather.path_to(port, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def mpath(self, *args, **kwargs) -> Self:
|
||||
self.pather.mpath(self.ports, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def path_into(self, *args, **kwargs) -> Self:
|
||||
""" Path_into, using the current port as the source """
|
||||
if len(self.ports) > 1:
|
||||
raise BuildError(f'Unable use implicit path_into() with {len(self.ports)} (>1) ports.')
|
||||
self.pather.path_into(self.ports[0], *args, **kwargs)
|
||||
return self
|
||||
|
||||
def path_from(self, *args, **kwargs) -> Self:
|
||||
""" Path_into, using the current port as the destination """
|
||||
if len(self.ports) > 1:
|
||||
raise BuildError(f'Unable use implicit path_from() with {len(self.ports)} (>1) ports.')
|
||||
thru = kwargs.pop('thru', None)
|
||||
self.pather.path_into(args[0], self.ports[0], *args[1:], **kwargs)
|
||||
if thru is not None:
|
||||
self.rename_from(thru)
|
||||
raise BuildError(f'Unable use implicit trace_into() with {len(self.ports)} (>1) ports.')
|
||||
self.pather.trace_into(self.ports[0], target_port, **kwargs)
|
||||
return self
|
||||
|
||||
def plug(
|
||||
|
|
@ -558,10 +616,13 @@ class PortPather:
|
|||
self.pather.plug(other, {self.ports[0]: other_port}, *args, **kwargs)
|
||||
return self
|
||||
|
||||
def plugged(self, other_port: str) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
def plugged(self, other_port: str | Mapping[str, str]) -> Self:
|
||||
if isinstance(other_port, Mapping):
|
||||
self.pather.plugged(dict(other_port))
|
||||
elif len(self.ports) > 1:
|
||||
raise BuildError(f'Unable use implicit plugged() with {len(self.ports)} (>1) ports.')
|
||||
self.pather.plugged({self.ports[0]: other_port})
|
||||
else:
|
||||
self.pather.plugged({self.ports[0]: other_port})
|
||||
return self
|
||||
|
||||
#
|
||||
|
|
@ -569,95 +630,91 @@ class PortPather:
|
|||
#
|
||||
def set_ptype(self, ptype: str) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather[port].set_ptype(ptype)
|
||||
self.pather.pattern[port].set_ptype(ptype)
|
||||
return self
|
||||
|
||||
def translate(self, *args, **kwargs) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather[port].translate(*args, **kwargs)
|
||||
self.pather.pattern[port].translate(*args, **kwargs)
|
||||
return self
|
||||
|
||||
def mirror(self, *args, **kwargs) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather[port].mirror(*args, **kwargs)
|
||||
self.pather.pattern[port].mirror(*args, **kwargs)
|
||||
return self
|
||||
|
||||
def rotate(self, rotation: float) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather[port].rotate(rotation)
|
||||
self.pather.pattern[port].rotate(rotation)
|
||||
return self
|
||||
|
||||
def set_rotation(self, rotation: float | None) -> Self:
|
||||
for port in self.ports:
|
||||
self.pather[port].set_rotation(rotation)
|
||||
self.pather.pattern[port].set_rotation(rotation)
|
||||
return self
|
||||
|
||||
def rename_to(self, new_name: str) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
BuildError('Use rename_ports() for >1 port')
|
||||
self.pather.rename_ports({self.ports[0]: new_name})
|
||||
self.ports[0] = new_name
|
||||
return self
|
||||
|
||||
def rename_from(self, old_name: str) -> Self:
|
||||
if len(self.ports) > 1:
|
||||
BuildError('Use rename_ports() for >1 port')
|
||||
self.pather.rename_ports({old_name: self.ports[0]})
|
||||
return self
|
||||
|
||||
def rename_ports(self, name_map: dict[str, str | None]) -> Self:
|
||||
def rename(self, name: str | Mapping[str, str | None]) -> Self:
|
||||
""" Rename active ports. Replaces `rename_to`. """
|
||||
name_map: dict[str, str | None]
|
||||
if isinstance(name, str):
|
||||
if len(self.ports) > 1:
|
||||
raise BuildError('Use a mapping to rename >1 port')
|
||||
name_map = {self.ports[0]: name}
|
||||
else:
|
||||
name_map = dict(name)
|
||||
self.pather.rename_ports(name_map)
|
||||
self.ports = [mm for mm in [name_map.get(pp, pp) for pp in self.ports] if mm is not None]
|
||||
return self
|
||||
|
||||
def add_ports(self, ports: Iterable[str]) -> Self:
|
||||
ports = list(ports)
|
||||
conflicts = set(ports) & set(self.ports)
|
||||
if conflicts:
|
||||
raise BuildError(f'ports {conflicts} already selected')
|
||||
self.ports += ports
|
||||
def select(self, ports: str | Iterable[str]) -> Self:
|
||||
""" Add ports to the selection. Replaces `add_ports`. """
|
||||
if isinstance(ports, str):
|
||||
ports = [ports]
|
||||
for port in ports:
|
||||
if port not in self.ports:
|
||||
self.ports.append(port)
|
||||
return self
|
||||
|
||||
def add_port(self, port: str, index: int | None = None) -> Self:
|
||||
if port in self.ports:
|
||||
raise BuildError(f'{port=} already selected')
|
||||
if index is not None:
|
||||
self.ports.insert(index, port)
|
||||
def deselect(self, ports: str | Iterable[str]) -> Self:
|
||||
""" Remove ports from the selection. Replaces `drop_port`. """
|
||||
if isinstance(ports, str):
|
||||
ports = [ports]
|
||||
ports_set = set(ports)
|
||||
self.ports = [pp for pp in self.ports if pp not in ports_set]
|
||||
return self
|
||||
|
||||
def mark(self, name: str | Mapping[str, str]) -> Self:
|
||||
""" Bookmark current port(s). Replaces `save_copy`. """
|
||||
name_map: Mapping[str, str]
|
||||
if isinstance(name, str):
|
||||
if len(self.ports) > 1:
|
||||
raise BuildError('Use a mapping to mark >1 port')
|
||||
name_map = {self.ports[0]: name}
|
||||
else:
|
||||
self.ports.append(port)
|
||||
name_map = name
|
||||
for src, dst in name_map.items():
|
||||
self.pather.pattern.ports[dst] = self.pather.pattern[src].copy()
|
||||
return self
|
||||
|
||||
def drop_port(self, port: str) -> Self:
|
||||
if port not in self.ports:
|
||||
raise BuildError(f'{port=} already not selected')
|
||||
self.ports = [pp for pp in self.ports if pp != port]
|
||||
def fork(self, name: str | Mapping[str, str]) -> Self:
|
||||
""" Split and follow new name. Replaces `into_copy`. """
|
||||
name_map: Mapping[str, str]
|
||||
if isinstance(name, str):
|
||||
if len(self.ports) > 1:
|
||||
raise BuildError('Use a mapping to fork >1 port')
|
||||
name_map = {self.ports[0]: name}
|
||||
else:
|
||||
name_map = name
|
||||
for src, dst in name_map.items():
|
||||
self.pather.pattern.ports[dst] = self.pather.pattern[src].copy()
|
||||
self.ports = [(dst if pp == src else pp) for pp in self.ports]
|
||||
return self
|
||||
|
||||
def into_copy(self, new_name: str, src: str | None = None) -> Self:
|
||||
""" Copy a port and replace it with the copy """
|
||||
if not self.ports:
|
||||
raise BuildError('Have no ports to copy')
|
||||
if len(self.ports) == 1:
|
||||
src = self.ports[0]
|
||||
elif src is None:
|
||||
raise BuildError('Must specify src when >1 port is available')
|
||||
if src not in self.ports:
|
||||
raise BuildError(f'{src=} not available')
|
||||
self.pather.ports[new_name] = self.pather[src].copy()
|
||||
self.ports = [(new_name if pp == src else pp) for pp in self.ports]
|
||||
return self
|
||||
|
||||
def save_copy(self, new_name: str, src: str | None = None) -> Self:
|
||||
""" Copy a port and but keep using the original """
|
||||
if not self.ports:
|
||||
raise BuildError('Have no ports to copy')
|
||||
if len(self.ports) == 1:
|
||||
src = self.ports[0]
|
||||
elif src is None:
|
||||
raise BuildError('Must specify src when >1 port is available')
|
||||
if src not in self.ports:
|
||||
raise BuildError(f'{src=} not available')
|
||||
self.pather.ports[new_name] = self.pather[src].copy()
|
||||
def drop(self) -> Self:
|
||||
""" Remove selected ports from the pattern and the PortPather. Replaces `delete(None)`. """
|
||||
for pp in self.ports:
|
||||
del self.pather.pattern.ports[pp]
|
||||
self.ports = []
|
||||
return self
|
||||
|
||||
@overload
|
||||
|
|
@ -668,10 +725,9 @@ class PortPather:
|
|||
|
||||
def delete(self, name: str | None = None) -> Self | None:
|
||||
if name is None:
|
||||
for pp in self.ports:
|
||||
del self.pather.ports[pp]
|
||||
self.drop()
|
||||
return None
|
||||
del self.pather.ports[name]
|
||||
del self.pather.pattern.ports[name]
|
||||
self.ports = [pp for pp in self.ports if pp != name]
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class RenderPather(PatherMixin):
|
||||
"""
|
||||
`RenderPather` is an alternative to `Pather` which uses the `path`/`path_to`/`mpath`
|
||||
`RenderPather` is an alternative to `Pather` which uses the `trace`/`trace_to`
|
||||
functions to plan out wire paths without incrementally generating the layout. Instead,
|
||||
it waits until `render` is called, at which point it draws all the planned segments
|
||||
simultaneously. This allows it to e.g. draw each wire using a single `Path` or
|
||||
|
|
@ -97,7 +97,7 @@ class RenderPather(PatherMixin):
|
|||
in which case it is interpreted as a name in `library`.
|
||||
Default `None` (no ports).
|
||||
tools: A mapping of {port: tool} which specifies what `Tool` should be used
|
||||
to generate waveguide or wire segments when `path`/`path_to`/`mpath`
|
||||
to generate waveguide or wire segments when `trace`/`trace_to`
|
||||
are called. Relies on `Tool.planL` and `Tool.render` implementations.
|
||||
name: If specified, `library[name]` is set to `self.pattern`.
|
||||
"""
|
||||
|
|
@ -150,7 +150,7 @@ class RenderPather(PatherMixin):
|
|||
and to which the new one should be added (if named). If not provided,
|
||||
`source.library` must exist and will be used.
|
||||
tools: `Tool`s which will be used by the pather for generating new wires
|
||||
or waveguides (via `path`/`path_to`/`mpath`).
|
||||
or waveguides (via `trace`/`trace_to`).
|
||||
in_prefix: Prepended to port names for newly-created ports with
|
||||
reversed directions compared to the current device.
|
||||
out_prefix: Prepended to port names for ports which are directly
|
||||
|
|
@ -377,7 +377,7 @@ class RenderPather(PatherMixin):
|
|||
PortList.plugged(self, connections)
|
||||
return self
|
||||
|
||||
def path(
|
||||
def _path(
|
||||
self,
|
||||
portspec: str,
|
||||
ccw: SupportsBool | None,
|
||||
|
|
@ -420,7 +420,7 @@ class RenderPather(PatherMixin):
|
|||
LibraryError if no valid name could be picked for the pattern.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.warning('Skipping geometry for path() since device is dead')
|
||||
logger.warning('Skipping geometry for _path() since device is dead')
|
||||
|
||||
port = self.pattern[portspec]
|
||||
in_ptype = port.ptype
|
||||
|
|
@ -460,7 +460,7 @@ class RenderPather(PatherMixin):
|
|||
|
||||
return self
|
||||
|
||||
def pathS(
|
||||
def _pathS(
|
||||
self,
|
||||
portspec: str,
|
||||
length: float,
|
||||
|
|
@ -504,7 +504,7 @@ class RenderPather(PatherMixin):
|
|||
LibraryError if no valid name could be picked for the pattern.
|
||||
"""
|
||||
if self._dead:
|
||||
logger.warning('Skipping geometry for pathS() since device is dead')
|
||||
logger.warning('Skipping geometry for _pathS() since device is dead')
|
||||
|
||||
port = self.pattern[portspec]
|
||||
in_ptype = port.ptype
|
||||
|
|
@ -527,8 +527,8 @@ class RenderPather(PatherMixin):
|
|||
jog1 = Port((0, 0), 0).measure_travel(t_port1)[0][1]
|
||||
|
||||
kwargs_plug = kwargs | {'plug_into': plug_into}
|
||||
self.path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
|
||||
self.path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
|
||||
self._path(portspec, ccw0, length - abs(jog1), **kwargs_no_out)
|
||||
self._path(portspec, not ccw0, abs(jog - jog0), **kwargs_plug)
|
||||
except (BuildError, NotImplementedError):
|
||||
if not self._dead:
|
||||
raise
|
||||
|
|
@ -564,7 +564,7 @@ class RenderPather(PatherMixin):
|
|||
append: bool = True,
|
||||
) -> Self:
|
||||
"""
|
||||
Generate the geometry which has been planned out with `path`/`path_to`/etc.
|
||||
Generate the geometry which has been planned out with `trace`/`trace_to`/etc.
|
||||
|
||||
Args:
|
||||
append: If `True`, the rendered geometry will be directly appended to
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue