Move plug/place/interface to Pattern
Since Pattern has ports already, these should live in Pattern and get wrapped elsewhere. Builder becomes a context-holder (holding .library and .dead) and some code duplication goes away.
This commit is contained in:
parent
4af9493840
commit
f6bfd3b638
@ -18,39 +18,44 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class Builder(PortList):
|
class Builder(PortList):
|
||||||
"""
|
"""
|
||||||
TODO DOCUMENT Builder
|
A `Builder` is a helper object used for snapping together multiple
|
||||||
A `Device` is a combination of a `Pattern` with a set of named `Port`s
|
lower-level patterns at their `Port`s.
|
||||||
which can be used to "snap" devices together to make complex layouts.
|
|
||||||
|
|
||||||
`Device`s can be as simple as one or two ports (e.g. an electrical pad
|
The `Builder` mostly just holds context, in the form of a `Library`,
|
||||||
or wire), but can also be used to build and represent a large routed
|
in addition to its underlying pattern. This simplifies some calls
|
||||||
layout (e.g. a logical block with multiple I/O connections or even a
|
to `plug` and `place`, by making the library implicit.
|
||||||
full chip).
|
|
||||||
|
|
||||||
For convenience, ports can be read out using square brackets:
|
`Builder` can also be `set_dead()`, at which point further calls to `plug()`
|
||||||
- `device['A'] == Port((0, 0), 0)`
|
and `place()` are ignored (intended for debugging).
|
||||||
- `device[['A', 'B']] == {'A': Port((0, 0), 0), 'B': Port((0, 0), pi)}`
|
|
||||||
|
|
||||||
Examples: Creating a Device
|
|
||||||
|
Examples: Creating a Builder
|
||||||
===========================
|
===========================
|
||||||
- `Device(pattern, ports={'A': port_a, 'C': port_c})` uses an existing
|
- `Builder(library, ports={'A': port_a, 'C': port_c}, name='mypat')` makes
|
||||||
pattern and defines some ports.
|
an empty pattern, adds the given ports, and places it into `library`
|
||||||
|
under the name `'mypat'`.
|
||||||
|
|
||||||
- `Device(ports=None)` makes a new empty pattern with
|
- `Builder(library)` makes an empty pattern with no ports. The pattern
|
||||||
default ports ('A' and 'B', in opposite directions, at (0, 0)).
|
is not added into `library` and must later be added with e.g.
|
||||||
|
`library['mypat'] = builder.pattern`
|
||||||
|
|
||||||
- `my_device.build('my_layout')` makes a new pattern and instantiates
|
- `Builder(library, pattern=pattern, name='mypat')` uses an existing
|
||||||
`my_device` in it with offset (0, 0) as a base for further building.
|
pattern (including its ports) and sets `library['mypat'] = pattern`.
|
||||||
|
|
||||||
- `my_device.as_interface('my_component', port_map=['A', 'B'])` makes a new
|
- `Builder.interface(other_pat, port_map=['A', 'B'], library=library)`
|
||||||
(empty) pattern, copies over ports 'A' and 'B' from `my_device`, and
|
makes a new (empty) pattern, copies over ports 'A' and 'B' from
|
||||||
creates additional ports 'in_A' and 'in_B' facing in the opposite
|
`other_pat`, and creates additional ports 'in_A' and 'in_B' facing
|
||||||
directions. This can be used to build a device which can plug into
|
in the opposite directions. This can be used to build a device which
|
||||||
`my_device` (using the 'in_*' ports) but which does not itself include
|
can plug into `other_pat` (using the 'in_*' ports) but which does not
|
||||||
`my_device` as a subcomponent.
|
itself include `other_pat` as a subcomponent.
|
||||||
|
|
||||||
Examples: Adding to a Device
|
- `Builder.interface(other_builder, ...)` does the same thing as
|
||||||
============================
|
`Builder.interface(other_builder.pattern, ...)` but also uses
|
||||||
|
`other_builder.library` as its library by default.
|
||||||
|
|
||||||
|
|
||||||
|
Examples: Adding to a pattern
|
||||||
|
=============================
|
||||||
- `my_device.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})`
|
- `my_device.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})`
|
||||||
instantiates `subdevice` into `my_device`, plugging ports 'A' and 'B'
|
instantiates `subdevice` into `my_device`, plugging ports 'A' and 'B'
|
||||||
of `my_device` into ports 'C' and 'B' of `subdevice`. The connected ports
|
of `my_device` into ports 'C' and 'B' of `subdevice`. The connected ports
|
||||||
@ -75,10 +80,9 @@ class Builder(PortList):
|
|||||||
pattern: Pattern
|
pattern: Pattern
|
||||||
""" Layout of this device """
|
""" Layout of this device """
|
||||||
|
|
||||||
library: ILibrary | None
|
library: ILibrary
|
||||||
"""
|
"""
|
||||||
Library from which existing patterns should be referenced, and to which
|
Library from which patterns should be referenced
|
||||||
new ones should be added
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_dead: bool
|
_dead: bool
|
||||||
@ -94,7 +98,7 @@ class Builder(PortList):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
library: ILibrary | None = None,
|
library: ILibrary,
|
||||||
*,
|
*,
|
||||||
pattern: Pattern | None = None,
|
pattern: Pattern | None = None,
|
||||||
ports: str | Mapping[str, Port] | None = None,
|
ports: str | Mapping[str, Port] | None = None,
|
||||||
@ -114,15 +118,11 @@ class Builder(PortList):
|
|||||||
if self.pattern.ports:
|
if self.pattern.ports:
|
||||||
raise BuildError('Ports supplied for pattern with pre-existing ports!')
|
raise BuildError('Ports supplied for pattern with pre-existing ports!')
|
||||||
if isinstance(ports, str):
|
if isinstance(ports, str):
|
||||||
if library is None:
|
|
||||||
raise BuildError('Ports given as a string, but `library` was `None`!')
|
|
||||||
ports = library.abstract(ports).ports
|
ports = library.abstract(ports).ports
|
||||||
|
|
||||||
self.pattern.ports.update(copy.deepcopy(dict(ports)))
|
self.pattern.ports.update(copy.deepcopy(dict(ports)))
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
if library is None:
|
|
||||||
raise BuildError('Name was supplied, but no library was given!')
|
|
||||||
library[name] = self.pattern
|
library[name] = self.pattern
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -137,31 +137,15 @@ class Builder(PortList):
|
|||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
) -> 'Builder':
|
) -> 'Builder':
|
||||||
"""
|
"""
|
||||||
Begin building a new device based on all or some of the ports in the
|
Wrapper for `Pattern.interface()`, which returns a Builder instead.
|
||||||
source device. Do not include the source device; instead use it
|
|
||||||
to define ports (the "interface") for the new device.
|
|
||||||
|
|
||||||
The ports specified by `port_map` (default: all ports) are copied to
|
|
||||||
new device, and additional (input) ports are created facing in the
|
|
||||||
opposite directions. The specified `in_prefix` and `out_prefix` are
|
|
||||||
prepended to the port names to differentiate them.
|
|
||||||
|
|
||||||
By default, the flipped ports are given an 'in_' prefix and unflipped
|
|
||||||
ports keep their original names, enabling intuitive construction of
|
|
||||||
a device that will "plug into" the current device; the 'in_*' ports
|
|
||||||
are used for plugging the devices together while the original port
|
|
||||||
names are used for building the new device.
|
|
||||||
|
|
||||||
Another use-case could be to build the new device using the 'in_'
|
|
||||||
ports, creating a new device which could be used in place of the
|
|
||||||
current device.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
source: A collection of ports (e.g. Pattern, Builder, or dict)
|
source: A collection of ports (e.g. Pattern, Builder, or dict)
|
||||||
from which to create the interface.
|
from which to create the interface. May be a pattern name if
|
||||||
library: Library from which existing patterns should be referenced, TODO
|
`library` is provided.
|
||||||
and to which new ones should be added. If not provided,
|
library: Library from which existing patterns should be referenced,
|
||||||
the source's library will be used (if available).
|
and to which the new one should be added (if named). If not provided,
|
||||||
|
`source.library` must exist and will be used.
|
||||||
in_prefix: Prepended to port names for newly-created ports with
|
in_prefix: Prepended to port names for newly-created ports with
|
||||||
reversed directions compared to the current device.
|
reversed directions compared to the current device.
|
||||||
out_prefix: Prepended to port names for ports which are directly
|
out_prefix: Prepended to port names for ports which are directly
|
||||||
@ -185,72 +169,16 @@ class Builder(PortList):
|
|||||||
if library is None:
|
if library is None:
|
||||||
if hasattr(source, 'library') and isinstance(source.library, ILibrary):
|
if hasattr(source, 'library') and isinstance(source.library, ILibrary):
|
||||||
library = source.library
|
library = source.library
|
||||||
|
else:
|
||||||
|
raise BuildError('No library was given, and `source.library` does not have one either.')
|
||||||
|
|
||||||
if isinstance(source, str):
|
if isinstance(source, str):
|
||||||
if library is None:
|
source = library.abstract(source).ports
|
||||||
raise BuildError('Source given as a string, but `library` was `None`!')
|
|
||||||
orig_ports = library.abstract(source).ports
|
|
||||||
elif isinstance(source, PortList):
|
|
||||||
orig_ports = source.ports
|
|
||||||
elif isinstance(source, dict):
|
|
||||||
orig_ports = source
|
|
||||||
else:
|
|
||||||
raise BuildError(f'Unable to get ports from {type(source)}: {source}')
|
|
||||||
|
|
||||||
if port_map:
|
pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map)
|
||||||
if isinstance(port_map, dict):
|
new = Builder(library=library, pattern=pat, name=name)
|
||||||
missing_inkeys = set(port_map.keys()) - set(orig_ports.keys())
|
|
||||||
mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map}
|
|
||||||
else:
|
|
||||||
port_set = set(port_map)
|
|
||||||
missing_inkeys = port_set - set(orig_ports.keys())
|
|
||||||
mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set}
|
|
||||||
|
|
||||||
if missing_inkeys:
|
|
||||||
raise PortError(f'`port_map` keys not present in source: {missing_inkeys}')
|
|
||||||
else:
|
|
||||||
mapped_ports = orig_ports
|
|
||||||
|
|
||||||
ports_in = {f'{in_prefix}{name}': port.deepcopy().rotate(pi)
|
|
||||||
for name, port in mapped_ports.items()}
|
|
||||||
ports_out = {f'{out_prefix}{name}': port.deepcopy()
|
|
||||||
for name, port in mapped_ports.items()}
|
|
||||||
|
|
||||||
duplicates = set(ports_out.keys()) & set(ports_in.keys())
|
|
||||||
if duplicates:
|
|
||||||
raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
|
|
||||||
|
|
||||||
new = Builder(library=library, ports={**ports_in, **ports_out}, name=name)
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
# @overload
|
|
||||||
# def plug(
|
|
||||||
# self,
|
|
||||||
# other: Abstract | str,
|
|
||||||
# map_in: dict[str, str],
|
|
||||||
# map_out: dict[str, str | None] | None,
|
|
||||||
# *,
|
|
||||||
# mirrored: bool = False,
|
|
||||||
# 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: bool = False,
|
|
||||||
# inherit_name: bool = True,
|
|
||||||
# set_rotation: bool | None = None,
|
|
||||||
# append: bool = False,
|
|
||||||
# ) -> Self:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def plug(
|
def plug(
|
||||||
self,
|
self,
|
||||||
other: Abstract | str | Pattern,
|
other: Abstract | str | Pattern,
|
||||||
@ -263,34 +191,18 @@ class Builder(PortList):
|
|||||||
append: bool = False,
|
append: bool = False,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""
|
"""
|
||||||
Instantiate or append a pattern into the current device, connecting
|
Wrapper around `Pattern.plug` which allows a string for `other`.
|
||||||
the ports specified by `map_in` and renaming the unconnected
|
The `Builder`'s library is used to dereference the string (or `Abstract`, if
|
||||||
ports specified by `map_out`.
|
one is passed with `append=True`).
|
||||||
|
|
||||||
Examples:
|
|
||||||
=========
|
|
||||||
- `my_device.plug(lib, 'subdevice', {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})`
|
|
||||||
instantiates `lib['subdevice']` into `my_device`, plugging ports 'A' and 'B'
|
|
||||||
of `my_device` into ports 'C' and 'B' of `subdevice`. The connected ports
|
|
||||||
are removed and any unconnected ports from `subdevice` are added to
|
|
||||||
`my_device`. Port 'D' of `subdevice` (unconnected) is renamed to 'myport'.
|
|
||||||
|
|
||||||
- `my_device.plug(lib, 'wire', {'myport': 'A'})` places port 'A' of `lib['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 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.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
other: An `Abstract` describing the device to be instatiated.
|
other: An `Abstract`, string, or `Pattern` describing the device to be instatiated.
|
||||||
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
|
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
|
||||||
port connections between the two devices.
|
port connections between the two devices.
|
||||||
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
|
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
|
||||||
new names for ports in `other`.
|
new names for ports in `other`.
|
||||||
mirrored: Enables mirroring `other` across the x or y axes prior
|
mirrored: Enables mirroring `other` across the x axis prior to
|
||||||
to connecting any ports.
|
connecting any ports.
|
||||||
inherit_name: If `True`, and `map_in` specifies only a single port,
|
inherit_name: If `True`, and `map_in` specifies only a single port,
|
||||||
and `map_out` is `None`, and `other` has only two ports total,
|
and `map_out` is `None`, and `other` has only two ports total,
|
||||||
then automatically renames the output port of `other` to the
|
then automatically renames the output port of `other` to the
|
||||||
@ -303,6 +215,9 @@ class Builder(PortList):
|
|||||||
port with `rotation=None`), `set_rotation` must be provided
|
port with `rotation=None`), `set_rotation` must be provided
|
||||||
to indicate how much `other` should be rotated. Otherwise,
|
to indicate how much `other` should be rotated. Otherwise,
|
||||||
`set_rotation` must remain `None`.
|
`set_rotation` must remain `None`.
|
||||||
|
append: If `True`, `other` is appended instead of being referenced.
|
||||||
|
Note that this does not flatten `other`, so its refs will still
|
||||||
|
be refs (now inside `self`).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
@ -320,72 +235,21 @@ class Builder(PortList):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
if self.library is None:
|
|
||||||
raise BuildError('No library available, but `other` was a string!')
|
|
||||||
other = self.library.abstract(other)
|
other = self.library.abstract(other)
|
||||||
|
if append and isinstance(other, Abstract):
|
||||||
|
other = self.library[other.name]
|
||||||
|
|
||||||
# If asked to inherit a name, check that all conditions are met
|
self.pattern.plug(
|
||||||
if (inherit_name
|
other=other,
|
||||||
and not map_out
|
map_in=map_in,
|
||||||
and len(map_in) == 1
|
map_out=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()))}
|
|
||||||
|
|
||||||
if map_out is None:
|
|
||||||
map_out = {}
|
|
||||||
map_out = copy.deepcopy(map_out)
|
|
||||||
|
|
||||||
self.check_ports(other.ports.keys(), map_in, map_out)
|
|
||||||
translation, rotation, pivot = self.find_transform(
|
|
||||||
other,
|
|
||||||
map_in,
|
|
||||||
mirrored=mirrored,
|
mirrored=mirrored,
|
||||||
|
inherit_name=inherit_name,
|
||||||
set_rotation=set_rotation,
|
set_rotation=set_rotation,
|
||||||
|
append=append,
|
||||||
)
|
)
|
||||||
|
|
||||||
# get rid of plugged ports
|
|
||||||
for ki, vi in map_in.items():
|
|
||||||
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, append=append)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# @overload
|
|
||||||
# def place(
|
|
||||||
# self,
|
|
||||||
# other: Abstract | str,
|
|
||||||
# *,
|
|
||||||
# offset: ArrayLike,
|
|
||||||
# rotation: float,
|
|
||||||
# pivot: ArrayLike,
|
|
||||||
# mirrored: bool = False,
|
|
||||||
# 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: bool = False,
|
|
||||||
# port_map: dict[str, str | None] | None,
|
|
||||||
# skip_port_check: bool,
|
|
||||||
# append: Literal[True],
|
|
||||||
# ) -> Self:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def place(
|
def place(
|
||||||
self,
|
self,
|
||||||
other: Abstract | str | Pattern,
|
other: Abstract | str | Pattern,
|
||||||
@ -440,52 +304,20 @@ class Builder(PortList):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
if self.library is None:
|
|
||||||
raise BuildError('No library available, but `other` was a string!')
|
|
||||||
other = self.library.abstract(other)
|
other = self.library.abstract(other)
|
||||||
|
if append and isinstance(other, Abstract):
|
||||||
|
other = self.library[other.name]
|
||||||
|
|
||||||
if port_map is None:
|
self.pattern.place(
|
||||||
port_map = {}
|
other=other,
|
||||||
|
offset=offset,
|
||||||
if not skip_port_check:
|
rotation=rotation,
|
||||||
self.check_ports(other.ports.keys(), map_in=None, map_out=port_map)
|
pivot=pivot,
|
||||||
|
mirrored=mirrored,
|
||||||
ports = {}
|
port_map=port_map,
|
||||||
for name, port in other.ports.items():
|
skip_port_check=skip_port_check,
|
||||||
new_name = port_map.get(name, name)
|
append=append,
|
||||||
if new_name is None:
|
)
|
||||||
continue
|
|
||||||
ports[new_name] = port
|
|
||||||
|
|
||||||
for name, port in ports.items():
|
|
||||||
p = port.deepcopy()
|
|
||||||
if mirrored:
|
|
||||||
p.mirror()
|
|
||||||
p.rotate_around(pivot, rotation)
|
|
||||||
p.translate(offset)
|
|
||||||
self.ports[name] = p
|
|
||||||
|
|
||||||
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()
|
|
||||||
if mirrored:
|
|
||||||
other_copy.mirror()
|
|
||||||
other_copy.rotate_around(pivot, rotation)
|
|
||||||
other_copy.translate_elements(offset)
|
|
||||||
self.pattern.append(other_copy)
|
|
||||||
else:
|
|
||||||
assert not isinstance(other, Pattern)
|
|
||||||
ref = Ref(mirrored=mirrored)
|
|
||||||
ref.rotate_around(pivot, rotation)
|
|
||||||
ref.translate(offset)
|
|
||||||
self.pattern.refs[other.name].append(ref)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def translate(self, offset: ArrayLike) -> Self:
|
def translate(self, offset: ArrayLike) -> Self:
|
||||||
|
@ -149,14 +149,19 @@ class Pather(Builder):
|
|||||||
cls,
|
cls,
|
||||||
builder: Builder,
|
builder: Builder,
|
||||||
*,
|
*,
|
||||||
library: ILibrary | None = None,
|
|
||||||
tools: Tool | MutableMapping[str | None, Tool] | None = None,
|
tools: Tool | MutableMapping[str | None, Tool] | None = None,
|
||||||
) -> 'Pather':
|
) -> 'Pather':
|
||||||
"""TODO from_builder docs"""
|
"""
|
||||||
library = library if library is not None else builder.library
|
Construct a `Pather` by adding tools to a `Builder`.
|
||||||
if library is None:
|
|
||||||
raise BuildError('No library available for Pather!')
|
Args:
|
||||||
new = Pather(library=library, tools=tools, pattern=builder.pattern)
|
builder: Builder to turn into a Pather
|
||||||
|
tools: Tools for the `Pather`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Pather object, using `builder.library` and `builder.pattern`.
|
||||||
|
"""
|
||||||
|
new = Pather(library=builder.library, tools=tools, pattern=builder.pattern)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -183,17 +188,11 @@ class Pather(Builder):
|
|||||||
if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict):
|
if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict):
|
||||||
tools = source.tools
|
tools = source.tools
|
||||||
|
|
||||||
new = Pather.from_builder(
|
if isinstance(source, str):
|
||||||
Builder.interface(
|
source = library.abstract(source).ports
|
||||||
source=source,
|
|
||||||
library=library,
|
pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map)
|
||||||
in_prefix=in_prefix,
|
new = Pather(library=library, pattern=pat, name=name, tools=tools)
|
||||||
out_prefix=out_prefix,
|
|
||||||
port_map=port_map,
|
|
||||||
name=name,
|
|
||||||
),
|
|
||||||
tools=tools,
|
|
||||||
)
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
@ -9,7 +9,7 @@ from numpy.typing import ArrayLike
|
|||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ref import Ref
|
from ..ref import Ref
|
||||||
from ..library import ILibrary
|
from ..library import ILibrary, Library
|
||||||
from ..error import PortError, BuildError
|
from ..error import PortError, BuildError
|
||||||
from ..ports import PortList, Port
|
from ..ports import PortList, Port
|
||||||
from ..abstract import Abstract
|
from ..abstract import Abstract
|
||||||
@ -28,7 +28,7 @@ class RenderPather(PortList):
|
|||||||
pattern: Pattern
|
pattern: Pattern
|
||||||
""" Layout of this device """
|
""" Layout of this device """
|
||||||
|
|
||||||
library: ILibrary | None
|
library: ILibrary
|
||||||
""" Library from which patterns should be referenced """
|
""" Library from which patterns should be referenced """
|
||||||
|
|
||||||
_dead: bool
|
_dead: bool
|
||||||
@ -52,7 +52,7 @@ class RenderPather(PortList):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
library: ILibrary | None = None,
|
library: ILibrary,
|
||||||
*,
|
*,
|
||||||
pattern: Pattern | None = None,
|
pattern: Pattern | None = None,
|
||||||
ports: str | Mapping[str, Port] | None = None,
|
ports: str | Mapping[str, Port] | None = None,
|
||||||
@ -99,6 +99,7 @@ class RenderPather(PortList):
|
|||||||
source: PortList | Mapping[str, Port] | str,
|
source: PortList | Mapping[str, Port] | str,
|
||||||
*,
|
*,
|
||||||
library: ILibrary | None = None,
|
library: ILibrary | None = None,
|
||||||
|
tools: Tool | MutableMapping[str | None, Tool] | None = None,
|
||||||
in_prefix: str = 'in_',
|
in_prefix: str = 'in_',
|
||||||
out_prefix: str = '',
|
out_prefix: str = '',
|
||||||
port_map: dict[str, str] | Sequence[str] | None = None,
|
port_map: dict[str, str] | Sequence[str] | None = None,
|
||||||
@ -154,42 +155,17 @@ class RenderPather(PortList):
|
|||||||
if library is None:
|
if library is None:
|
||||||
if hasattr(source, 'library') and isinstance(source.library, ILibrary):
|
if hasattr(source, 'library') and isinstance(source.library, ILibrary):
|
||||||
library = source.library
|
library = source.library
|
||||||
|
else:
|
||||||
|
raise BuildError('No library provided (and not present in `source.library`')
|
||||||
|
|
||||||
|
if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict):
|
||||||
|
tools = source.tools
|
||||||
|
|
||||||
if isinstance(source, str):
|
if isinstance(source, str):
|
||||||
if library is None:
|
source = library.abstract(source).ports
|
||||||
raise BuildError('Source given as a string, but `library` was `None`!')
|
|
||||||
orig_ports = library.abstract(source).ports
|
|
||||||
elif isinstance(source, PortList):
|
|
||||||
orig_ports = source.ports
|
|
||||||
elif isinstance(source, dict):
|
|
||||||
orig_ports = source
|
|
||||||
else:
|
|
||||||
raise BuildError(f'Unable to get ports from {type(source)}: {source}')
|
|
||||||
|
|
||||||
if port_map:
|
pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map)
|
||||||
if isinstance(port_map, dict):
|
new = RenderPather(library=library, pattern=pat, name=name, tools=tools)
|
||||||
missing_inkeys = set(port_map.keys()) - set(orig_ports.keys())
|
|
||||||
mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map}
|
|
||||||
else:
|
|
||||||
port_set = set(port_map)
|
|
||||||
missing_inkeys = port_set - set(orig_ports.keys())
|
|
||||||
mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set}
|
|
||||||
|
|
||||||
if missing_inkeys:
|
|
||||||
raise PortError(f'`port_map` keys not present in source: {missing_inkeys}')
|
|
||||||
else:
|
|
||||||
mapped_ports = orig_ports
|
|
||||||
|
|
||||||
ports_in = {f'{in_prefix}{pname}': port.deepcopy().rotate(pi)
|
|
||||||
for pname, port in mapped_ports.items()}
|
|
||||||
ports_out = {f'{out_prefix}{pname}': port.deepcopy()
|
|
||||||
for pname, port in mapped_ports.items()}
|
|
||||||
|
|
||||||
duplicates = set(ports_out.keys()) & set(ports_in.keys())
|
|
||||||
if duplicates:
|
|
||||||
raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
|
|
||||||
|
|
||||||
new = RenderPather(library=library, ports={**ports_in, **ports_out}, name=name)
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def plug(
|
def plug(
|
||||||
@ -201,44 +177,41 @@ class RenderPather(PortList):
|
|||||||
mirrored: bool = False,
|
mirrored: bool = False,
|
||||||
inherit_name: bool = True,
|
inherit_name: bool = True,
|
||||||
set_rotation: bool | None = None,
|
set_rotation: bool | None = None,
|
||||||
|
append: bool = False,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
if self._dead:
|
if self._dead:
|
||||||
logger.error('Skipping plug() since device is dead')
|
logger.error('Skipping plug() since device is dead')
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
other_tgt: Pattern | Abstract
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
if self.library is None:
|
other_tgt = self.library.abstract(other)
|
||||||
raise BuildError('No library available, but `other` was a string!')
|
if append and isinstance(other, Abstract):
|
||||||
other = self.library.abstract(other)
|
other_tgt = self.library[other.name]
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
self.check_ports(other.ports.keys(), map_in, map_out)
|
|
||||||
translation, rotation, pivot = self.find_transform(
|
|
||||||
other,
|
|
||||||
map_in,
|
|
||||||
mirrored=mirrored,
|
|
||||||
set_rotation=set_rotation,
|
|
||||||
)
|
|
||||||
|
|
||||||
# get rid of plugged ports
|
# get rid of plugged ports
|
||||||
for ki, vi in map_in.items():
|
for kk in map_in.keys():
|
||||||
if ki in self.paths:
|
if kk in self.paths:
|
||||||
self.paths[ki].append(RenderStep('P', None, self.ports[ki].copy(), self.ports[ki].copy(), None))
|
self.paths[kk].append(RenderStep('P', None, self.ports[kk].copy(), self.ports[kk].copy(), None))
|
||||||
del self.ports[ki]
|
|
||||||
map_out[vi] = None
|
plugged = map_in.values()
|
||||||
self.place(other, offset=translation, rotation=rotation, pivot=pivot,
|
for name, port in other_tgt.ports.items():
|
||||||
mirrored=mirrored, port_map=map_out, skip_port_check=True)
|
if name in plugged:
|
||||||
|
continue
|
||||||
|
new_name = map_out.get(name, name) if map_out is not None else name
|
||||||
|
if new_name is not None and new_name in self.paths:
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def place(
|
def place(
|
||||||
@ -251,43 +224,34 @@ class RenderPather(PortList):
|
|||||||
mirrored: bool = False,
|
mirrored: bool = False,
|
||||||
port_map: dict[str, str | None] | None = None,
|
port_map: dict[str, str | None] | None = None,
|
||||||
skip_port_check: bool = False,
|
skip_port_check: bool = False,
|
||||||
|
append: bool = False,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
if self._dead:
|
if self._dead:
|
||||||
logger.error('Skipping place() since device is dead')
|
logger.error('Skipping place() since device is dead')
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
other_tgt: Pattern | Abstract
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
if self.library is None:
|
other_tgt = self.library.abstract(other)
|
||||||
raise BuildError('No library available, but `other` was a string!')
|
if append and isinstance(other, Abstract):
|
||||||
other = self.library.abstract(other)
|
other_tgt = self.library[other.name]
|
||||||
|
|
||||||
if port_map is None:
|
for name, port in other_tgt.ports.items():
|
||||||
port_map = {}
|
new_name = port_map.get(name, name) if port_map is not None else name
|
||||||
|
if new_name is not None and new_name in self.paths:
|
||||||
if not skip_port_check:
|
|
||||||
self.check_ports(other.ports.keys(), map_in=None, map_out=port_map)
|
|
||||||
|
|
||||||
ports = {}
|
|
||||||
for name, port in other.ports.items():
|
|
||||||
new_name = port_map.get(name, name)
|
|
||||||
if new_name is None:
|
|
||||||
continue
|
|
||||||
ports[new_name] = port
|
|
||||||
if new_name in self.paths:
|
|
||||||
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
|
self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
|
||||||
|
|
||||||
for name, port in ports.items():
|
self.pattern.place(
|
||||||
p = port.deepcopy()
|
other=other_tgt,
|
||||||
if mirrored:
|
offset=offset,
|
||||||
p.mirror()
|
rotation=rotation,
|
||||||
p.rotate_around(pivot, rotation)
|
pivot=pivot,
|
||||||
p.translate(offset)
|
mirrored=mirrored,
|
||||||
self.ports[name] = p
|
port_map=port_map,
|
||||||
|
skip_port_check=skip_port_check,
|
||||||
|
append=append,
|
||||||
|
)
|
||||||
|
|
||||||
ref = Ref(mirrored=mirrored)
|
|
||||||
ref.rotate_around(pivot, rotation)
|
|
||||||
ref.translate(offset)
|
|
||||||
self.pattern.refs[other.name].append(ref)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def retool(
|
def retool(
|
||||||
@ -409,22 +373,32 @@ class RenderPather(PortList):
|
|||||||
|
|
||||||
def render(
|
def render(
|
||||||
self,
|
self,
|
||||||
lib: ILibrary | None = None,
|
|
||||||
append: bool = True,
|
append: bool = True,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
lib = lib if lib is not None else self.library
|
"""
|
||||||
assert lib is not None
|
Generate the geometry which has been planned out with `path`/`path_to`/etc.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
append: If `True`, the rendered geometry will be directly appended to
|
||||||
|
`self.pattern`. Note that it will not be flattened, so if only one
|
||||||
|
layer of hierarchy is eliminated.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
"""
|
||||||
|
lib = self.library
|
||||||
tool_port_names = ('A', 'B')
|
tool_port_names = ('A', 'B')
|
||||||
bb = Builder(lib)
|
pat = Pattern()
|
||||||
|
|
||||||
def render_batch(lib: ILibrary, portspec: str, batch: list[RenderStep], append: bool) -> None:
|
def render_batch(portspec: str, batch: list[RenderStep], append: bool) -> None:
|
||||||
assert batch[0].tool is not None
|
assert batch[0].tool is not None
|
||||||
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
|
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
|
||||||
bb.ports[portspec] = batch[0].start_port.copy()
|
pat.ports[portspec] = batch[0].start_port.copy()
|
||||||
bb.plug(name, {portspec: tool_port_names[0]}, append=append)
|
|
||||||
if append:
|
if append:
|
||||||
del lib[name]
|
pat.plug(lib[name], {portspec: tool_port_names[0]}, append=append)
|
||||||
|
del lib[name] # NOTE if the rendered pattern has refs, those are now in `pat` but not flattened
|
||||||
|
else:
|
||||||
|
pat.plug(lib.abstract(name), {portspec: tool_port_names[0]}, append=append)
|
||||||
|
|
||||||
for portspec, steps in self.paths.items():
|
for portspec, steps in self.paths.items():
|
||||||
batch: list[RenderStep] = []
|
batch: list[RenderStep] = []
|
||||||
@ -434,7 +408,7 @@ class RenderPather(PortList):
|
|||||||
|
|
||||||
# If we can't continue a batch, render it
|
# If we can't continue a batch, render it
|
||||||
if batch and (not appendable_op or not same_tool):
|
if batch and (not appendable_op or not same_tool):
|
||||||
render_batch(lib, portspec, batch, append)
|
render_batch(portspec, batch, append)
|
||||||
batch = []
|
batch = []
|
||||||
|
|
||||||
# batch is emptied already if we couldn't continue it
|
# batch is emptied already if we couldn't continue it
|
||||||
@ -442,16 +416,16 @@ class RenderPather(PortList):
|
|||||||
batch.append(step)
|
batch.append(step)
|
||||||
|
|
||||||
# Opcodes which break the batch go below this line
|
# Opcodes which break the batch go below this line
|
||||||
if not appendable_op and portspec in bb.ports:
|
if not appendable_op and portspec in pat.ports:
|
||||||
del bb.ports[portspec]
|
del pat.ports[portspec]
|
||||||
|
|
||||||
#If the last batch didn't end yet
|
#If the last batch didn't end yet
|
||||||
if batch:
|
if batch:
|
||||||
render_batch(lib, portspec, batch, append)
|
render_batch(portspec, batch, append)
|
||||||
|
|
||||||
self.paths.clear()
|
self.paths.clear()
|
||||||
bb.ports.clear()
|
pat.ports.clear()
|
||||||
self.pattern.append(bb.pattern)
|
self.pattern.append(pat)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -14,10 +14,11 @@ from numpy.typing import NDArray, ArrayLike
|
|||||||
# .visualize imports matplotlib and matplotlib.collections
|
# .visualize imports matplotlib and matplotlib.collections
|
||||||
|
|
||||||
from .ref import Ref
|
from .ref import Ref
|
||||||
|
from .abstract import Abstract
|
||||||
from .shapes import Shape, Polygon, Path, DEFAULT_POLY_NUM_VERTICES
|
from .shapes import Shape, Polygon, Path, DEFAULT_POLY_NUM_VERTICES
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .utils import rotation_matrix_2d, annotations_t, layer_t
|
from .utils import rotation_matrix_2d, annotations_t, layer_t
|
||||||
from .error import PatternError
|
from .error import PatternError, PortError
|
||||||
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable, Bounded
|
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable, Bounded
|
||||||
from .ports import Port, PortList
|
from .ports import Port, PortList
|
||||||
|
|
||||||
@ -860,6 +861,346 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
|||||||
pyplot.ylabel('y')
|
pyplot.ylabel('y')
|
||||||
pyplot.show()
|
pyplot.show()
|
||||||
|
|
||||||
|
# @overload
|
||||||
|
# def place(
|
||||||
|
# self,
|
||||||
|
# other: Pattern,
|
||||||
|
# *,
|
||||||
|
# offset: ArrayLike,
|
||||||
|
# rotation: float,
|
||||||
|
# pivot: ArrayLike,
|
||||||
|
# mirrored: bool,
|
||||||
|
# port_map: dict[str, str | None] | None,
|
||||||
|
# skip_port_check: bool,
|
||||||
|
# append: bool,
|
||||||
|
# ) -> Self:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# @overload
|
||||||
|
# def place(
|
||||||
|
# self,
|
||||||
|
# other: Abstract,
|
||||||
|
# *,
|
||||||
|
# offset: ArrayLike,
|
||||||
|
# rotation: float,
|
||||||
|
# pivot: ArrayLike,
|
||||||
|
# mirrored: bool,
|
||||||
|
# port_map: dict[str, str | None] | None,
|
||||||
|
# skip_port_check: bool,
|
||||||
|
# append: Literal[False],
|
||||||
|
# ) -> Self:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
def place(
|
||||||
|
self,
|
||||||
|
other: Abstract | Pattern,
|
||||||
|
*,
|
||||||
|
offset: ArrayLike = (0, 0),
|
||||||
|
rotation: float = 0,
|
||||||
|
pivot: ArrayLike = (0, 0),
|
||||||
|
mirrored: bool = False,
|
||||||
|
port_map: dict[str, str | None] | None = None,
|
||||||
|
skip_port_check: bool = False,
|
||||||
|
append: bool = False,
|
||||||
|
) -> Self:
|
||||||
|
"""
|
||||||
|
Instantiate or append the pattern `other` into the current pattern, adding its
|
||||||
|
ports to those of the current pattern (but not connecting/removing any ports).
|
||||||
|
|
||||||
|
Mirroring is applied before rotation; translation (`offset`) is applied last.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
=========
|
||||||
|
- `my_pat.place(pad_pat, offset=(10, 10), rotation=pi / 2, port_map={'A': 'gnd'})`
|
||||||
|
instantiates `pad` at the specified (x, y) offset and with the specified
|
||||||
|
rotation, adding its ports to those of `my_pat`. Port 'A' of `pad` is
|
||||||
|
renamed to 'gnd' so that further routing can use this signal or net name
|
||||||
|
rather than the port name on the original `pad_pat` pattern.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
other: An `Abstract` or `Pattern` describing the device to be instatiated.
|
||||||
|
offset: Offset at which to place the instance. Default (0, 0).
|
||||||
|
rotation: Rotation applied to the instance before placement. Default 0.
|
||||||
|
pivot: Rotation is applied around this pivot point (default (0, 0)).
|
||||||
|
Rotation is applied prior to translation (`offset`).
|
||||||
|
mirrored: Whether theinstance should be mirrored across the x axis.
|
||||||
|
Mirroring is applied before translation and rotation.
|
||||||
|
port_map: dict of `{'old_name': 'new_name'}` mappings, specifying
|
||||||
|
new names for ports in the instantiated pattern. New names can be
|
||||||
|
`None`, which will delete those ports.
|
||||||
|
skip_port_check: Can be used to skip the internal call to `check_ports`,
|
||||||
|
in case it has already been performed elsewhere.
|
||||||
|
append: If `True`, `other` is appended instead of being referenced.
|
||||||
|
Note that this does not flatten `other`, so its refs will still
|
||||||
|
be refs (now inside `self`).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
`PortError` if any ports specified in `map_in` or `map_out` do not
|
||||||
|
exist in `self.ports` or `other.ports`.
|
||||||
|
`PortError` if there are any duplicate names after `map_in` and `map_out`
|
||||||
|
are applied.
|
||||||
|
"""
|
||||||
|
if port_map is None:
|
||||||
|
port_map = {}
|
||||||
|
|
||||||
|
if not skip_port_check:
|
||||||
|
self.check_ports(other.ports.keys(), map_in=None, map_out=port_map)
|
||||||
|
|
||||||
|
ports = {}
|
||||||
|
for name, port in other.ports.items():
|
||||||
|
new_name = port_map.get(name, name)
|
||||||
|
if new_name is None:
|
||||||
|
continue
|
||||||
|
ports[new_name] = port
|
||||||
|
|
||||||
|
for name, port in ports.items():
|
||||||
|
p = port.deepcopy()
|
||||||
|
if mirrored:
|
||||||
|
p.mirror()
|
||||||
|
p.rotate_around(pivot, rotation)
|
||||||
|
p.translate(offset)
|
||||||
|
self.ports[name] = p
|
||||||
|
|
||||||
|
if append:
|
||||||
|
if isinstance(other, Abstract):
|
||||||
|
raise PatternError('Must provide a full `Pattern` (not an `Abstract`) when appending!')
|
||||||
|
other_copy = other.deepcopy()
|
||||||
|
other_copy.ports.clear()
|
||||||
|
if mirrored:
|
||||||
|
other_copy.mirror()
|
||||||
|
other_copy.rotate_around(pivot, rotation)
|
||||||
|
other_copy.translate_elements(offset)
|
||||||
|
self.append(other_copy)
|
||||||
|
else:
|
||||||
|
assert not isinstance(other, Pattern)
|
||||||
|
ref = Ref(mirrored=mirrored)
|
||||||
|
ref.rotate_around(pivot, rotation)
|
||||||
|
ref.translate(offset)
|
||||||
|
self.refs[other.name].append(ref)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# @overload
|
||||||
|
# def plug(
|
||||||
|
# self,
|
||||||
|
# other: Abstract,
|
||||||
|
# map_in: dict[str, str],
|
||||||
|
# map_out: dict[str, str | None] | None,
|
||||||
|
# *,
|
||||||
|
# mirrored: bool,
|
||||||
|
# inherit_name: bool,
|
||||||
|
# set_rotation: bool | None,
|
||||||
|
# append: Literal[False],
|
||||||
|
# ) -> Self:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# @overload
|
||||||
|
# def plug(
|
||||||
|
# self,
|
||||||
|
# other: Pattern,
|
||||||
|
# map_in: dict[str, str],
|
||||||
|
# map_out: dict[str, str | None] | None,
|
||||||
|
# *,
|
||||||
|
# mirrored: bool,
|
||||||
|
# inherit_name: bool,
|
||||||
|
# set_rotation: bool | None,
|
||||||
|
# append: bool,
|
||||||
|
# ) -> Self:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
def plug(
|
||||||
|
self,
|
||||||
|
other: Abstract | Pattern,
|
||||||
|
map_in: dict[str, str],
|
||||||
|
map_out: dict[str, str | None] | None = None,
|
||||||
|
*,
|
||||||
|
mirrored: bool = False,
|
||||||
|
inherit_name: bool = True,
|
||||||
|
set_rotation: bool | None = None,
|
||||||
|
append: bool = False,
|
||||||
|
) -> Self:
|
||||||
|
"""
|
||||||
|
Instantiate or append a pattern into the current pattern, connecting
|
||||||
|
the ports specified by `map_in` and renaming the unconnected
|
||||||
|
ports specified by `map_out`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
=========
|
||||||
|
- `my_pat.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})`
|
||||||
|
instantiates `subdevice` into `my_pat`, plugging ports 'A' and 'B'
|
||||||
|
of `my_pat` into ports 'C' and 'B' of `subdevice`. The connected ports
|
||||||
|
are removed and any unconnected ports from `subdevice` are added to
|
||||||
|
`my_pat`. Port 'D' of `subdevice` (unconnected) is renamed to 'myport'.
|
||||||
|
|
||||||
|
- `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`,
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
other: A `Pattern` or `Abstract` describing the subdevice to be instatiated.
|
||||||
|
map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
|
||||||
|
port connections between the current pattern and the subdevice.
|
||||||
|
map_out: dict of `{'old_name': 'new_name'}` mappings, specifying
|
||||||
|
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
|
||||||
|
(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
|
||||||
|
the ports being connected (i.e. all pairs have at least one
|
||||||
|
port with `rotation=None`), `set_rotation` must be provided
|
||||||
|
to indicate how much `other` should be rotated. Otherwise,
|
||||||
|
`set_rotation` must remain `None`.
|
||||||
|
append: If `True`, `other` is appended instead of being referenced.
|
||||||
|
Note that this does not flatten `other`, so its refs will still
|
||||||
|
be refs (now inside `self`).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
`PortError` if any ports specified in `map_in` or `map_out` do not
|
||||||
|
exist in `self.ports` or `other_names`.
|
||||||
|
`PortError` if there are any duplicate names after `map_in` and `map_out`
|
||||||
|
are applied.
|
||||||
|
`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)
|
||||||
|
|
||||||
|
self.check_ports(other.ports.keys(), map_in, map_out)
|
||||||
|
translation, rotation, pivot = self.find_transform(
|
||||||
|
other,
|
||||||
|
map_in,
|
||||||
|
mirrored=mirrored,
|
||||||
|
set_rotation=set_rotation,
|
||||||
|
)
|
||||||
|
|
||||||
|
# get rid of plugged ports
|
||||||
|
for ki, vi in map_in.items():
|
||||||
|
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,
|
||||||
|
append=append,
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def interface(
|
||||||
|
cls,
|
||||||
|
source: PortList | Mapping[str, Port],
|
||||||
|
*,
|
||||||
|
in_prefix: str = 'in_',
|
||||||
|
out_prefix: str = '',
|
||||||
|
port_map: dict[str, str] | Sequence[str] | None = None,
|
||||||
|
) -> 'Pattern':
|
||||||
|
"""
|
||||||
|
Generate an empty pattern with ports based on all or some of the ports
|
||||||
|
in the `source`. Do not include the source device istelf; instead use
|
||||||
|
it to define ports (the "interface") for the new device.
|
||||||
|
|
||||||
|
The ports specified by `port_map` (default: all ports) are copied to
|
||||||
|
new device, and additional (input) ports are created facing in the
|
||||||
|
opposite directions. The specified `in_prefix` and `out_prefix` are
|
||||||
|
prepended to the port names to differentiate them.
|
||||||
|
|
||||||
|
By default, the flipped ports are given an 'in_' prefix and unflipped
|
||||||
|
ports keep their original names, enabling intuitive construction of
|
||||||
|
a device that will "plug into" the current device; the 'in_*' ports
|
||||||
|
are used for plugging the devices together while the original port
|
||||||
|
names are used for building the new device.
|
||||||
|
|
||||||
|
Another use-case could be to build the new device using the 'in_'
|
||||||
|
ports, creating a new device which could be used in place of the
|
||||||
|
current device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: A collection of ports (e.g. Pattern, Builder, or dict)
|
||||||
|
from which to create the interface.
|
||||||
|
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
|
||||||
|
copied from the current device.
|
||||||
|
port_map: Specification for ports to copy into the new device:
|
||||||
|
- If `None`, all ports are copied.
|
||||||
|
- If a sequence, only the listed ports are copied
|
||||||
|
- If a mapping, the listed ports (keys) are copied and
|
||||||
|
renamed (to the values).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The new empty pattern, with 2x as many ports as listed in port_map.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
`PortError` if `port_map` contains port names not present in the
|
||||||
|
current device.
|
||||||
|
`PortError` if applying the prefixes results in duplicate port
|
||||||
|
names.
|
||||||
|
"""
|
||||||
|
if isinstance(source, PortList):
|
||||||
|
orig_ports = source.ports
|
||||||
|
elif isinstance(source, dict):
|
||||||
|
orig_ports = source
|
||||||
|
else:
|
||||||
|
raise PatternError(f'Unable to get ports from {type(source)}: {source}')
|
||||||
|
|
||||||
|
if port_map:
|
||||||
|
if isinstance(port_map, dict):
|
||||||
|
missing_inkeys = set(port_map.keys()) - set(orig_ports.keys())
|
||||||
|
mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map}
|
||||||
|
else:
|
||||||
|
port_set = set(port_map)
|
||||||
|
missing_inkeys = port_set - set(orig_ports.keys())
|
||||||
|
mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set}
|
||||||
|
|
||||||
|
if missing_inkeys:
|
||||||
|
raise PortError(f'`port_map` keys not present in source: {missing_inkeys}')
|
||||||
|
else:
|
||||||
|
mapped_ports = orig_ports
|
||||||
|
|
||||||
|
ports_in = {f'{in_prefix}{name}': port.deepcopy().rotate(pi)
|
||||||
|
for name, port in mapped_ports.items()}
|
||||||
|
ports_out = {f'{out_prefix}{name}': port.deepcopy()
|
||||||
|
for name, port in mapped_ports.items()}
|
||||||
|
|
||||||
|
duplicates = set(ports_out.keys()) & set(ports_in.keys())
|
||||||
|
if duplicates:
|
||||||
|
raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
|
||||||
|
|
||||||
|
new = Pattern(ports={**ports_in, **ports_out})
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
TT = TypeVar('TT')
|
TT = TypeVar('TT')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user