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
|
## Glossary
|
||||||
- `Library`: A collection of named cells. OASIS or GDS "library" or file.
|
- `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.
|
- `Pattern`: A collection of geometry, text labels, and reference to other patterns.
|
||||||
OASIS or GDS "Cell", DXF "Block".
|
OASIS or GDS "Cell", DXF "Block".
|
||||||
- `Ref`: A reference to another pattern. GDS "AREF/SREF", OASIS "Placement".
|
- `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".
|
- `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
|
## TODO
|
||||||
|
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Simplified Pattern assembly (`Builder`)
|
Simplified Pattern assembly (`Builder`)
|
||||||
"""
|
"""
|
||||||
from typing import Self, Sequence, Mapping, Literal, overload
|
from typing import Self, Sequence, Mapping
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from numpy import pi
|
|
||||||
from numpy.typing import ArrayLike
|
from numpy.typing import ArrayLike
|
||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
from ..ref import Ref
|
|
||||||
from ..library import ILibrary
|
from ..library import ILibrary
|
||||||
from ..error import PortError, BuildError
|
from ..error import BuildError
|
||||||
from ..ports import PortList, Port
|
from ..ports import PortList, Port
|
||||||
from ..abstract import Abstract
|
from ..abstract import Abstract
|
||||||
|
|
||||||
|
@ -324,10 +324,10 @@ class Pather(Builder):
|
|||||||
|
|
||||||
tool = self.tools.get(portspec, self.tools[None])
|
tool = self.tools.get(portspec, self.tools[None])
|
||||||
in_ptype = self.pattern[portspec].ptype
|
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)
|
name = self.library.get_name(base_name)
|
||||||
self.library[name] = pat
|
abstract = self.library << tree.rename_top(name)
|
||||||
return self.plug(Abstract(name, pat.ports), {portspec: tool_port_names[0]})
|
return self.plug(abstract, {portspec: tool_port_names[0]})
|
||||||
|
|
||||||
def path_to(
|
def path_to(
|
||||||
self,
|
self,
|
||||||
@ -399,12 +399,12 @@ class Pather(Builder):
|
|||||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||||
if is_horizontal:
|
if is_horizontal:
|
||||||
if y is not None:
|
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:
|
if position is None:
|
||||||
position = x
|
position = x
|
||||||
else:
|
else:
|
||||||
if x is not None:
|
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:
|
if position is None:
|
||||||
position = y
|
position = y
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
"""
|
||||||
|
Pather with batched (multi-step) rendering
|
||||||
|
"""
|
||||||
from typing import Self, Sequence, Mapping, MutableMapping
|
from typing import Self, Sequence, Mapping, MutableMapping
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
@ -9,15 +12,13 @@ from numpy import pi
|
|||||||
from numpy.typing import ArrayLike
|
from numpy.typing import ArrayLike
|
||||||
|
|
||||||
from ..pattern import Pattern
|
from ..pattern import Pattern
|
||||||
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
|
||||||
from ..utils import SupportsBool
|
from ..utils import SupportsBool
|
||||||
from .tools import Tool, RenderStep
|
from .tools import Tool, RenderStep
|
||||||
from .utils import ell
|
from .utils import ell
|
||||||
from .builder import Builder
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -488,12 +489,12 @@ class RenderPather(PortList):
|
|||||||
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
is_horizontal = numpy.isclose(port.rotation % pi, 0)
|
||||||
if is_horizontal:
|
if is_horizontal:
|
||||||
if y is not None:
|
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:
|
if position is None:
|
||||||
position = x
|
position = x
|
||||||
else:
|
else:
|
||||||
if x is not None:
|
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:
|
if position is None:
|
||||||
position = y
|
position = y
|
||||||
|
|
||||||
|
@ -17,16 +17,30 @@ from ..pattern import Pattern
|
|||||||
from ..abstract import Abstract
|
from ..abstract import Abstract
|
||||||
from ..library import ILibrary, Library
|
from ..library import ILibrary, Library
|
||||||
from ..error import BuildError
|
from ..error import BuildError
|
||||||
from .builder import Builder
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class RenderStep:
|
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']
|
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'
|
tool: 'Tool | None'
|
||||||
|
""" The current tool. May be `None` if `opcode='P'` """
|
||||||
|
|
||||||
start_port: Port
|
start_port: Port
|
||||||
end_port: Port
|
end_port: Port
|
||||||
|
|
||||||
data: Any
|
data: Any
|
||||||
|
""" Arbitrary tool-specific data"""
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.opcode != 'P' and self.tool is None:
|
if self.opcode != 'P' and self.tool is None:
|
||||||
@ -34,6 +48,13 @@ class RenderStep:
|
|||||||
|
|
||||||
|
|
||||||
class Tool:
|
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(
|
def path(
|
||||||
self,
|
self,
|
||||||
ccw: SupportsBool | None,
|
ccw: SupportsBool | None,
|
||||||
@ -43,7 +64,40 @@ class Tool:
|
|||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
port_names: tuple[str, str] = ('A', 'B'),
|
port_names: tuple[str, str] = ('A', 'B'),
|
||||||
**kwargs,
|
**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)}')
|
raise NotImplementedError(f'path() not implemented for {type(self)}')
|
||||||
|
|
||||||
def planL(
|
def planL(
|
||||||
@ -55,11 +109,41 @@ class Tool:
|
|||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> tuple[Port, Any]:
|
) -> 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)}')
|
raise NotImplementedError(f'planL() not implemented for {type(self)}')
|
||||||
|
|
||||||
def planS(
|
def planS(
|
||||||
self,
|
self,
|
||||||
ccw: SupportsBool | None,
|
|
||||||
length: float,
|
length: float,
|
||||||
jog: float,
|
jog: float,
|
||||||
*,
|
*,
|
||||||
@ -67,17 +151,91 @@ class Tool:
|
|||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> tuple[Port, Any]:
|
) -> 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)}')
|
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(
|
def render(
|
||||||
self,
|
self,
|
||||||
batch: Sequence[RenderStep],
|
batch: Sequence[RenderStep],
|
||||||
*,
|
*,
|
||||||
in_ptype: str | None = None,
|
|
||||||
out_ptype: str | None = None,
|
|
||||||
port_names: Sequence[str] = ('A', 'B'),
|
port_names: Sequence[str] = ('A', 'B'),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> ILibrary:
|
) -> 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
|
assert not batch or batch[0].tool == self
|
||||||
raise NotImplementedError(f'render() not implemented for {type(self)}')
|
raise NotImplementedError(f'render() not implemented for {type(self)}')
|
||||||
|
|
||||||
@ -87,13 +245,26 @@ abstract_tuple_t = tuple[Abstract, str, str]
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BasicTool(Tool, metaclass=ABCMeta):
|
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]
|
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
|
bend: abstract_tuple_t # Assumed to be clockwise
|
||||||
|
""" `clockwise_bend_abstract, in_port_name, out_port_name` """
|
||||||
|
|
||||||
transitions: dict[str, abstract_tuple_t]
|
transitions: dict[str, abstract_tuple_t]
|
||||||
|
""" `{ptype: (transition_abstract`, ptype_port_name, other_port_name), ...}` """
|
||||||
|
|
||||||
default_out_ptype: str
|
default_out_ptype: str
|
||||||
|
""" Default value for out_ptype """
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class LData:
|
class LData:
|
||||||
|
""" Data for planL """
|
||||||
straight_length: float
|
straight_length: float
|
||||||
ccw: SupportsBool | None
|
ccw: SupportsBool | None
|
||||||
in_transition: abstract_tuple_t | None
|
in_transition: abstract_tuple_t | None
|
||||||
@ -108,7 +279,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
|||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
port_names: tuple[str, str] = ('A', 'B'),
|
port_names: tuple[str, str] = ('A', 'B'),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Pattern:
|
) -> Library:
|
||||||
_out_port, data = self.planL(
|
_out_port, data = self.planL(
|
||||||
ccw,
|
ccw,
|
||||||
length,
|
length,
|
||||||
@ -117,22 +288,22 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
gen_straight, sport_in, sport_out = self.straight
|
gen_straight, sport_in, sport_out = self.straight
|
||||||
tree = Library()
|
tree, pat = Library.mktree('_path')
|
||||||
bb = Builder(library=tree, name='_path').add_port_pair(names=port_names)
|
pat.add_port_pair(names=port_names)
|
||||||
if data.in_transition:
|
if data.in_transition:
|
||||||
ipat, iport_theirs, _iport_ours = 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):
|
if not numpy.isclose(data.straight_length, 0):
|
||||||
straight = tree << {'_straight': gen_straight(data.straight_length)}
|
straight = tree <= {'_straight': gen_straight(data.straight_length)}
|
||||||
bb.plug(straight, {port_names[1]: sport_in})
|
pat.plug(straight, {port_names[1]: sport_in})
|
||||||
if data.ccw is not None:
|
if data.ccw is not None:
|
||||||
bend, bport_in, bport_out = self.bend
|
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:
|
if data.out_transition:
|
||||||
opat, oport_theirs, oport_ours = 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(
|
def planL(
|
||||||
self,
|
self,
|
||||||
@ -220,8 +391,8 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
) -> ILibrary:
|
) -> ILibrary:
|
||||||
|
|
||||||
tree = Library()
|
tree, pat = Library.mktree('_path')
|
||||||
bb = Builder(library=tree, name='_path').add_port_pair(names=(port_names[0], port_names[1]))
|
pat.add_port_pair(names=(port_names[0], port_names[1]))
|
||||||
|
|
||||||
gen_straight, sport_in, _sport_out = self.straight
|
gen_straight, sport_in, _sport_out = self.straight
|
||||||
for step in batch:
|
for step in batch:
|
||||||
@ -231,28 +402,39 @@ class BasicTool(Tool, metaclass=ABCMeta):
|
|||||||
if step.opcode == 'L':
|
if step.opcode == 'L':
|
||||||
if in_transition:
|
if in_transition:
|
||||||
ipat, iport_theirs, _iport_ours = 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):
|
if not numpy.isclose(straight_length, 0):
|
||||||
straight_pat = gen_straight(straight_length)
|
straight_pat = gen_straight(straight_length)
|
||||||
if append:
|
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:
|
else:
|
||||||
straight = tree << {'_straight': straight_pat}
|
straight = tree <= {'_straight': straight_pat}
|
||||||
bb.plug(straight, {port_names[1]: sport_in}, append=True)
|
pat.plug(straight, {port_names[1]: sport_in}, append=True)
|
||||||
if ccw is not None:
|
if ccw is not None:
|
||||||
bend, bport_in, bport_out = self.bend
|
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:
|
if out_transition:
|
||||||
opat, oport_theirs, oport_ours = 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
|
return tree
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PathTool(Tool, metaclass=ABCMeta):
|
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: layer_t
|
||||||
|
""" Layer to draw on """
|
||||||
|
|
||||||
width: float
|
width: float
|
||||||
|
""" `Path` width """
|
||||||
|
|
||||||
ptype: str = 'unk'
|
ptype: str = 'unk'
|
||||||
|
""" ptype for any ports in patterns generated by this tool """
|
||||||
|
|
||||||
#@dataclass(frozen=True, slots=True)
|
#@dataclass(frozen=True, slots=True)
|
||||||
#class LData:
|
#class LData:
|
||||||
@ -273,7 +455,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
|||||||
out_ptype: str | None = None,
|
out_ptype: str | None = None,
|
||||||
port_names: tuple[str, str] = ('A', 'B'),
|
port_names: tuple[str, str] = ('A', 'B'),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Pattern:
|
) -> Library:
|
||||||
out_port, dxy = self.planL(
|
out_port, dxy = self.planL(
|
||||||
ccw,
|
ccw,
|
||||||
length,
|
length,
|
||||||
@ -281,7 +463,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
|||||||
out_ptype=out_ptype,
|
out_ptype=out_ptype,
|
||||||
)
|
)
|
||||||
|
|
||||||
pat = Pattern()
|
tree, pat = Library.mktree('_path')
|
||||||
pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)])
|
pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)])
|
||||||
|
|
||||||
if ccw is None:
|
if ccw is None:
|
||||||
@ -296,7 +478,7 @@ class PathTool(Tool, metaclass=ABCMeta):
|
|||||||
port_names[1]: Port(dxy, rotation=out_rot, ptype=self.ptype),
|
port_names[1]: Port(dxy, rotation=out_rot, ptype=self.ptype),
|
||||||
}
|
}
|
||||||
|
|
||||||
return pat
|
return tree
|
||||||
|
|
||||||
def planL(
|
def planL(
|
||||||
self,
|
self,
|
||||||
|
@ -122,6 +122,8 @@ def ell(
|
|||||||
orig_offsets = numpy.array([p.offset for p in ports.values()])
|
orig_offsets = numpy.array([p.offset for p in ports.values()])
|
||||||
rot_offsets = (rot_matrix @ orig_offsets.T).T
|
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_order = ((-1 if ccw else 1) * rot_offsets[:, 1]).argsort(kind='stable')
|
||||||
y_ind = numpy.empty_like(y_order, dtype=int)
|
y_ind = numpy.empty_like(y_order, dtype=int)
|
||||||
y_ind[y_order] = numpy.arange(y_ind.shape[0])
|
y_ind[y_order] = numpy.arange(y_ind.shape[0])
|
||||||
|
@ -765,7 +765,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
|||||||
|
|
||||||
def prune_layers(self) -> Self:
|
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:
|
Returns:
|
||||||
self
|
self
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
import copy
|
import copy
|
||||||
import math
|
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
@ -231,7 +230,7 @@ class Arc(Shape):
|
|||||||
|
|
||||||
def get_thetas(inner: bool) -> NDArray[numpy.float64]:
|
def get_thetas(inner: bool) -> NDArray[numpy.float64]:
|
||||||
""" Figure out the parameter values at which we should place vertices to meet the arclength constraint"""
|
""" 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)
|
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])
|
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.
|
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:
|
def __copy__(self) -> Self:
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
|
Loading…
x
Reference in New Issue
Block a user