Masque README
Masque is a Python module for designing lithography masks.
The general idea is to implement something resembling the GDSII file-format, but with some vectorized element types (eg. circles, not just polygons) and the ability to output to multiple formats.
Installation
Requirements:
- python >= 3.11
- numpy
- klamath (used for GDSII i/o)
Optional requirements:
ezdxf(DXF i/o): ezdxfoasis(OASIS i/o): fatamorganasvg(SVG output): svgwritevisualization(shape plotting): matplotlibtext(Textshape): matplotlib, freetype
Install with pip:
pip install 'masque[oasis,dxf,svg,visualization,text]'
Overview
A layout consists of a hierarchy of Patterns stored in a single Library.
Each Pattern can contain Refs pointing at other patterns, Shapes, Labels, and Ports.
masque departs from several "classic" GDSII paradigms:
- A
Patternobject does not store its own name. A name is only assigned when the pattern is placed into aLibrary, which is effectively a name->Patternmapping. - Layer info for
Shapess andLabels 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
Shapes 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
- This simplifies many common tasks: filtering
Reftarget names are also determined in the key of the container dict (e.g.pattern.refs[target_name]).- This similarly simplifies filtering
Refs by target name, updating to a new target, and checking if a givenPatternis referenced.
- This similarly simplifies filtering
Patternnames are set by their containingLibraryand are not stored in thePatternobjects.- This guarantees that there are no duplicate pattern names within any given
Library. - Likewise, enumerating all the names (and all the
Patterns) in aLibraryis straightforward.
- This guarantees that there are no duplicate pattern names within any given
- Each
Ref,Shape, orLabelcan be repeated multiple times by attaching arepetitionobject 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).
- This is similar to how OASIS reptitions are handled, and provides extra flexibility over the GDSII
approach of only allowing arrays through AREF (
Labels 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
Shapes are allowed. For example, elliptical arcs are a basic shape type.- This enables compatibility with OASIS (e.g. circles) and other formats.
Shapes 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.
Patternobjects also containPortinformation, which can be used to "snap" together multiple sub-components by matching up the requested port offsets and rotations.- Port rotations are defined as counter-clockwise angles from the +x axis.
- Ports point into the interior of their associated device.
- Port rotations may be
Nonein the case of non-oriented ports. - Ports have a
ptypestring which is compared in order to catch mismatched connections at build time. - Ports can be exported into/imported from
Labels stored directly in the layout, editable from standard tools (e.g. KLayout). A default format is provided.
In one important way, masque stays very orthodox:
References are accomplished by listing the target's name, not its Pattern object.
- The main downside of this is that any operations that traverse the hierarchy require
both the
Patternand theLibrarywhich is contains its reference targets. - This guarantees that names within a
Libraryremain unique at all times.- Since this can be tedious in cases where you don't actually care about the name of a
pattern, patterns whose names start with
SINGLE_USE_PREFIX(default: an underscore) may be silently renamed in order to maintain uniqueness. Seemasque.library.SINGLE_USE_PREFIX,masque.library._rename_patterns(), andILibrary.add()for more details.
- Since this can be tedious in cases where you don't actually care about the name of a
pattern, patterns whose names start with
- Having all patterns accessible through the
Libraryavoids having to perform a tree traversal for every operation which needs to touch allPatternobjects (e.g. deleting a layer everywhere or scaling all patterns). - Since
Patterndoesn't know its own name, you can't create a reference by passing in aPatternobject -- you need to know its name. - You can reference a
Patternbefore it is created, so long as you have already decided on its name. - Functions like
Pattern.place()andPattern.plug()need to receive a pattern's name in order to create a reference, but they also need to access the pattern's ports.- One way to provide this data is through an
Abstract, generated viaLibrary.abstract()or through aLibrary.abstract_view(). - Another way is use
Builder.place()orBuilder.plug(), which automatically creates anAbstractfrom its internally-referencedLibrary.
- One way to provide this data is through an
Glossary
Library: A collection of named cells. OASIS or GDS "library" or file.Tree: Any{name: pattern}mapping 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".Shape: Individual geometric entity. OASIS or GDS "Geometry element", DXF "LWPolyline" or "Polyline".repetition: Repetition operation. OASIS "repetition". GDS "AREF" is aRefcombined with aGridrepetition.Label: Text label. Not rendered into geometry. OASIS, GDS, DXF "Text".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
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
# 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(...), ...)
# With a `Builder` and `place()`/`plug()` the `lib <<` portion can be implicit:
my_builder = Builder(library=lib, ...)
...
my_builder.place(make_tree(...))
We can also use this shorthand to quickly add and reference a single flat (as yet un-named) pattern:
anonymous_pattern = Pattern(...)
my_pattern.ref(lib << {'_tentative_name': anonymous_pattern}, ...)
Place a hierarchical pattern into a layout, preserving its port info
# 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:
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
# 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
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
- Better interface for polygon operations (e.g. with
pyclipper)- de-embedding
- boolean ops
- Tests tests tests
- check renderpather
- pather and renderpather examples