Compare commits
9 Commits
eeca5666f4
...
9026103b51
Author | SHA1 | Date | |
---|---|---|---|
9026103b51 | |||
4ae98a94ed | |||
808766f5a9 | |||
973b70ee07 | |||
aa839facdc | |||
837f42b9ed | |||
4699d5c14f | |||
ccd8a2270a | |||
a82b1d4dcf |
107
README.md
107
README.md
@ -73,6 +73,7 @@ Each `Pattern` can contain `Ref`s pointing at other patterns, `Shape`s, and `Lab
|
||||
|
||||
## Glossary
|
||||
- `Library`: A collection of named cells. OASIS or GDS "library" or file.
|
||||
- "tree": Any Library which has only one topcell.
|
||||
- `Pattern`: A collection of geometry, text labels, and reference to other patterns.
|
||||
OASIS or GDS "Cell", DXF "Block".
|
||||
- `Ref`: A reference to another pattern. GDS "AREF/SREF", OASIS "Placement".
|
||||
@ -83,6 +84,112 @@ Each `Pattern` can contain `Ref`s pointing at other patterns, `Shape`s, and `Lab
|
||||
- `annotation`: Additional metadata. OASIS or GDS "property".
|
||||
|
||||
|
||||
## Syntax, shorthand, and design patterns
|
||||
Most syntax and behavior should follow normal python conventions.
|
||||
There are a few exceptions, either meant to catch common mistakes or to provide a shorthand for common operations:
|
||||
|
||||
### `Library` objects don't allow overwriting already-existing patterns
|
||||
```python3
|
||||
library['mycell'] = pattern0
|
||||
library['mycell'] = pattern1 # Error! 'mycell' already exists and can't be overwritten
|
||||
del library['mycell'] # We can explicitly delete it
|
||||
library['mycell'] = pattern1 # And now it's ok to assign a new value
|
||||
library.delete('mycell') # This also deletes all refs pointing to 'mycell' by default
|
||||
```
|
||||
|
||||
### Insert a newly-made hierarchical pattern (with children) into a layout
|
||||
```python3
|
||||
# Let's say we have a function which returns a new library containing one topcell (and possibly children)
|
||||
tree = make_tree(...)
|
||||
|
||||
# To reference this cell in our layout, we have to add all its children to our `library` first:
|
||||
top_name = tree.top() # get the name of the topcell
|
||||
name_mapping = library.add(tree) # add all patterns from `tree`, renaming elgible conflicting patterns
|
||||
new_name = name_mapping.get(top_name, top_name) # get the new name for the cell (in case it was auto-renamed)
|
||||
my_pattern.ref(new_name, ...) # instantiate the cell
|
||||
|
||||
# This can be accomplished as follows
|
||||
new_name = library << tree # Add `tree` into `library` and return the top cell's new name
|
||||
my_pattern.ref(new_name, ...) # instantiate the cell
|
||||
|
||||
# In practice, you may do lots of
|
||||
my_pattern.ref(lib << make_tree(...), ...)
|
||||
```
|
||||
|
||||
We can also use this shorthand to quickly add and reference a single flat (as yet un-named) pattern:
|
||||
```python3
|
||||
anonymous_pattern = Pattern(...)
|
||||
my_pattern.ref(lib << {'_tentative_name': anonymous_pattern}, ...)
|
||||
```
|
||||
|
||||
### Place a hierarchical pattern into a layout, preserving its port info
|
||||
```python3
|
||||
# As above, we have a function that makes a new library containing one topcell (and possibly children)
|
||||
tree = make_tree(...)
|
||||
|
||||
# We need to go get its port info to `place()` it into our existing layout,
|
||||
new_name = library << tree # Add the tree to the library and return its name (see `<<` above)
|
||||
abstract = library.abstract(tree) # An `Abstract` stores a pattern's name and its ports (but no geometry)
|
||||
my_pattern.place(abstract, ...)
|
||||
|
||||
# With shorthand,
|
||||
abstract = library <= tree
|
||||
my_pattern.place(abstract, ...)
|
||||
|
||||
# or
|
||||
my_pattern.place(library << make_tree(...), ...)
|
||||
|
||||
|
||||
### Quickly add geometry, labels, or refs:
|
||||
The long form for adding elements can be overly verbose:
|
||||
```python3
|
||||
my_pattern.shapes[layer].append(Polygon(vertices, ...))
|
||||
my_pattern.labels[layer] += [Label('my text')]
|
||||
my_pattern.refs[target_name].append(Ref(offset=..., ...))
|
||||
```
|
||||
|
||||
There is shorthand for the most common elements:
|
||||
```python3
|
||||
my_pattern.polygon(layer=layer, vertices=vertices, ...)
|
||||
my_pattern.rect(layer=layer, xctr=..., xmin=..., ymax=..., ly=...) # rectangle; pick 4 of 6 constraints
|
||||
my_pattern.rect(layer=layer, ymin=..., ymax=..., xctr=..., lx=...)
|
||||
my_pattern.path(...)
|
||||
my_pattern.label(layer, 'my_text')
|
||||
my_pattern.ref(target_name, offset=..., ...)
|
||||
```
|
||||
|
||||
### Accessing ports
|
||||
```python3
|
||||
# Square brackets pull from the underlying `.ports` dict:
|
||||
assert pattern['input'] is pattern.ports['input']
|
||||
|
||||
# And you can use them to read multiple ports at once:
|
||||
assert pattern[('input', 'output')] == {
|
||||
'input': pattern.ports['input'],
|
||||
'output': pattern.ports['output'],
|
||||
}
|
||||
|
||||
# But you shouldn't use them for anything except reading
|
||||
pattern['input'] = Port(...) # Error!
|
||||
has_input = ('input' in pattern) # Error!
|
||||
```
|
||||
|
||||
### Building patterns
|
||||
```python3
|
||||
library = Library(...)
|
||||
my_pattern_name, my_pattern = library.mkpat(some_name_generator())
|
||||
...
|
||||
def _make_my_subpattern() -> str:
|
||||
# This function can draw from the outer scope (e.g. `library`) but will not pollute the outer scope
|
||||
# (e.g. the variable `subpattern` will not be accessible from outside the function; you must load it
|
||||
# from within `library`).
|
||||
subpattern_name, subpattern = library.mkpat(...)
|
||||
subpattern.rect(...)
|
||||
...
|
||||
return subpattern_name
|
||||
my_pattern.ref(_make_my_subpattern(), offset=..., ...)
|
||||
```
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
|
@ -1,17 +1,15 @@
|
||||
"""
|
||||
Simplified Pattern assembly (`Builder`)
|
||||
"""
|
||||
from typing import Self, Sequence, Mapping, Literal, overload
|
||||
from typing import Self, Sequence, Mapping
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from numpy import pi
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from ..pattern import Pattern
|
||||
from ..ref import Ref
|
||||
from ..library import ILibrary
|
||||
from ..error import PortError, BuildError
|
||||
from ..error import BuildError
|
||||
from ..ports import PortList, Port
|
||||
from ..abstract import Abstract
|
||||
|
||||
|
@ -324,10 +324,10 @@ class Pather(Builder):
|
||||
|
||||
tool = self.tools.get(portspec, self.tools[None])
|
||||
in_ptype = self.pattern[portspec].ptype
|
||||
pat = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
|
||||
tree = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
|
||||
name = self.library.get_name(base_name)
|
||||
self.library[name] = pat
|
||||
return self.plug(Abstract(name, pat.ports), {portspec: tool_port_names[0]})
|
||||
abstract = self.library << tree.rename_top(name)
|
||||
return self.plug(abstract, {portspec: tool_port_names[0]})
|
||||
|
||||
def path_to(
|
||||
self,
|
||||
@ -399,12 +399,12 @@ class Pather(Builder):
|
||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||
if is_horizontal:
|
||||
if y is not None:
|
||||
raise BuildError(f'Asked to path to y-coordinate, but port is horizontal')
|
||||
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(f'Asked to path to x-coordinate, but port is vertical')
|
||||
raise BuildError('Asked to path to x-coordinate, but port is vertical')
|
||||
if position is None:
|
||||
position = y
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
"""
|
||||
Pather with batched (multi-step) rendering
|
||||
"""
|
||||
from typing import Self, Sequence, Mapping, MutableMapping
|
||||
import copy
|
||||
import logging
|
||||
@ -9,15 +12,13 @@ from numpy import pi
|
||||
from numpy.typing import ArrayLike
|
||||
|
||||
from ..pattern import Pattern
|
||||
from ..ref import Ref
|
||||
from ..library import ILibrary, Library
|
||||
from ..library import ILibrary
|
||||
from ..error import PortError, BuildError
|
||||
from ..ports import PortList, Port
|
||||
from ..abstract import Abstract
|
||||
from ..utils import SupportsBool
|
||||
from .tools import Tool, RenderStep
|
||||
from .utils import ell
|
||||
from .builder import Builder
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -488,12 +489,12 @@ class RenderPather(PortList):
|
||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||
if is_horizontal:
|
||||
if y is not None:
|
||||
raise BuildError(f'Asked to path to y-coordinate, but port is horizontal')
|
||||
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(f'Asked to path to x-coordinate, but port is vertical')
|
||||
raise BuildError('Asked to path to x-coordinate, but port is vertical')
|
||||
if position is None:
|
||||
position = y
|
||||
|
||||
|
@ -17,16 +17,30 @@ from ..pattern import Pattern
|
||||
from ..abstract import Abstract
|
||||
from ..library import ILibrary, Library
|
||||
from ..error import BuildError
|
||||
from .builder import Builder
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RenderStep:
|
||||
"""
|
||||
Representation of a single saved operation, used by `RenderPather` and passed
|
||||
to `Tool.render()` when `RenderPather.render()` is called.
|
||||
"""
|
||||
opcode: Literal['L', 'S', 'U', 'P']
|
||||
""" What operation is being performed.
|
||||
L: planL (straight, optionally with a single bend)
|
||||
S: planS (s-bend)
|
||||
U: planU (u-bend)
|
||||
P: plug
|
||||
"""
|
||||
|
||||
tool: 'Tool | None'
|
||||
""" The current tool. May be `None` if `opcode='P'` """
|
||||
|
||||
start_port: Port
|
||||
end_port: Port
|
||||
|
||||
data: Any
|
||||
""" Arbitrary tool-specific data"""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.opcode != 'P' and self.tool is None:
|
||||
@ -34,6 +48,13 @@ class RenderStep:
|
||||
|
||||
|
||||
class Tool:
|
||||
"""
|
||||
Interface for path (e.g. wire or waveguide) generation.
|
||||
|
||||
Note that subclasses may implement only a subset of the methods and leave others
|
||||
unimplemented (e.g. in cases where they don't make sense or the required components
|
||||
are impractical or unavailable).
|
||||
"""
|
||||
def path(
|
||||
self,
|
||||
ccw: SupportsBool | None,
|
||||
@ -43,7 +64,40 @@ class Tool:
|
||||
out_ptype: str | None = None,
|
||||
port_names: tuple[str, str] = ('A', 'B'),
|
||||
**kwargs,
|
||||
) -> Pattern:
|
||||
) -> Library:
|
||||
"""
|
||||
Create a wire or waveguide that travels exactly `length` distance along the axis
|
||||
of its input port.
|
||||
|
||||
Used by `Pather`.
|
||||
|
||||
The output port must be exactly `length` away along the input port's axis, but
|
||||
may be placed an additional (unspecified) distance away along the perpendicular
|
||||
direction. The output port should be rotated (or not) based on the value of
|
||||
`ccw`.
|
||||
|
||||
The input and output ports should be compatible with `in_ptype` and
|
||||
`out_ptype`, respectively. They should also be named `port_names[0]` and
|
||||
`port_names[1]`, respectively.
|
||||
|
||||
Args:
|
||||
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.
|
||||
(There may be a tool-dependent offset along the other axis.)
|
||||
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
|
||||
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
|
||||
port_names: The output pattern will have its input port named `port_names[0]` and
|
||||
its output named `port_names[1]`.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
|
||||
Returns:
|
||||
A pattern tree containing the requested L-shaped (or straight) wire or waveguide
|
||||
|
||||
Raises:
|
||||
BuildError if an impossible or unsupported geometry is requested.
|
||||
"""
|
||||
raise NotImplementedError(f'path() not implemented for {type(self)}')
|
||||
|
||||
def planL(
|
||||
@ -55,11 +109,41 @@ class Tool:
|
||||
out_ptype: str | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[Port, Any]:
|
||||
"""
|
||||
Plan a wire or waveguide that travels exactly `length` distance along the axis
|
||||
of its input port.
|
||||
|
||||
Used by `RenderPather`.
|
||||
|
||||
The output port must be exactly `length` away along the input port's axis, but
|
||||
may be placed an additional (unspecified) distance away along the perpendicular
|
||||
direction. The output port should be rotated (or not) based on the value of
|
||||
`ccw`.
|
||||
|
||||
The input and output ports should be compatible with `in_ptype` and
|
||||
`out_ptype`, respectively.
|
||||
|
||||
Args:
|
||||
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.
|
||||
(There may be a tool-dependent offset along the other axis.)
|
||||
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
|
||||
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
|
||||
Returns:
|
||||
The calculated output `Port` for the wire.
|
||||
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering.
|
||||
|
||||
Raises:
|
||||
BuildError if an impossible or unsupported geometry is requested.
|
||||
"""
|
||||
raise NotImplementedError(f'planL() not implemented for {type(self)}')
|
||||
|
||||
def planS(
|
||||
self,
|
||||
ccw: SupportsBool | None,
|
||||
length: float,
|
||||
jog: float,
|
||||
*,
|
||||
@ -67,17 +151,91 @@ class Tool:
|
||||
out_ptype: str | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[Port, Any]:
|
||||
"""
|
||||
Plan a wire or waveguide that travels exactly `length` distance along the axis
|
||||
of its input port and `jog` distance along the perpendicular axis (i.e. an S-bend).
|
||||
|
||||
Used by `RenderPather`.
|
||||
|
||||
The output port must have an orientation rotated by pi from the input port.
|
||||
|
||||
The input and output ports should be compatible with `in_ptype` and
|
||||
`out_ptype`, respectively.
|
||||
|
||||
Args:
|
||||
length: The total distance from input to output, along the input's axis only.
|
||||
jog: The total offset from the input to output, along the perpendicular axis.
|
||||
A positive number implies a rightwards shift (i.e. clockwise bend followed
|
||||
by a counterclockwise bend)
|
||||
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
|
||||
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
|
||||
Returns:
|
||||
The calculated output `Port` for the wire.
|
||||
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering.
|
||||
|
||||
Raises:
|
||||
BuildError if an impossible or unsupported geometry is requested.
|
||||
"""
|
||||
raise NotImplementedError(f'planS() not implemented for {type(self)}')
|
||||
|
||||
def planU(
|
||||
self,
|
||||
jog: float,
|
||||
*,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
**kwargs,
|
||||
) -> tuple[Port, Any]:
|
||||
"""
|
||||
# NOTE: TODO: U-bend is WIP; this interface may change in the future.
|
||||
|
||||
Plan a wire or waveguide that travels exactly `jog` distance along the axis
|
||||
perpendicular to its input port (i.e. a U-bend).
|
||||
|
||||
Used by `RenderPather`.
|
||||
|
||||
The output port must have an orientation identical to the input port.
|
||||
|
||||
The input and output ports should be compatible with `in_ptype` and
|
||||
`out_ptype`, respectively.
|
||||
|
||||
Args:
|
||||
jog: The total offset from the input to output, along the perpendicular axis.
|
||||
A positive number implies a rightwards shift (i.e. clockwise bend followed
|
||||
by a counterclockwise bend)
|
||||
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
|
||||
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
|
||||
Returns:
|
||||
The calculated output `Port` for the wire.
|
||||
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering.
|
||||
|
||||
Raises:
|
||||
BuildError if an impossible or unsupported geometry is requested.
|
||||
"""
|
||||
raise NotImplementedError(f'planU() not implemented for {type(self)}')
|
||||
|
||||
def render(
|
||||
self,
|
||||
batch: Sequence[RenderStep],
|
||||
*,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
port_names: Sequence[str] = ('A', 'B'),
|
||||
**kwargs,
|
||||
) -> ILibrary:
|
||||
"""
|
||||
Render the provided `batch` of `RenderStep`s into geometry, returning a tree
|
||||
(a Library with a single topcell).
|
||||
|
||||
Args:
|
||||
batch: A sequence of `RenderStep` objects containing the ports and data
|
||||
provided by this tool's `planL`/`planS`/`planU` functions.
|
||||
port_names: The topcell's input and output ports should be named
|
||||
`port_names[0]` and `port_names[1]` respectively.
|
||||
kwargs: Custom tool-specific parameters.
|
||||
"""
|
||||
assert not batch or batch[0].tool == self
|
||||
raise NotImplementedError(f'render() not implemented for {type(self)}')
|
||||
|
||||
@ -87,13 +245,26 @@ abstract_tuple_t = tuple[Abstract, str, str]
|
||||
|
||||
@dataclass
|
||||
class BasicTool(Tool, metaclass=ABCMeta):
|
||||
"""
|
||||
A simple tool which relies on a single pre-rendered `bend` pattern, a function
|
||||
for generating straight paths, and a table of pre-rendered `transitions` for converting
|
||||
from non-native ptypes.
|
||||
"""
|
||||
straight: tuple[Callable[[float], Pattern], str, str]
|
||||
""" `create_straight(length: float), in_port_name, out_port_name` """
|
||||
|
||||
bend: abstract_tuple_t # Assumed to be clockwise
|
||||
""" `clockwise_bend_abstract, in_port_name, out_port_name` """
|
||||
|
||||
transitions: dict[str, abstract_tuple_t]
|
||||
""" `{ptype: (transition_abstract`, ptype_port_name, other_port_name), ...}` """
|
||||
|
||||
default_out_ptype: str
|
||||
""" Default value for out_ptype """
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LData:
|
||||
""" Data for planL """
|
||||
straight_length: float
|
||||
ccw: SupportsBool | None
|
||||
in_transition: abstract_tuple_t | None
|
||||
@ -108,7 +279,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
||||
out_ptype: str | None = None,
|
||||
port_names: tuple[str, str] = ('A', 'B'),
|
||||
**kwargs,
|
||||
) -> Pattern:
|
||||
) -> Library:
|
||||
_out_port, data = self.planL(
|
||||
ccw,
|
||||
length,
|
||||
@ -117,22 +288,22 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
||||
)
|
||||
|
||||
gen_straight, sport_in, sport_out = self.straight
|
||||
tree = Library()
|
||||
bb = Builder(library=tree, name='_path').add_port_pair(names=port_names)
|
||||
tree, pat = Library.mktree('_path')
|
||||
pat.add_port_pair(names=port_names)
|
||||
if data.in_transition:
|
||||
ipat, iport_theirs, _iport_ours = data.in_transition
|
||||
bb.plug(ipat, {port_names[1]: iport_theirs})
|
||||
pat.plug(ipat, {port_names[1]: iport_theirs})
|
||||
if not numpy.isclose(data.straight_length, 0):
|
||||
straight = tree << {'_straight': gen_straight(data.straight_length)}
|
||||
bb.plug(straight, {port_names[1]: sport_in})
|
||||
straight = tree <= {'_straight': gen_straight(data.straight_length)}
|
||||
pat.plug(straight, {port_names[1]: sport_in})
|
||||
if data.ccw is not None:
|
||||
bend, bport_in, bport_out = self.bend
|
||||
bb.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
|
||||
pat.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
|
||||
if data.out_transition:
|
||||
opat, oport_theirs, oport_ours = data.out_transition
|
||||
bb.plug(opat, {port_names[1]: oport_ours})
|
||||
pat.plug(opat, {port_names[1]: oport_ours})
|
||||
|
||||
return bb.pattern
|
||||
return tree
|
||||
|
||||
def planL(
|
||||
self,
|
||||
@ -220,8 +391,8 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
||||
**kwargs,
|
||||
) -> ILibrary:
|
||||
|
||||
tree = Library()
|
||||
bb = Builder(library=tree, name='_path').add_port_pair(names=(port_names[0], port_names[1]))
|
||||
tree, pat = Library.mktree('_path')
|
||||
pat.add_port_pair(names=(port_names[0], port_names[1]))
|
||||
|
||||
gen_straight, sport_in, _sport_out = self.straight
|
||||
for step in batch:
|
||||
@ -231,28 +402,39 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
||||
if step.opcode == 'L':
|
||||
if in_transition:
|
||||
ipat, iport_theirs, _iport_ours = in_transition
|
||||
bb.plug(ipat, {port_names[1]: iport_theirs})
|
||||
pat.plug(ipat, {port_names[1]: iport_theirs})
|
||||
if not numpy.isclose(straight_length, 0):
|
||||
straight_pat = gen_straight(straight_length)
|
||||
if append:
|
||||
bb.plug(straight_pat, {port_names[1]: sport_in}, append=True)
|
||||
pat.plug(straight_pat, {port_names[1]: sport_in}, append=True)
|
||||
else:
|
||||
straight = tree << {'_straight': straight_pat}
|
||||
bb.plug(straight, {port_names[1]: sport_in}, append=True)
|
||||
straight = tree <= {'_straight': straight_pat}
|
||||
pat.plug(straight, {port_names[1]: sport_in}, append=True)
|
||||
if ccw is not None:
|
||||
bend, bport_in, bport_out = self.bend
|
||||
bb.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
|
||||
pat.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
|
||||
if out_transition:
|
||||
opat, oport_theirs, oport_ours = out_transition
|
||||
bb.plug(opat, {port_names[1]: oport_ours})
|
||||
pat.plug(opat, {port_names[1]: oport_ours})
|
||||
return tree
|
||||
|
||||
|
||||
@dataclass
|
||||
class PathTool(Tool, metaclass=ABCMeta):
|
||||
"""
|
||||
A tool which draws `Path` geometry elements.
|
||||
|
||||
If `planL` / `render` are used, the `Path` elements can cover >2 vertices;
|
||||
with `path` only individual rectangles will be drawn.
|
||||
"""
|
||||
layer: layer_t
|
||||
""" Layer to draw on """
|
||||
|
||||
width: float
|
||||
""" `Path` width """
|
||||
|
||||
ptype: str = 'unk'
|
||||
""" ptype for any ports in patterns generated by this tool """
|
||||
|
||||
#@dataclass(frozen=True, slots=True)
|
||||
#class LData:
|
||||
@ -273,7 +455,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
||||
out_ptype: str | None = None,
|
||||
port_names: tuple[str, str] = ('A', 'B'),
|
||||
**kwargs,
|
||||
) -> Pattern:
|
||||
) -> Library:
|
||||
out_port, dxy = self.planL(
|
||||
ccw,
|
||||
length,
|
||||
@ -281,7 +463,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
||||
out_ptype=out_ptype,
|
||||
)
|
||||
|
||||
pat = Pattern()
|
||||
tree, pat = Library.mktree('_path')
|
||||
pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)])
|
||||
|
||||
if ccw is None:
|
||||
@ -296,7 +478,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
||||
port_names[1]: Port(dxy, rotation=out_rot, ptype=self.ptype),
|
||||
}
|
||||
|
||||
return pat
|
||||
return tree
|
||||
|
||||
def planL(
|
||||
self,
|
||||
|
@ -122,6 +122,8 @@ def ell(
|
||||
orig_offsets = numpy.array([p.offset for p in ports.values()])
|
||||
rot_offsets = (rot_matrix @ orig_offsets.T).T
|
||||
|
||||
# ordering_base = rot_offsets.T * [[1], [-1 if ccw else 1]] # could work, but this is actually a more complex routing problem
|
||||
# y_order = numpy.lexsort(ordering_base) # (need to make sure we don't collide with the next input port @ same y)
|
||||
y_order = ((-1 if ccw else 1) * rot_offsets[:, 1]).argsort(kind='stable')
|
||||
y_ind = numpy.empty_like(y_order, dtype=int)
|
||||
y_ind[y_order] = numpy.arange(y_ind.shape[0])
|
||||
|
@ -765,7 +765,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
||||
|
||||
def prune_layers(self) -> Self:
|
||||
"""
|
||||
Removes empty layers (empty lists) in `self.shapes` and `self.labels`.
|
||||
Remove empty layers (empty lists) in `self.shapes` and `self.labels`.
|
||||
|
||||
Returns:
|
||||
self
|
||||
|
@ -1,6 +1,5 @@
|
||||
from typing import Any
|
||||
import copy
|
||||
import math
|
||||
|
||||
import numpy
|
||||
from numpy import pi
|
||||
@ -231,7 +230,7 @@ class Arc(Shape):
|
||||
|
||||
def get_thetas(inner: bool) -> NDArray[numpy.float64]:
|
||||
""" Figure out the parameter values at which we should place vertices to meet the arclength constraint"""
|
||||
dr = -self.width / 2.0 * (-1 if inner else 1)
|
||||
#dr = -self.width / 2.0 * (-1 if inner else 1)
|
||||
|
||||
n_pts = numpy.ceil(2 * pi * max(self.radii) / max_arclen).astype(int)
|
||||
arc_lengths, thetas = get_arclens(n_pts, *a_ranges[0 if inner else 1])
|
||||
|
@ -30,7 +30,7 @@ class Shape(PositionableImpl, Rotatable, Mirrorable, Copyable, Scalable,
|
||||
"""
|
||||
Class specifying functions common to all shapes.
|
||||
"""
|
||||
__slots__ = () # Children should use AutoSlots
|
||||
__slots__ = () # Children should use AutoSlots or set slots themselves
|
||||
|
||||
def __copy__(self) -> Self:
|
||||
cls = self.__class__
|
||||
|
Loading…
x
Reference in New Issue
Block a user