From 8a0c985e36158dadcb26d37ce3b06029ab7f594a Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:06:57 -0800 Subject: [PATCH 1/6] [plug()] rename `inherit_name` arg to `thru` and allow passing a string Breaking change Affects Pattern, Builder, Pather, RenderPather --- masque/builder/builder.py | 20 +++++++++------ masque/builder/renderpather.py | 16 +++++++----- masque/pattern.py | 46 +++++++++++++++++++++------------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/masque/builder/builder.py b/masque/builder/builder.py index 4494751..ee1d277 100644 --- a/masque/builder/builder.py +++ b/masque/builder/builder.py @@ -67,7 +67,7 @@ class Builder(PortList): - `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`, - 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 'myport'. This allows easy extension of existing ports without changing 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, *, mirrored: bool = False, - inherit_name: bool = True, + thru: bool | str = True, set_rotation: bool | None = None, append: bool = False, ok_connections: Iterable[tuple[str, str]] = (), @@ -246,11 +246,15 @@ class Builder(PortList): new names for ports in `other`. mirrored: Enables mirroring `other` across the x axis prior to connecting any ports. - inherit_name: If `True`, and `map_in` specifies only a single port, - and `map_out` is `None`, and `other` has only two ports total, - then automatically renames the output port of `other` to the - name of the port from `self` that appears in `map_in`. This - makes it easy to extend a device with simple 2-port devices + thru: If map_in specifies only a single port, `thru` provides a mechainsm + to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`, + - If True (default), and `other` has only two ports total, and map_out + doesn't specify a name for the other port, its name is set to the key + 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 called. See "Examples" above for more info. Default `True`. set_rotation: If the necessary rotation cannot be determined from @@ -296,7 +300,7 @@ class Builder(PortList): map_in = map_in, map_out = map_out, mirrored = mirrored, - inherit_name = inherit_name, + thru = thru, set_rotation = set_rotation, append = append, ok_connections = ok_connections, diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index eb78cb8..23c466a 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -193,7 +193,7 @@ class RenderPather(PortList, PatherMixin): map_out: dict[str, str | None] | None = None, *, mirrored: bool = False, - inherit_name: bool = True, + thru: bool | str = True, set_rotation: bool | None = None, append: bool = False, ) -> Self: @@ -210,11 +210,15 @@ class RenderPather(PortList, PatherMixin): new names for ports in `other`. mirrored: Enables mirroring `other` across the x axis prior to connecting any ports. - inherit_name: If `True`, and `map_in` specifies only a single port, - and `map_out` is `None`, and `other` has only two ports total, - then automatically renames the output port of `other` to the - name of the port from `self` that appears in `map_in`. This - makes it easy to extend a device with simple 2-port devices + thru: If map_in specifies only a single port, `thru` provides a mechainsm + to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`, + - If True (default), and `other` has only two ports total, and map_out + doesn't specify a name for the other port, its name is set to the key + 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 called. See "Examples" above for more info. Default `True`. set_rotation: If the necessary rotation cannot be determined from diff --git a/masque/pattern.py b/masque/pattern.py index fd29d5f..d801b5d 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1202,7 +1202,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): # map_out: dict[str, str | None] | None, # *, # mirrored: bool, -# inherit_name: bool, +# thru: bool | str, # set_rotation: bool | None, # append: Literal[False], # ) -> Self: @@ -1216,7 +1216,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): # map_out: dict[str, str | None] | None, # *, # mirrored: bool, -# inherit_name: bool, +# thru: bool | str, # set_rotation: bool | None, # append: bool, # ) -> Self: @@ -1229,7 +1229,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): map_out: dict[str, str | None] | None = None, *, mirrored: bool = False, - inherit_name: bool = True, + thru: bool | str = True, set_rotation: bool | None = None, append: bool = False, 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' of `my_pat`. 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 allows easy extension of existing ports without changing their names or 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`. mirrored: Enables mirroring `other` across the x axis prior to connecting any ports. - inherit_name: If `True`, and `map_in` specifies only a single port, - and `map_out` is `None`, and `other` has only two ports total, - then automatically renames the output port of `other` to the - name of the port from `self` that appears in `map_in`. This - makes it easy to extend a pattern with simple 2-port devices + thru: If map_in specifies only a single port, `thru` provides a mechainsm + to avoid repeating the port name. Eg, for `map_in={'myport': 'A'}`, + - If True (default), and `other` has only two ports total, and map_out + doesn't specify a name for the other port, its name is set to the key + 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 called. See "Examples" above for more info. Default `True`. set_rotation: If the necessary rotation cannot be determined from @@ -1295,18 +1299,26 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): `PortError` if the specified port mapping is not achieveable (the ports 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: 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) translation, rotation, pivot = self.find_transform( other, From 3a1a4b91268c4245b6d18b036d945a173fbec722 Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:07:07 -0800 Subject: [PATCH 2/6] [RenderPather] whitespace --- masque/builder/renderpather.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index 23c466a..682f9ec 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -265,13 +265,13 @@ class RenderPather(PortList, PatherMixin): self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None)) self.pattern.plug( - other=other_tgt, - map_in=map_in, - map_out=map_out, - mirrored=mirrored, - inherit_name=inherit_name, - set_rotation=set_rotation, - append=append, + other = other_tgt, + map_in = map_in, + map_out = map_out, + mirrored = mirrored, + thru = thru, + set_rotation = set_rotation, + append = append, ) return self @@ -337,14 +337,14 @@ class RenderPather(PortList, PatherMixin): self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None)) self.pattern.place( - other=other_tgt, - offset=offset, - rotation=rotation, - pivot=pivot, - mirrored=mirrored, - port_map=port_map, - skip_port_check=skip_port_check, - append=append, + other = other_tgt, + offset = offset, + rotation = rotation, + pivot = pivot, + mirrored = mirrored, + port_map = port_map, + skip_port_check = skip_port_check, + append = append, ) return self From dfd61b3a39c8c790251dc4d5c723e12ae7d94765 Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:07:36 -0800 Subject: [PATCH 3/6] fixup! [plug()] rename `inherit_name` arg to `thru` and allow passing a string --- masque/pattern.py | 1 - 1 file changed, 1 deletion(-) diff --git a/masque/pattern.py b/masque/pattern.py index d801b5d..7c5f8c3 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1318,7 +1318,6 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): 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) translation, rotation, pivot = self.find_transform( other, From 2b7b1cd6e2a233c9a9a33e5369f9e472d94541e4 Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:16:34 -0800 Subject: [PATCH 4/6] [PortPather] add PortPather --- masque/__init__.py | 1 + masque/builder/__init__.py | 1 + masque/builder/pather_mixin.py | 95 ++++++++++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/masque/__init__.py b/masque/__init__.py index 9dbf233..4ad7e69 100644 --- a/masque/__init__.py +++ b/masque/__init__.py @@ -80,6 +80,7 @@ from .builder import ( SimpleTool as SimpleTool, AutoTool as AutoTool, PathTool as PathTool, + PortPather as PortPather, ) from .utils import ( ports2data as ports2data, diff --git a/masque/builder/__init__.py b/masque/builder/__init__.py index eb5047f..2fd00a4 100644 --- a/masque/builder/__init__.py +++ b/masque/builder/__init__.py @@ -1,6 +1,7 @@ from .builder import Builder as Builder from .pather import Pather as Pather from .renderpather import RenderPather as RenderPather +from .pather_mixin import PortPather as PortPather from .utils import ell as ell from .tools import ( Tool as Tool, diff --git a/masque/builder/pather_mixin.py b/masque/builder/pather_mixin.py index 477c8aa..571f0bb 100644 --- a/masque/builder/pather_mixin.py +++ b/masque/builder/pather_mixin.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import Self, TYPE_CHECKING from collections.abc import Sequence, Iterator import logging from contextlib import contextmanager @@ -11,11 +11,15 @@ from numpy.typing import ArrayLike from ..pattern import Pattern from ..library import ILibrary from ..error import PortError, BuildError -from ..utils import rotation_matrix_2d, SupportsBool -#from ..abstract import Abstract +from ..utils import SupportsBool +from ..abstract import Abstract from .tools import Tool from .utils import ell +if TYPE_CHECKING: + from .pather import Pather + from .renderpather import RenderPather + logger = logging.getLogger(__name__) @@ -435,3 +439,88 @@ class PatherMixin(metaclass=ABCMeta): """ self.pattern.flatten(self.library) 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 From fcd3d9663d732294ae51f0d1e081d1499c8f05a6 Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:17:10 -0800 Subject: [PATCH 5/6] fixup! [plug()] rename `inherit_name` arg to `thru` and allow passing a string --- masque/pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masque/pattern.py b/masque/pattern.py index 7c5f8c3..4a401b6 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1305,7 +1305,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): # If asked to inherit a name, check that all conditions are met if isinstance(thru, str): - if not len(map_in == 1): + 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') From 7c5c1c26c8c503cc9d30e45e94386ad7f5739fe8 Mon Sep 17 00:00:00 2001 From: jan Date: Wed, 19 Nov 2025 00:17:38 -0800 Subject: [PATCH 6/6] add missing float_raster dep for manhattanize_slow --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 062098d..9a29065 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ dxf = ["ezdxf~=1.0.2"] svg = ["svgwrite"] visualize = ["matplotlib"] text = ["matplotlib", "freetype-py"] +manhatanize_slow = ["float_raster"] [tool.ruff]