various doc updates
This commit is contained in:
parent
0f9746c2a5
commit
e3fdcba645
70
README.md
70
README.md
@ -15,32 +15,65 @@ to output to multiple formats.
|
|||||||
Requirements:
|
Requirements:
|
||||||
* python >= 3.11
|
* python >= 3.11
|
||||||
* numpy
|
* numpy
|
||||||
* klamath (optional, used for `gdsii` i/o)
|
* klamath (used for GDSII i/o)
|
||||||
* matplotlib (optional, used for `visualization` functions and `text`)
|
|
||||||
* ezdxf (optional, used for `dxf` i/o)
|
Optional requirements:
|
||||||
* fatamorgana (optional, used for `oasis` i/o)
|
* `ezdxf` (DXF i/o): ezdxf
|
||||||
* svgwrite (optional, used for `svg` output)
|
* `oasis` (OASIS i/o): fatamorgana
|
||||||
* freetype (optional, used for `text`)
|
* `svg` (SVG output): svgwrite
|
||||||
|
* `visualization` (shape plotting): matplotlib
|
||||||
|
* `text` (`Text` shape): matplotlib, freetype
|
||||||
|
|
||||||
|
|
||||||
Install with pip:
|
Install with pip:
|
||||||
```bash
|
```bash
|
||||||
pip install 'masque[visualization,oasis,dxf,svg,text]'
|
pip install 'masque[oasis,dxf,svg,visualization,text]'
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, install from git
|
## Overview
|
||||||
```bash
|
|
||||||
pip install git+https://mpxd.net/code/jan/masque.git@release
|
A layout consists of a hierarchy of `Pattern`s stored in a single `Library`.
|
||||||
```
|
Each `Pattern` can contain `Ref`s pointing at other patterns, `Shape`s, and `Label`s.
|
||||||
|
|
||||||
|
`masque` departs from several "classic" GDSII paradigms:
|
||||||
|
- Layer info for `Shape`ss and `Label`s is not stored in the individual shape and label objects.
|
||||||
|
Instead, the layer is determined by the key for the container dict (e.g. `pattern.shapes[layer]`).
|
||||||
|
* This simplifies many common tasks: filtering `Shape`s by layer, remapping layers, and checking if
|
||||||
|
a layer is empty.
|
||||||
|
* Technically, this allows reusing the same shape or label object across multiple layers. This isn't
|
||||||
|
part of the standard workflow since a mixture of single-use and multi-use shapes could be confusing.
|
||||||
|
* This is similar to the approach used in [KLayout](https://www.klayout.de)
|
||||||
|
- `Ref` target names are also determined in the key of the container dict (e.g. `pattern.refs[target_name]`).
|
||||||
|
* This similarly simplifies filtering `Ref`s by target name, updating to a new target, and checking
|
||||||
|
if a given `Pattern` is referenced.
|
||||||
|
- `Pattern` names are set by their containing `Library` and are not stored in the `Pattern` objects.
|
||||||
|
* This guarantees that there are no duplicate pattern names within any given `Library`.
|
||||||
|
* Likewise, enumerating all the names (and all the `Pattern`s) in a `Library` is straightforward.
|
||||||
|
- Each `Ref`, `Shape`, or `Label` can be repeated multiple times by attaching a `repetition` object to it.
|
||||||
|
* This is similar to how OASIS reptitions are handled, and provides extra flexibility over the GDSII
|
||||||
|
approach of only allowing arrays through AREF (`Ref` + `repetition`).
|
||||||
|
- `Label`s do not have an orientation or presentation
|
||||||
|
* This is in line with how they are used in practice, and how they are represented in OASIS.
|
||||||
|
- Non-polygonal `Shape`s are allowed. For example, elliptical arcs are a basic shape type.
|
||||||
|
* This enables compatibility with OASIS (e.g. circles) and other formats.
|
||||||
|
* `Shape`s provide a `.to_polygons()` method for GDSII compatibility.
|
||||||
|
- Most coordinate values are stored as 64-bit floats internally.
|
||||||
|
* 1 earth radii in nanometers (6e15) is still represented without approximation (53 bit mantissa -> 2^53 > 9e15)
|
||||||
|
* Operations that would otherwise clip/round on are still represented approximately.
|
||||||
|
* Memory usage is usually dominated by other Python overhead.
|
||||||
|
|
||||||
|
|
||||||
## Glossary
|
## Glossary
|
||||||
- `Library`: OASIS or GDS "library" or file (a collection of named cells)
|
- `Library`: A collection of named cells. OASIS or GDS "library" or file.
|
||||||
- `Pattern`: OASIS or GDS "Cell", DXF "Block"
|
- `Pattern`: A collection of geometry, text labels, and reference to other patterns.
|
||||||
- `Ref`: GDS "AREF/SREF", OASIS "Placement"
|
OASIS or GDS "Cell", DXF "Block".
|
||||||
- `Shape`: OASIS or GDS "Geometry element", DXF "LWPolyline" or "Polyline"
|
- `Ref`: A reference to another pattern. GDS "AREF/SREF", OASIS "Placement".
|
||||||
- `repetition`: OASIS "repetition". GDS "AREF" is a `Ref` combined with a `Grid` repetition.
|
- `Shape`: Individual geometric entity. OASIS or GDS "Geometry element", DXF "LWPolyline" or "Polyline".
|
||||||
- `Label`: OASIS, GDS, DXF "Text".
|
- `repetition`: Repetition operation. OASIS "repetition".
|
||||||
- `annotation`: OASIS or GDS "property"
|
GDS "AREF" is a `Ref` combined with a `Grid` repetition.
|
||||||
|
- `Label`: Text label. Not rendered into geometry. OASIS, GDS, DXF "Text".
|
||||||
|
- `annotation`: Additional metadata. OASIS or GDS "property".
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
@ -48,7 +81,6 @@ pip install git+https://mpxd.net/code/jan/masque.git@release
|
|||||||
* Better interface for polygon operations (e.g. with `pyclipper`)
|
* Better interface for polygon operations (e.g. with `pyclipper`)
|
||||||
- de-embedding
|
- de-embedding
|
||||||
- boolean ops
|
- boolean ops
|
||||||
* DOCS DOCS DOCS
|
|
||||||
* Tests tests tests
|
* Tests tests tests
|
||||||
* check renderpather
|
* check renderpather
|
||||||
* pather and renderpather examples
|
* pather and renderpather examples
|
||||||
|
@ -495,12 +495,12 @@ def _labels_to_texts(labels: dict[layer_t, list[Label]]) -> list[klamath.element
|
|||||||
xy=xy,
|
xy=xy,
|
||||||
string=label.string.encode('ASCII'),
|
string=label.string.encode('ASCII'),
|
||||||
properties=properties,
|
properties=properties,
|
||||||
presentation=0, # TODO maybe set some of these?
|
presentation=0, # font number & alignment -- unused by us
|
||||||
angle_deg=0,
|
angle_deg=0, # rotation -- unused by us
|
||||||
invert_y=False,
|
invert_y=False, # inversion -- unused by us
|
||||||
width=0,
|
width=0, # stroke width -- unused by us
|
||||||
path_type=0,
|
path_type=0, # text path endcaps, unused
|
||||||
mag=1,
|
mag=1, # size -- unused by us
|
||||||
)
|
)
|
||||||
texts.append(text)
|
texts.append(text)
|
||||||
return texts
|
return texts
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
Library classes for managing unique name->pattern mappings and
|
Library classes for managing unique name->pattern mappings and
|
||||||
deferred loading or creation.
|
deferred loading or creation.
|
||||||
|
|
||||||
# TODO documentn all library classes
|
# TODO documennt all library classes
|
||||||
# TODO toplevel documentation of library, classes, and abstracts
|
# TODO toplevel documentation of library, classes, and abstracts
|
||||||
"""
|
"""
|
||||||
from typing import Callable, Self, Type, TYPE_CHECKING, cast
|
from typing import Callable, Self, Type, TYPE_CHECKING, cast
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Base object representing a lithography mask.
|
Object representing a one multi-layer lithographic layout.
|
||||||
|
A single level of hierarchical references is included.
|
||||||
"""
|
"""
|
||||||
from typing import Callable, Sequence, cast, Mapping, Self, Any, Iterable, TypeVar, MutableMapping
|
from typing import Callable, Sequence, cast, Mapping, Self, Any, Iterable, TypeVar, MutableMapping
|
||||||
import copy
|
import copy
|
||||||
@ -27,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
||||||
"""
|
"""
|
||||||
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
2D layout consisting of some set of shapes, labels, and references to other Pattern objects
|
||||||
(via Ref). Shapes are assumed to inherit from masque.shapes.Shape or provide equivalent functions.
|
(via Ref). Shapes are assumed to inherit from `masque.shapes.Shape` or provide equivalent functions.
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'shapes', 'labels', 'refs', '_ports',
|
'shapes', 'labels', 'refs', '_ports',
|
||||||
@ -864,6 +865,19 @@ TT = TypeVar('TT')
|
|||||||
|
|
||||||
|
|
||||||
def chain_elements(*args: Mapping[Any, Iterable[TT]]) -> Iterable[TT]:
|
def chain_elements(*args: Mapping[Any, Iterable[TT]]) -> Iterable[TT]:
|
||||||
|
"""
|
||||||
|
Iterate over each element in one or more {layer: elements} mappings.
|
||||||
|
|
||||||
|
Useful when you want to do some operation on all shapes and/or labels,
|
||||||
|
disregarding which layer they are on.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: One or more {layer: [element0, ...]} mappings.
|
||||||
|
Can also be applied to e.g. {target: [ref0, ...]} mappings.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An iterable containing all elements, regardless of layer.
|
||||||
|
"""
|
||||||
return chain(*(chain.from_iterable(aa.values()) for aa in args))
|
return chain(*(chain.from_iterable(aa.values()) for aa in args))
|
||||||
|
|
||||||
|
|
||||||
@ -871,6 +885,20 @@ def map_layers(
|
|||||||
elements: Mapping[layer_t, Sequence[TT]],
|
elements: Mapping[layer_t, Sequence[TT]],
|
||||||
map_layer: Callable[[layer_t], layer_t],
|
map_layer: Callable[[layer_t], layer_t],
|
||||||
) -> defaultdict[layer_t, list[TT]]:
|
) -> defaultdict[layer_t, list[TT]]:
|
||||||
|
"""
|
||||||
|
Move all the elements from one layer onto a different layer.
|
||||||
|
Can also handle multiple such mappings simultaneously.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
elements: Mapping of {old_layer: geometry_or_labels}.
|
||||||
|
map_layer: Callable which may be called with each layer present in `elements`,
|
||||||
|
and should return the new layer to which it will be mapped.
|
||||||
|
A simple example which maps `old_layer` to `new_layer` and leaves all others
|
||||||
|
as-is would look like `lambda layer: {old_layer: new_layer}.get(layer, layer)`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Mapping of {new_layer: geometry_or_labels}
|
||||||
|
"""
|
||||||
new_elements: defaultdict[layer_t, list[TT]] = defaultdict(list)
|
new_elements: defaultdict[layer_t, list[TT]] = defaultdict(list)
|
||||||
for old_layer, seq in elements.items():
|
for old_layer, seq in elements.items():
|
||||||
new_layer = map_layer(old_layer)
|
new_layer = map_layer(old_layer)
|
||||||
@ -882,6 +910,20 @@ def map_targets(
|
|||||||
refs: Mapping[str | None, Sequence[Ref]],
|
refs: Mapping[str | None, Sequence[Ref]],
|
||||||
map_target: Callable[[str | None], str | None],
|
map_target: Callable[[str | None], str | None],
|
||||||
) -> defaultdict[str | None, list[Ref]]:
|
) -> defaultdict[str | None, list[Ref]]:
|
||||||
|
"""
|
||||||
|
Change the target of all references to a given cell.
|
||||||
|
Can also handle multiple such mappings simultaneously.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
refs: Mapping of {old_target: ref_objects}.
|
||||||
|
map_target: Callable which may be called with each target present in `refs`,
|
||||||
|
and should return the new target to which it will be mapped.
|
||||||
|
A simple example which maps `old_target` to `new_target` and leaves all others
|
||||||
|
as-is would look like `lambda target: {old_target: new_target}.get(target, target)`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Mapping of {new_target: ref_objects}
|
||||||
|
"""
|
||||||
new_refs: defaultdict[str | None, list[Ref]] = defaultdict(list)
|
new_refs: defaultdict[str | None, list[Ref]] = defaultdict(list)
|
||||||
for old_target, seq in refs.items():
|
for old_target, seq in refs.items():
|
||||||
new_target = map_target(old_target)
|
new_target = map_target(old_target)
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Ref provides basic support for nesting Pattern objects within each other, by adding
|
Ref provides basic support for nesting Pattern objects within each other.
|
||||||
offset, rotation, scaling, and other such properties to the reference.
|
It carries offset, rotation, mirroring, and scaling data for each individual instance.
|
||||||
"""
|
"""
|
||||||
#TODO more top-level documentation for ref
|
|
||||||
|
|
||||||
from typing import Mapping, TYPE_CHECKING, Self
|
from typing import Mapping, TYPE_CHECKING, Self
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
@ -28,10 +26,15 @@ class Ref(
|
|||||||
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
`Ref` provides basic support for nesting Pattern objects within each other, by adding
|
`Ref` provides basic support for nesting Pattern objects within each other.
|
||||||
offset, rotation, scaling, and associated methods.
|
|
||||||
|
|
||||||
Note: Order is (mirror, rotate, scale, translate, repeat)
|
It containts the transformation (mirror, rotation, scale, offset, repetition)
|
||||||
|
and annotations for a single instantiation of a `Pattern`.
|
||||||
|
|
||||||
|
Note that the target (i.e. which pattern a `Ref` instantiates) is not stored within the
|
||||||
|
`Ref` itself, but is specified by the containing `Pattern`.
|
||||||
|
|
||||||
|
Order of operations is (mirror, rotate, scale, translate, repeat).
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'_mirrored',
|
'_mirrored',
|
||||||
|
@ -26,6 +26,9 @@ class Path(Shape):
|
|||||||
A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape,
|
A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape,
|
||||||
and an offset.
|
and an offset.
|
||||||
|
|
||||||
|
Note that the setter for `Path.vertices` may (but may not) create a copy of the
|
||||||
|
passed vertex coordinates. See `numpy.array(..., copy=False)` for details.
|
||||||
|
|
||||||
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
A normalized_form(...) is available, but can be quite slow with lots of vertices.
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
@ -61,12 +64,14 @@ class Path(Shape):
|
|||||||
def cap(self) -> PathCap:
|
def cap(self) -> PathCap:
|
||||||
"""
|
"""
|
||||||
Path end-cap
|
Path end-cap
|
||||||
|
|
||||||
|
Note that `cap_extensions` will be reset to default values if
|
||||||
|
`cap` is changed away from `PathCap.SquareCustom`.
|
||||||
"""
|
"""
|
||||||
return self._cap
|
return self._cap
|
||||||
|
|
||||||
@cap.setter
|
@cap.setter
|
||||||
def cap(self, val: PathCap) -> None:
|
def cap(self, val: PathCap) -> None:
|
||||||
# TODO: Document that setting cap can change cap_extensions
|
|
||||||
self._cap = PathCap(val)
|
self._cap = PathCap(val)
|
||||||
if self.cap != PathCap.SquareCustom:
|
if self.cap != PathCap.SquareCustom:
|
||||||
self.cap_extensions = None
|
self.cap_extensions = None
|
||||||
@ -80,6 +85,9 @@ class Path(Shape):
|
|||||||
"""
|
"""
|
||||||
Path end-cap extension
|
Path end-cap extension
|
||||||
|
|
||||||
|
Note that `cap_extensions` will be reset to default values if
|
||||||
|
`cap` is changed away from `PathCap.SquareCustom`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
2-element ndarray or `None`
|
2-element ndarray or `None`
|
||||||
"""
|
"""
|
||||||
@ -101,13 +109,16 @@ class Path(Shape):
|
|||||||
@property
|
@property
|
||||||
def vertices(self) -> Any: # mypy#3004 NDArray[numpy.float64]]:
|
def vertices(self) -> Any: # mypy#3004 NDArray[numpy.float64]]:
|
||||||
"""
|
"""
|
||||||
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
Vertices of the path (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`
|
||||||
|
|
||||||
|
When setting, note that a copy of the provided vertices may or may not be made,
|
||||||
|
following the rules from `numpy.array(.., copy=False)`.
|
||||||
"""
|
"""
|
||||||
return self._vertices
|
return self._vertices
|
||||||
|
|
||||||
@vertices.setter
|
@vertices.setter
|
||||||
def vertices(self, val: ArrayLike) -> None:
|
def vertices(self, val: ArrayLike) -> None:
|
||||||
val = numpy.array(val, dtype=float) # TODO document that these might not be copied
|
val = numpy.array(val, dtype=float)
|
||||||
if len(val.shape) < 2 or val.shape[1] != 2:
|
if len(val.shape) < 2 or val.shape[1] != 2:
|
||||||
raise PatternError('Vertices must be an Nx2 array')
|
raise PatternError('Vertices must be an Nx2 array')
|
||||||
if val.shape[0] < 2:
|
if val.shape[0] < 2:
|
||||||
@ -218,7 +229,7 @@ class Path(Shape):
|
|||||||
Returns:
|
Returns:
|
||||||
The resulting Path object
|
The resulting Path object
|
||||||
"""
|
"""
|
||||||
# TODO: needs testing
|
# TODO: Path.travel() needs testing
|
||||||
direction = numpy.array([1, 0])
|
direction = numpy.array([1, 0])
|
||||||
|
|
||||||
verts = [numpy.zeros(2)]
|
verts = [numpy.zeros(2)]
|
||||||
|
@ -18,7 +18,7 @@ class Polygon(Shape):
|
|||||||
implicitly-closed boundary, and an offset.
|
implicitly-closed boundary, and an offset.
|
||||||
|
|
||||||
Note that the setter for `Polygon.vertices` may (but may not) create a copy of the
|
Note that the setter for `Polygon.vertices` may (but may not) create a copy of the
|
||||||
passed vertex coordinates. See `numpy.array()` for details.
|
passed vertex coordinates. See `numpy.array(..., copy=False)` for details.
|
||||||
|
|
||||||
A `normalized_form(...)` is available, but can be quite slow with lots of vertices.
|
A `normalized_form(...)` is available, but can be quite slow with lots of vertices.
|
||||||
"""
|
"""
|
||||||
@ -36,12 +36,15 @@ class Polygon(Shape):
|
|||||||
def vertices(self) -> Any: # mypy#3004 NDArray[numpy.float64]:
|
def vertices(self) -> Any: # mypy#3004 NDArray[numpy.float64]:
|
||||||
"""
|
"""
|
||||||
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
Vertices of the polygon (Nx2 ndarray: `[[x0, y0], [x1, y1], ...]`)
|
||||||
|
|
||||||
|
When setting, note that a copy of the provided vertices may or may not be made,
|
||||||
|
following the rules from `numpy.array(.., copy=False)`.
|
||||||
"""
|
"""
|
||||||
return self._vertices
|
return self._vertices
|
||||||
|
|
||||||
@vertices.setter
|
@vertices.setter
|
||||||
def vertices(self, val: ArrayLike) -> None:
|
def vertices(self, val: ArrayLike) -> None:
|
||||||
val = numpy.array(val, dtype=float) # note that this hopefully won't create a copy
|
val = numpy.array(val, dtype=float)
|
||||||
if len(val.shape) < 2 or val.shape[1] != 2:
|
if len(val.shape) < 2 or val.shape[1] != 2:
|
||||||
raise PatternError('Vertices must be an Nx2 array')
|
raise PatternError('Vertices must be an Nx2 array')
|
||||||
if val.shape[0] < 3:
|
if val.shape[0] < 3:
|
||||||
|
Loading…
Reference in New Issue
Block a user