Compare commits

..

6 Commits

7 changed files with 160 additions and 49 deletions

View File

@ -80,6 +80,7 @@ from .builder import (
SimpleTool as SimpleTool, SimpleTool as SimpleTool,
AutoTool as AutoTool, AutoTool as AutoTool,
PathTool as PathTool, PathTool as PathTool,
PortPather as PortPather,
) )
from .utils import ( from .utils import (
ports2data as ports2data, ports2data as ports2data,

View File

@ -1,6 +1,7 @@
from .builder import Builder as Builder from .builder import Builder as Builder
from .pather import Pather as Pather from .pather import Pather as Pather
from .renderpather import RenderPather as RenderPather from .renderpather import RenderPather as RenderPather
from .pather_mixin import PortPather as PortPather
from .utils import ell as ell from .utils import ell as ell
from .tools import ( from .tools import (
Tool as Tool, Tool as Tool,

View File

@ -67,7 +67,7 @@ class Builder(PortList):
- `my_device.plug(wire, {'myport': 'A'})` places port 'A' of `wire` at 'myport' - `my_device.plug(wire, {'myport': 'A'})` places port 'A' of `wire` at 'myport'
of `my_device`. If `wire` has only two ports (e.g. 'A' and 'B'), no `map_out`, of `my_device`. If `wire` has only two ports (e.g. 'A' and 'B'), no `map_out`,
argument is provided, and the `inherit_name` argument is not explicitly argument is provided, and the `thru` argument is not explicitly
set to `False`, the unconnected port of `wire` is automatically renamed to set to `False`, the unconnected port of `wire` is automatically renamed to
'myport'. This allows easy extension of existing ports without changing 'myport'. This allows easy extension of existing ports without changing
their names or having to provide `map_out` each time `plug` is called. their names or having to provide `map_out` each time `plug` is called.
@ -223,7 +223,7 @@ class Builder(PortList):
map_out: dict[str, str | None] | None = None, map_out: dict[str, str | None] | None = None,
*, *,
mirrored: bool = False, mirrored: bool = False,
inherit_name: bool = True, thru: bool | str = True,
set_rotation: bool | None = None, set_rotation: bool | None = None,
append: bool = False, append: bool = False,
ok_connections: Iterable[tuple[str, str]] = (), ok_connections: Iterable[tuple[str, str]] = (),
@ -246,11 +246,15 @@ class Builder(PortList):
new names for ports in `other`. new names for ports in `other`.
mirrored: Enables mirroring `other` across the x axis prior to mirrored: Enables mirroring `other` across the x axis prior to
connecting any ports. connecting any ports.
inherit_name: If `True`, and `map_in` specifies only a single port, thru: If map_in specifies only a single port, `thru` provides a mechainsm
and `map_out` is `None`, and `other` has only two ports total, to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`,
then automatically renames the output port of `other` to the - If True (default), and `other` has only two ports total, and map_out
name of the port from `self` that appears in `map_in`. This doesn't specify a name for the other port, its name is set to the key
makes it easy to extend a device with simple 2-port devices in `map_in`, i.e. 'myport'.
- If a string, `map_out[thru]` is set to the key in `map_in` (i.e. 'myport').
An error is raised if that entry already exists.
This makes it easy to extend a pattern with simple 2-port devices
(e.g. wires) without providing `map_out` each time `plug` is (e.g. wires) without providing `map_out` each time `plug` is
called. See "Examples" above for more info. Default `True`. called. See "Examples" above for more info. Default `True`.
set_rotation: If the necessary rotation cannot be determined from set_rotation: If the necessary rotation cannot be determined from
@ -296,7 +300,7 @@ class Builder(PortList):
map_in = map_in, map_in = map_in,
map_out = map_out, map_out = map_out,
mirrored = mirrored, mirrored = mirrored,
inherit_name = inherit_name, thru = thru,
set_rotation = set_rotation, set_rotation = set_rotation,
append = append, append = append,
ok_connections = ok_connections, ok_connections = ok_connections,

View File

@ -1,4 +1,4 @@
from typing import Self from typing import Self, TYPE_CHECKING
from collections.abc import Sequence, Iterator from collections.abc import Sequence, Iterator
import logging import logging
from contextlib import contextmanager from contextlib import contextmanager
@ -11,11 +11,15 @@ from numpy.typing import ArrayLike
from ..pattern import Pattern from ..pattern import Pattern
from ..library import ILibrary from ..library import ILibrary
from ..error import PortError, BuildError from ..error import PortError, BuildError
from ..utils import rotation_matrix_2d, SupportsBool from ..utils import SupportsBool
#from ..abstract import Abstract from ..abstract import Abstract
from .tools import Tool from .tools import Tool
from .utils import ell from .utils import ell
if TYPE_CHECKING:
from .pather import Pather
from .renderpather import RenderPather
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -435,3 +439,88 @@ class PatherMixin(metaclass=ABCMeta):
""" """
self.pattern.flatten(self.library) self.pattern.flatten(self.library)
return self return self
class PortPather:
"""
Single-port state manager
This class provides a convenient way to perform multiple pathing operations on a
port without needing to repeatedly pass its name.
"""
port: str
pather: 'Pather | RenderPather'
def __init__(self, port: str, pather: 'Pather | RenderPather') -> None:
self.port = port
self.pather = pather
#
# Delegate to pather
#
def retool(self, tool: Tool) -> Self:
self.pather.retool(tool, keys=[self.port])
return self
@contextmanager
def toolctx(self, tool: Tool) -> Iterator[Self]:
with self.pather.toolctx(tool, keys=[self.port]):
yield self
def path(self, *args, **kwargs) -> Self:
self.pather.path(self.port, *args, **kwargs)
return self
def pathS(self, *args, **kwargs) -> Self:
self.pather.pathS(self.port, *args, **kwargs)
return self
def path_to(self, *args, **kwargs) -> Self:
self.pather.path_to(self.port, *args, **kwargs)
return self
def path_into(self, *args, **kwargs) -> Self:
self.pather.path_into(self.port, *args, **kwargs)
return self
def path_from(self, *args, **kwargs) -> Self:
self.pather.path_into(args[0], self.port, *args[1:], **kwargs)
return self
def mpath(self, *args, **kwargs) -> Self:
self.pather.mpath([self.port], *args, **kwargs)
return self
def plug(
self,
other: Abstract | str,
other_port: str,
*args,
**kwargs,
) -> Self:
self.pather.plug(other, {self.port: other_port}, *args, **kwargs)
return self
def plugged(self, other_port: str) -> Self:
self.pather.plugged({self.port: other_port})
return self
#
# Delegate to port
#
def set_ptype(self, ptype: str) -> Self:
self.pather[self.port].set_ptype(ptype)
return self
def mirror(self, *args, **kwargs) -> Self:
self.pather[self.port].mirror(*args, **kwargs)
return self
def rotate(self, rotation: float) -> Self:
self.pather[self.port].rotate(rotation)
return self
def set_rotation(self, rotation: float | None) -> Self:
self.pather[self.port].set_rotation(rotation)
return self

View File

@ -193,7 +193,7 @@ class RenderPather(PortList, PatherMixin):
map_out: dict[str, str | None] | None = None, map_out: dict[str, str | None] | None = None,
*, *,
mirrored: bool = False, mirrored: bool = False,
inherit_name: bool = True, thru: bool | str = True,
set_rotation: bool | None = None, set_rotation: bool | None = None,
append: bool = False, append: bool = False,
) -> Self: ) -> Self:
@ -210,11 +210,15 @@ class RenderPather(PortList, PatherMixin):
new names for ports in `other`. new names for ports in `other`.
mirrored: Enables mirroring `other` across the x axis prior to mirrored: Enables mirroring `other` across the x axis prior to
connecting any ports. connecting any ports.
inherit_name: If `True`, and `map_in` specifies only a single port, thru: If map_in specifies only a single port, `thru` provides a mechainsm
and `map_out` is `None`, and `other` has only two ports total, to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`,
then automatically renames the output port of `other` to the - If True (default), and `other` has only two ports total, and map_out
name of the port from `self` that appears in `map_in`. This doesn't specify a name for the other port, its name is set to the key
makes it easy to extend a device with simple 2-port devices in `map_in`, i.e. 'myport'.
- If a string, `map_out[thru]` is set to the key in `map_in` (i.e. 'myport').
An error is raised if that entry already exists.
This makes it easy to extend a pattern with simple 2-port devices
(e.g. wires) without providing `map_out` each time `plug` is (e.g. wires) without providing `map_out` each time `plug` is
called. See "Examples" above for more info. Default `True`. called. See "Examples" above for more info. Default `True`.
set_rotation: If the necessary rotation cannot be determined from set_rotation: If the necessary rotation cannot be determined from
@ -265,7 +269,7 @@ class RenderPather(PortList, PatherMixin):
map_in = map_in, map_in = map_in,
map_out = map_out, map_out = map_out,
mirrored = mirrored, mirrored = mirrored,
inherit_name=inherit_name, thru = thru,
set_rotation = set_rotation, set_rotation = set_rotation,
append = append, append = append,
) )

View File

@ -1202,7 +1202,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
# map_out: dict[str, str | None] | None, # map_out: dict[str, str | None] | None,
# *, # *,
# mirrored: bool, # mirrored: bool,
# inherit_name: bool, # thru: bool | str,
# set_rotation: bool | None, # set_rotation: bool | None,
# append: Literal[False], # append: Literal[False],
# ) -> Self: # ) -> Self:
@ -1216,7 +1216,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
# map_out: dict[str, str | None] | None, # map_out: dict[str, str | None] | None,
# *, # *,
# mirrored: bool, # mirrored: bool,
# inherit_name: bool, # thru: bool | str,
# set_rotation: bool | None, # set_rotation: bool | None,
# append: bool, # append: bool,
# ) -> Self: # ) -> Self:
@ -1229,7 +1229,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
map_out: dict[str, str | None] | None = None, map_out: dict[str, str | None] | None = None,
*, *,
mirrored: bool = False, mirrored: bool = False,
inherit_name: bool = True, thru: bool | str = True,
set_rotation: bool | None = None, set_rotation: bool | None = None,
append: bool = False, append: bool = False,
ok_connections: Iterable[tuple[str, str]] = (), ok_connections: Iterable[tuple[str, str]] = (),
@ -1250,7 +1250,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
- `my_pat.plug(wire, {'myport': 'A'})` places port 'A' of `wire` at 'myport' - `my_pat.plug(wire, {'myport': 'A'})` places port 'A' of `wire` at 'myport'
of `my_pat`. of `my_pat`.
If `wire` has only two ports (e.g. 'A' and 'B'), no `map_out` argument is If `wire` has only two ports (e.g. 'A' and 'B'), no `map_out` argument is
provided, and the `inherit_name` argument is not explicitly set to `False`, provided, and the `thru` argument is not explicitly set to `False`,
the unconnected port of `wire` is automatically renamed to 'myport'. This the unconnected port of `wire` is automatically renamed to 'myport'. This
allows easy extension of existing ports without changing their names or allows easy extension of existing ports without changing their names or
having to provide `map_out` each time `plug` is called. having to provide `map_out` each time `plug` is called.
@ -1263,11 +1263,15 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
new names for ports in `other`. new names for ports in `other`.
mirrored: Enables mirroring `other` across the x axis prior to connecting mirrored: Enables mirroring `other` across the x axis prior to connecting
any ports. any ports.
inherit_name: If `True`, and `map_in` specifies only a single port, thru: If map_in specifies only a single port, `thru` provides a mechainsm
and `map_out` is `None`, and `other` has only two ports total, to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`,
then automatically renames the output port of `other` to the - If True (default), and `other` has only two ports total, and map_out
name of the port from `self` that appears in `map_in`. This doesn't specify a name for the other port, its name is set to the key
makes it easy to extend a pattern with simple 2-port devices in `map_in`, i.e. 'myport'.
- If a string, `map_out[thru]` is set to the key in `map_in` (i.e. 'myport').
An error is raised if that entry already exists.
This makes it easy to extend a pattern with simple 2-port devices
(e.g. wires) without providing `map_out` each time `plug` is (e.g. wires) without providing `map_out` each time `plug` is
called. See "Examples" above for more info. Default `True`. called. See "Examples" above for more info. Default `True`.
set_rotation: If the necessary rotation cannot be determined from set_rotation: If the necessary rotation cannot be determined from
@ -1295,18 +1299,25 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
`PortError` if the specified port mapping is not achieveable (the ports `PortError` if the specified port mapping is not achieveable (the ports
do not line up) do not line up)
""" """
# If asked to inherit a name, check that all conditions are met
if (inherit_name
and not map_out
and len(map_in) == 1
and len(other.ports) == 2):
out_port_name = next(iter(set(other.ports.keys()) - set(map_in.values())))
map_out = {out_port_name: next(iter(map_in.keys()))}
if map_out is None: if map_out is None:
map_out = {} map_out = {}
map_out = copy.deepcopy(map_out) map_out = copy.deepcopy(map_out)
# If asked to inherit a name, check that all conditions are met
if isinstance(thru, str):
if not len(map_in) == 1:
raise PatternError(f'Got {thru=} but have multiple map_in entries; don\'t know which one to use')
if thru in map_out:
raise PatternError(f'Got {thru=} but tha port already exists in map_out')
map_out[thru] = next(iter(map_in.keys()))
elif (bool(thru)
and len(map_in) == 1
and not map_out
and len(other.ports) == 2
):
out_port_name = next(iter(set(other.ports.keys()) - set(map_in.values())))
map_out = {out_port_name: next(iter(map_in.keys()))}
self.check_ports(other.ports.keys(), map_in, map_out) self.check_ports(other.ports.keys(), map_in, map_out)
translation, rotation, pivot = self.find_transform( translation, rotation, pivot = self.find_transform(
other, other,

View File

@ -56,6 +56,7 @@ dxf = ["ezdxf~=1.0.2"]
svg = ["svgwrite"] svg = ["svgwrite"]
visualize = ["matplotlib"] visualize = ["matplotlib"]
text = ["matplotlib", "freetype-py"] text = ["matplotlib", "freetype-py"]
manhatanize_slow = ["float_raster"]
[tool.ruff] [tool.ruff]