Boolean and Morphological Operations
Combine shapes with union, intersection, difference and xor, grow and shrink them with dilate and erode, and the equivalent calls in gdsfactory, KLayout, gdstk, Luceda IPKISS and Nazca.
Boolean and morphological operations are shapes too: a shape statement whose primitive is an operation computes a derived shape from other shapes of the cell.
shape rect with 10um, 6um as body
shape circle with 2um as hole
place body at (0, 0)
place hole at body.right_center
shape difference with body, hole as notched| Operation | Signature | Result |
|---|---|---|
union | union(a, b?) | The region covered by a or b. |
intersection | intersection(a, b) | The region covered by both. |
difference | difference(a, b) | The region of a outside b. |
xor | xor(a, b) | The region covered by exactly one of them. |
dilate | dilate(a, distance, join?, segments?) | a grown by distance. |
erode | erode(a, distance, join?, segments?) | a shrunk by distance. |
As on Shape Primitives, the examples are compiled in your browser. Examples with several layers draw si in blue, slab in amber and metal in violet.
Derived shapes
Operands. a and b are a shape or a list of shapes; a list counts as the union of its shapes. Operands may be on any layer and may themselves be derived shapes. A list can be collected in a loop with a local variable:
shape rect with 14um, 3um as bar
place bar at (0, 0)
define holes = []
for k in 0..6 {
shape circle with 600nm as hole
place hole at bar.center + ((k - 2.5) * 2um, 0)
holes.append(hole)
}
shape difference with bar, holes as gratingPosition. The operands are evaluated where they are placed, and the operation runs in cell coordinates, so the result lies where its operands are. A derived shape cannot be placed. Its bounding-box anchors (center, bottom_left, …) and width / height work as for any shape, so other shapes and markers can be placed relative to it. To move or repeat a result, clone it: the copy is an ordinary shape with the same geometry, whose local origin is the cell origin, and it must be placed.
Replacement. An operand on the same layer as the derived shape is replaced by it and is not written on its own. Operands on other layers stay, so a cladding on slab can be derived from a waveguide on si without removing the waveguide. A shape can be an operand of several derived shapes; operands must still be placed.
layer si {
shape rect with 16um, 800nm as core
place core at (0, 0)
}
layer slab {
shape dilate with core, 2um as cladding
shape difference with cladding, core as trench
}Here cladding replaces nothing (its operand is on si), trench replaces cladding (both on slab), and core stays on si.
Evaluation. A derived shape is computed when one of its anchors is read, or at the end of the cell. A dependency cycle between derived shapes is reported like a placement cycle. Reading an anchor of an empty result, such as the intersection of disjoint shapes, is an error. A result without area writes nothing.
Fill rule. Each polygon covers the region of its nonzero winding: self-crossing and self-touching polygons, and keyhole polygons, mean what they draw. paths take part as their flush, mitered outline.
Booleans
union
union(a, b?): the region covered by a or b. With a list, b can be left out.
shape rect with 10um, 10um as a
shape circle with 6um as b
place a at (0, 0)
place b at a.top_right
shape union with a, b as resultintersection
intersection(a, b): the region covered by both.
shape rect with 10um, 10um as a
shape circle with 6um as b
place a at (0, 0)
place b at a.top_right
shape intersection with a, b as resultdifference
difference(a, b): the region of a outside b. A b that lies inside a leaves a hole; see output for how holes are written.
shape rect with 10um, 10um as a
shape circle with 6um as b
shape rect with 4um, 3um as window
place a at (0, 0)
place b at a.top_right
place window at a.center offset (-1um, -1um)
shape difference with a, [b, window] as resultxor
xor(a, b): the region covered by exactly one of a and b.
shape rect with 10um, 10um as a
shape circle with 6um as b
place a at (0, 0)
place b at a.top_right
shape xor with a, b as resultMorphology
dilate
dilate(a, distance, join?, segments?): a grown by distance, the Minkowski sum with a disk of radius distance (a mitered square for join = "miter"). Holes and gaps narrower than 2 · distance close.
| Parameter | Type | Default | Meaning |
|---|---|---|---|
a | shape or [shape] | required | The shapes to grow. |
distance | size | required | Growth, not negative. |
join | string | "miter" | Convex corners: "miter" keeps them sharp, squared off where they would reach further than twice the distance (turns of more than 120°); "round" makes circular arcs (a true disk dilation); "bevel" cuts straight between the two offset edges. |
segments | integer | auto | Segments per full circle of the round corners (auto: 1 DBU chord error). |
layer si {
shape cross with 10um, 3um as a
shape cross with 10um, 3um as b
shape cross with 10um, 3um as c
place a at (0, 0)
place b at (18um, 0)
place c at (36um, 0)
}
layer slab {
shape dilate with a, 2um as miter
shape dilate with b, 2um, join = "round" as round
shape dilate with c, 2um, join = "bevel" as bevel
}Concave corners stay sharp for every join: every point within the distance of the shape is covered, and no more.
erode
erode(a, distance, join?, segments?): a shrunk by distance, the set of points whose disk of radius distance fits inside a. Parts narrower than 2 · distance disappear. The parameters are those of dilate; join applies to concave corners, which erosion rounds ("round"), keeps sharp ("miter") or cuts ("bevel"), while convex corners stay sharp.
layer si {
shape polygon with [(0, 0), (14um, 0), (14um, 5um), (5um, 5um), (5um, 14um), (0, 14um)] as l
place l at (0, 0)
}
layer slab {
shape erode with l, 1500nm, join = "round" as inner
}Opening and closing
Two steps with the same distance make the classic morphological filters. An opening (erode, then dilate) rounds convex corners and removes parts narrower than twice the distance; a closing (dilate, then erode) rounds concave corners and fills gaps. With round joins the corners become arcs of that radius:
shape polygon with [(0, 0), (14um, 0), (14um, 5um), (5um, 5um), (5um, 14um), (0, 14um)] as l
place l at (0, 0)
shape erode with l, 2um as core
shape dilate with core, 2um, join = "round" as openedshape polygon with [(0, 0), (14um, 0), (14um, 5um), (5um, 5um), (5um, 14um), (0, 14um)] as l
place l at (0, 0)
shape dilate with l, 2um, join = "round" as grown
shape erode with grown, 2um, join = "round" as closedBoth results are on si, so every intermediate shape is replaced by the next one.
Recipes
- Cladding and exclusion layers.
dilatethe waveguide shapes onto another layer, as in the example above. Withjoin = "round"the cladding keeps a constant distance around bends. Dilation also extends past the waveguide ends;intersectionwith a rectangle trims it. - Trenches for rib waveguides:
differenceof the dilated core and the core. - Holes, gratings and photonic crystals: collect the holes in a loop and subtract the list, as in the grating above.
- Rounded corners of any polygon: an opening or closing with round joins, or
corner_radiuson the primitive itself. - Merging overlapping shapes into one polygon:
unionof the list.
Precision and output
The operations run on the database grid (1 nm by default). All edges are first snap-rounded: every edge is routed through the centers of the grid pixels around the vertices and crossings it passes, so edges meet only at vertices and no new crossings appear. A sweep then finds the winding number of each operand on both sides of every edge and keeps the edges where the result changes. Results deviate from exact geometry by less than one grid unit.
GDS polygons cannot have holes. A result with holes is written as a keyhole polygon: each hole is joined to the outer boundary by a zero-width cut from its rightmost vertex towards +x (straight when the cut meets the boundary on the grid). Axis-aligned rectangles are written as boxes. When writing GDS, polygons of more than 8000 vertices are halved along their longer side until every piece fits.
Reference: equivalents in other tools
Versions: gdsfactory 9, KLayout 0.30, gdstk 1.0, Luceda IPKISS 3 (import ipkiss3.all as i3) and Nazca Design 0.6 (nazca.clipper).
gdsfactory
gdsfactory works on layers of components and flattens the hierarchy; A and B are components or instances.
| gdsfactory | laylight |
|---|---|
gf.boolean(A, B, "or", layer) (also "|") | union with a, b |
gf.boolean(A, B, "and", layer) ("&") | intersection with a, b |
gf.boolean(A, B, "not", layer) ("-", "A-B") | difference with a, b |
gf.boolean(A, B, "xor", layer) ("^") | xor with a, b |
c.offset(layer, distance, corner_mode=2) | dilate with shapes, distance (see the corner conventions below) |
c.offset(layer, -distance) | erode with shapes, distance |
c.over_under(layer, distance) | dilate, then erode (a closing) |
c.get_region(layer, merge=True) | union with shapes |
KLayout (Region)
| KLayout | laylight |
|---|---|
r1 | r2 (+ joins without merging) | union with a, b |
r1 & r2 | intersection with a, b |
r1 - r2 | difference with a, b |
r1 ^ r2 | xor with a, b |
r.sized(d, mode) | dilate with a, d; KLayout distances are in DBU |
r.sized(-d, mode) | erode with a, d |
r.minkowski_sum(circle) | dilate with a, d, join = "round" |
r.merged() | union with a |
r.rounded_corners(r_inner, r_outer, n) | an opening / closing with round joins |
gdstk
| gdstk | laylight |
|---|---|
gdstk.boolean(a, b, "or") | union with a, b |
gdstk.boolean(a, b, "and") | intersection with a, b |
gdstk.boolean(a, b, "not") | difference with a, b |
gdstk.boolean(a, b, "xor") | xor with a, b |
gdstk.offset(p, d, join="miter", tolerance=2) | dilate with p, d |
gdstk.offset(p, d, join="round") | dilate with p, d, join = "round" |
gdstk.offset(p, -d, ...) | erode with p, d, ... |
Luceda IPKISS
| IPKISS | laylight |
|---|---|
shape1 | shape2, boundary1 | boundary2 | union with a, b |
shape1 & shape2 | intersection with a, b |
shape1 - shape2 | difference with a, b |
shape1 ^ shape2 | xor with a, b |
i3.get_elements_for_generated_layers(elements, {L0 - L1: Lout}) | a derived shape on the output layer with the shapes of both layers as operands |
i3.ShapeGrow(shape, amount), i3.ShapeOffset(shape, offset) | dilate with a, amount |
i3.ShapeGrow(shape, -amount) | erode with a, amount |
i3.merge_elements(elements, layers) | union with shapes |
Nazca Design
| Nazca | laylight |
|---|---|
clipper.merge_polygons(paths) | union with shapes |
clipper.clip_polygons(A, B) | intersection with a, b |
clipper.diff_polygons(A, B) | difference with a, b |
clipper.xor_polygons(A, B) | xor with a, b |
clipper.grow_polygons(paths, grow, jointype="round") | dilate with shapes, grow, join = "round" |
nd.Polygon(...).grow(grow, jointype="square") | dilate with a, grow |
clipper.grow_polygons(paths, -d) | erode with shapes, d |
add_layer2xsection(xs, layer, growx=d) | dilate of the waveguide onto the cladding layer |
Conventions that differ
- Miter limit. laylight's
"miter"keeps a corner sharp up to twice the distance and squares it off beyond: the same as gdstk'sjoin="miter", tolerance=2(Clipper). KLayout'ssizedmodes cut corners by how far the miter extends along the edges. The default mode 2 keeps right angles and cuts sharper corners; modes 4 and 5 are close to an unlimited miter. IPKISS'sShapeGrowis an unlimited miter. - Bevel. laylight's
"bevel"cuts straight between the two offset edges, like KLayout mode 0. gdstk'sjoin="bevel"is Clipper's square join, which cuts the corner at the distance, perpendicular to its bisector. - Round. laylight's
"round"is a true disk dilation or erosion. KLayout has no round sizing mode; useminkowski_sumwith a circle. Nazca'sgrow_polygonsdefaults to round joins, butPolygon.growdefaults to square. - Scope. gdsfactory, KLayout layer operations and IPKISS generated layers work on whole layers and flatten the cell hierarchy. laylight operates on named shapes of one cell and does not look into instances.
- Grid. laylight computes on the DBU grid with snap rounding. gdstk rounds to
precision(1 nm by default); Nazca'sgrow_polygonsusesaccuracy(0.1 µm by default).