various doc updates
This commit is contained in:
parent
04e15f7c85
commit
e2c7f8c8cc
70
README.md
70
README.md
@ -15,32 +15,65 @@ to output to multiple formats.
|
||||
Requirements:
|
||||
* python >= 3.11
|
||||
* numpy
|
||||
* klamath (optional, used for `gdsii` i/o)
|
||||
* matplotlib (optional, used for `visualization` functions and `text`)
|
||||
* ezdxf (optional, used for `dxf` i/o)
|
||||
* fatamorgana (optional, used for `oasis` i/o)
|
||||
* svgwrite (optional, used for `svg` output)
|
||||
* freetype (optional, used for `text`)
|
||||
* klamath (used for GDSII i/o)
|
||||
|
||||
Optional requirements:
|
||||
* `ezdxf` (DXF i/o): ezdxf
|
||||
* `oasis` (OASIS i/o): fatamorgana
|
||||
* `svg` (SVG output): svgwrite
|
||||
* `visualization` (shape plotting): matplotlib
|
||||
* `text` (`Text` shape): matplotlib, freetype
|
||||
|
||||
|
||||
Install with pip:
|
||||
```bash
|
||||
pip install 'masque[visualization,oasis,dxf,svg,text]'
|
||||
pip install 'masque[oasis,dxf,svg,visualization,text]'
|
||||
```
|
||||
|
||||
Alternatively, install from git
|
||||
```bash
|
||||
pip install git+https://mpxd.net/code/jan/masque.git@release
|
||||
```
|
||||
## Overview
|
||||
|
||||
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
|
||||
- `Library`: OASIS or GDS "library" or file (a collection of named cells)
|
||||
- `Pattern`: OASIS or GDS "Cell", DXF "Block"
|
||||
- `Ref`: GDS "AREF/SREF", OASIS "Placement"
|
||||
- `Shape`: OASIS or GDS "Geometry element", DXF "LWPolyline" or "Polyline"
|
||||
- `repetition`: OASIS "repetition". GDS "AREF" is a `Ref` combined with a `Grid` repetition.
|
||||
- `Label`: OASIS, GDS, DXF "Text".
|
||||
- `annotation`: OASIS or GDS "property"
|
||||
- `Library`: A collection of named cells. OASIS or GDS "library" or file.
|
||||
- `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".
|
||||
- `Shape`: Individual geometric entity. OASIS or GDS "Geometry element", DXF "LWPolyline" or "Polyline".
|
||||
- `repetition`: Repetition operation. OASIS "repetition".
|
||||
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
|
||||
@ -48,7 +81,6 @@ pip install git+https://mpxd.net/code/jan/masque.git@release
|
||||
* Better interface for polygon operations (e.g. with `pyclipper`)
|
||||
- de-embedding
|
||||
- boolean ops
|
||||
* DOCS DOCS DOCS
|
||||
* Tests tests tests
|
||||
* check renderpather
|
||||
* pather and renderpather examples
|
||||
|
@ -495,12 +495,12 @@ def _labels_to_texts(labels: dict[layer_t, list[Label]]) -> list[klamath.element
|
||||
xy=xy,
|
||||
string=label.string.encode('ASCII'),
|
||||
properties=properties,
|
||||
presentation=0, # TODO maybe set some of these?
|
||||
angle_deg=0,
|
||||
invert_y=False,
|
||||
width=0,
|
||||
path_type=0,
|
||||
mag=1,
|
||||
presentation=0, # font number & alignment -- unused by us
|
||||
angle_deg=0, # rotation -- unused by us
|
||||
invert_y=False, # inversion -- unused by us
|
||||
width=0, # stroke width -- unused by us
|
||||
path_type=0, # text path endcaps, unused
|
||||
mag=1, # size -- unused by us
|
||||
)
|
||||
texts.append(text)
|
||||
return texts
|
||||
|
@ -2,7 +2,7 @@
|
||||
Library classes for managing unique name->pattern mappings and
|
||||
deferred loading or creation.
|
||||
|
||||
# TODO documentn all library classes
|
||||
# TODO documennt all library classes
|
||||
# TODO toplevel documentation of library, classes, and abstracts
|
||||
"""
|
||||
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
|
||||
import copy
|
||||
@ -27,7 +28,7 @@ logger = logging.getLogger(__name__)
|
||||
class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
||||
"""
|
||||
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__ = (
|
||||
'shapes', 'labels', 'refs', '_ports',
|
||||
@ -864,6 +865,19 @@ TT = TypeVar('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))
|
||||
|
||||
|
||||
@ -871,6 +885,20 @@ def map_layers(
|
||||
elements: Mapping[layer_t, Sequence[TT]],
|
||||
map_layer: Callable[[layer_t], layer_t],
|
||||
) -> 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)
|
||||
for old_layer, seq in elements.items():
|
||||
new_layer = map_layer(old_layer)
|
||||
@ -882,6 +910,20 @@ def map_targets(
|
||||
refs: Mapping[str | None, Sequence[Ref]],
|
||||
map_target: Callable[[str | None], str | None],
|
||||
) -> 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)
|
||||
for old_target, seq in refs.items():
|
||||
new_target = map_target(old_target)
|
||||
|
@ -1,9 +1,7 @@
|
||||
"""
|
||||
Ref provides basic support for nesting Pattern objects within each other, by adding
|
||||
offset, rotation, scaling, and other such properties to the reference.
|
||||
Ref provides basic support for nesting Pattern objects within each other.
|
||||
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
|
||||
import copy
|
||||
|
||||
@ -28,10 +26,15 @@ class Ref(
|
||||
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||
):
|
||||
"""
|
||||
`Ref` provides basic support for nesting Pattern objects within each other, by adding
|
||||
offset, rotation, scaling, and associated methods.
|
||||
`Ref` provides basic support for nesting Pattern objects within each other.
|
||||
|
||||
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__ = (
|
||||
'_mirrored',
|
||||
|
@ -26,6 +26,9 @@ class Path(Shape):
|
||||
A path, consisting of a bunch of vertices (Nx2 ndarray), a width, an end-cap shape,
|
||||
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.
|
||||
"""
|
||||
__slots__ = (
|
||||
@ -61,12 +64,14 @@ class Path(Shape):
|
||||
def cap(self) -> PathCap:
|
||||
"""
|
||||
Path end-cap
|
||||
|
||||
Note that `cap_extensions` will be reset to default values if
|
||||
`cap` is changed away from `PathCap.SquareCustom`.
|
||||
"""
|
||||
return self._cap
|
||||
|
||||
@cap.setter
|
||||
def cap(self, val: PathCap) -> None:
|
||||
# TODO: Document that setting cap can change cap_extensions
|
||||
self._cap = PathCap(val)
|
||||
if self.cap != PathCap.SquareCustom:
|
||||
self.cap_extensions = None
|
||||
@ -80,6 +85,9 @@ class Path(Shape):
|
||||
"""
|
||||
Path end-cap extension
|
||||
|
||||
Note that `cap_extensions` will be reset to default values if
|
||||
`cap` is changed away from `PathCap.SquareCustom`.
|
||||
|
||||
Returns:
|
||||
2-element ndarray or `None`
|
||||
"""
|
||||
@ -101,13 +109,16 @@ class Path(Shape):
|
||||
@property
|
||||
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
|
||||
|
||||
@vertices.setter
|
||||
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:
|
||||
raise PatternError('Vertices must be an Nx2 array')
|
||||
if val.shape[0] < 2:
|
||||
@ -218,7 +229,7 @@ class Path(Shape):
|
||||
Returns:
|
||||
The resulting Path object
|
||||
"""
|
||||
# TODO: needs testing
|
||||
# TODO: Path.travel() needs testing
|
||||
direction = numpy.array([1, 0])
|
||||
|
||||
verts = [numpy.zeros(2)]
|
||||
|
@ -18,7 +18,7 @@ class Polygon(Shape):
|
||||
implicitly-closed boundary, and an offset.
|
||||
|
||||
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.
|
||||
"""
|
||||
@ -36,12 +36,15 @@ class Polygon(Shape):
|
||||
def vertices(self) -> Any: # mypy#3004 NDArray[numpy.float64]:
|
||||
"""
|
||||
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
|
||||
|
||||
@vertices.setter
|
||||
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:
|
||||
raise PatternError('Vertices must be an Nx2 array')
|
||||
if val.shape[0] < 3:
|
||||
|
Loading…
Reference in New Issue
Block a user