various doc updates

master
jan 7 months ago
parent 04e15f7c85
commit e2c7f8c8cc

@ -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…
Cancel
Save