GDSPY(1)

๐Ÿ“ฆ
version 1.4.2 ยท 5 versions
, in ubuntu resolute, ubuntu stonking

๐Ÿ’– NAME ๐Ÿ’–

okay so bestie, meet gdspy โ€” the gdspy Documentation, no cap. โœจ

gdspy is a Python module that lets you create GDSII stream files. like, that's its whole thing and it ate.

Most features of the GDSII format are implemented in it, including support for polygons with any number of vertices. yes, ANY number. iconic behavior.

GDSII format references, if you wanna go read the lore yourself:

For installation instructions and other information, please check out the GitHub repository at https://github.com/heitzmann/gdspy or the README file included with the source. ๐Ÿซถ


โœจ GETTING STARTED โœจ

GDSII files contain a hierarchical representation of any polygonal geometry. They're mainly used in the microelectronics industry for the design of mask layouts, but they also show up in other areas. versatile queen. ๐Ÿ‘‘

Because it's a hierarchical format, repeated structures โ€” like identical transistors โ€” can be defined once and referenced multiple times in the layout, which reduces the file size. the way that it saves spaceโ€ฆ obsessed.

there's one important limitation in the GDSII format, and it's kinda the main character of this whole warning: it only supports weakly simple polygons (https://en.wikipedia.org/wiki/Simple_polygon), that is, polygons whose segments are allowed to intersect, but not cross. ๐Ÿ’€

In particular, curves and shapes with holes are not directly supported. Holes can be defined, nonetheless, by connecting their boundary to the boundary of the enclosing shape. In the case of curves, they have to be approximated by a polygon. The number of points in the polygonal approximation can be increased to better approximate the original curve up to some acceptable error.

The original GDSII format limits the number of vertices in a polygon to 199. Most modern software disregards this limit and allows an arbitrary number of points per polygon. Gdspy follows the modern version of GDSII, but this is an important issue to keep in mind if the generated file is going to be used in older systems. old systems stay in their lane fr fr.

The units used to represent shapes in the GDSII format are defined by the user. The default unit in gdspy is 1 ยตm (10โปโถ m), but that can be easily changed by the user. so chill.

๐Ÿฆ‹ First GDSII

Let's create our first GDSII file, bestie. it's giving first-day-of-school:

import gdspy

# Create the geometry: a single rectangle.
rect = gdspy.Rectangle((0, 0), (2, 1))
cell = gdspy.Cell('FIRST')
cell.add(rect)

# Save all created cells in file 'first.gds'.
gdspy.write_gds('first.gds')

# Optionally, display all cells using the internal viewer.
gdspy.LayoutViewer()

So after importing the gdspy module, we create a gdspy.Rectangle with opposing corners at positions (0, 0) and (2, 1).

Then a gdspy.Cell is created and the rectangle is added to the cell. All shapes in the GDSII format exist inside cells. A cell can be imagined as a piece of paper where the layout will be defined. Later, the cells can be used to create a hierarchy of geometries, as we'll see in References. nesting dolls but make it silicon. ๐ŸŽ€

Finally, the whole structure is saved in a file called "first.gds" in the current directory. By default, all created cells are included in this operation.

The GDSII file can be opened in a number of viewers and editors, such as KLayout (https://klayout.de/). Alternatively, gdspy includes a simple viewer that can also be used: gdspy.LayoutViewer.

๐Ÿ“ Polygons

General polygons can be defined by an ordered list of vertices. The orientation of the vertices (clockwise / counter-clockwise) is not important โ€” she doesn't gatekeep. ๐Ÿ’…

# Create a polygon from a list of vertices
points = [(0, 0), (2, 2), (2, 6), (-6, 6), (-6, -6), (-4, -4), (-4, 4), (0, 4)]
poly = gdspy.Polygon(points)

[image]

๐Ÿ•ณ๏ธ Holes

As mentioned back in Getting Started, holes have to be connected to the outer boundary of the polygon, like in the following example:

# Manually connect the hole to the outer boundary
cutout = gdspy.Polygon(
    [(0, 0), (5, 0), (5, 5), (0, 5), (0, 0), (2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]
)

[image]

๐ŸŒธ Circles

The gdspy.Round class creates circles, ellipses, doughnuts, arcs and slices. In all cases, the arguments tolerance or number_of_points will control the number of vertices used to approximate the curved shapes. it's literally doing calculus for your aesthetic. ๐Ÿ˜ญ

If the number of vertices in the polygon is larger than max_points (199 by default), it will be fractured into many smaller polygons with at most max_points vertices each.

# Circle centered at (0, 0), with radius 2 and tolerance 0.1
circle = gdspy.Round((0, 0), 2, tolerance=0.01)

# To create an ellipse, simply pass a list with 2 radii.
# Because the tolerance is small (resulting a large number of
# vertices), the ellipse is fractured in 2 polygons.
ellipse = gdspy.Round((4, 0), [1, 2], tolerance=1e-4)

# Circular arc example
arc = gdspy.Round(
    (2, 4),
    2,
    inner_radius=1,
    initial_angle=-0.2 * numpy.pi,
    final_angle=1.2 * numpy.pi,
    tolerance=0.01,
)

[image]

๐ŸŽ€ Curves

Constructing complex polygons by manually listing all vertices in gdspy.Polygon can be challenging, ngl. The class gdspy.Curve can be used to facilitate the creation of polygons by drawing their shapes step-by-step. It uses a syntax similar to the SVG path specification (https://www.w3.org/TR/SVG/paths.html). main character energy from a path spec. โœจ

A short summary of the available methods is presented below:

  • L / l โ€” Line segments
  • H / h โ€” Horizontal line segments
  • V / v โ€” Vertical line segments
  • C / c โ€” Cubic Bezier curve
  • S / s โ€” Smooth cubic Bezier curve
  • Q / q โ€” Quadratic Bezier curve
  • T / t โ€” Smooth quadratic Bezier curve
  • B / b โ€” General degree Bezier curve
  • I / i โ€” Smooth interpolating curve
  • arc โ€” Elliptical arc

The uppercase version of the methods considers that all coordinates are absolute, whereas the lowercase considers that they are relative to the current end point of the curve. Except for gdspy.Curve.I(), gdspy.Curve.i() and gdspy.Curve.arc(), they accept variable numbers of arguments that are used as coordinates to construct the primitive. flexible much? ๐Ÿ’…

# Construct a curve made of a sequence of line segments
c1 = gdspy.Curve(0, 0).L(1, 0, 2, 1, 2, 2, 0, 2)
p1 = gdspy.Polygon(c1.get_points())

# Construct another curve using relative coordinates
c2 = gdspy.Curve(3, 1).l(1, 0, 2, 1, 2, 2, 0, 2)
p2 = gdspy.Polygon(c2.get_points())

[image]

Coordinate pairs can be given as a complex number: real and imaginary parts are used as x and y coordinates, respectively. That is useful to define points in polar coordinates. nerd sniping but make it cute. ๐Ÿงš

Elliptical arcs have syntax similar to gdspy.Round, but they allow for an extra rotation of the major axis of the ellipse.

# Use complex numbers to facilitate writing polar coordinates
c3 = gdspy.Curve(0, 2).l(4 * numpy.exp(1j * numpy.pi / 6))
# Elliptical arcs have syntax similar to gdspy.Round
c3.arc((4, 2), 0.5 * numpy.pi, -0.5 * numpy.pi)
p3 = gdspy.Polygon(c3.get_points())

[image]

Other curves can be constructed as cubic, quadratic and general-degree Bezier curves. Additionally, a smooth interpolating curve can be calculated with the methods gdspy.Curve.I() and gdspy.Curve.i(), which have a number of arguments to control the shape of the curve.

# Cubic Bezier curves can be easily created with C and c
c4 = gdspy.Curve(0, 0).c(1, 0, 1, 1, 2, 1)
# Smooth continuation with S or s
c4.s(1, 1, 0, 1).S(numpy.exp(1j * numpy.pi / 6), 0, 0)
p4 = gdspy.Polygon(c4.get_points())

# Similarly for quadratic Bezier curves
c5 = gdspy.Curve(5, 3).Q(3, 2, 3, 0, 5, 0, 4.5, 1).T(5, 3)
p5 = gdspy.Polygon(c5.get_points())

# Smooth interpolating curves can be built using I or i, including
# closed shapes
c6 = gdspy.Curve(0, 3).i([(1, 0), (2, 0), (1, -1)], cycle=True)
p6 = gdspy.Polygon(c6.get_points())

[image]

๐Ÿ’… Transformations

All polygons can be transformed through gdspy.PolygonSet.translate(), gdspy.PolygonSet.rotate(), gdspy.PolygonSet.scale(), and gdspy.PolygonSet.mirror(). The transformations are applied in-place, i.e., no polygons are created. she's efficient like that. ๐ŸŒŸ

poly = gdspy.Rectangle((-2, -2), (2, 2))
poly.rotate(numpy.pi / 4)
poly.scale(1, 0.5)

[image]

๐Ÿ‘‘ Layer and Datatype

All shapes in the GDSII format are tagged with 2 properties: layer and datatype (or texttype in the case of gdspy.Label). They are always 0 by default, but can be any integer in the range from 0 to 255. two little tags, infinite lore. ๐Ÿ’œ

These properties have no predefined meaning. Literally none, bestie โ€” it's up to the system using the GDSII file to choose what to do with those tags. For example, in the CMOS fabrication process, each layer could represent a different lithography level. ๐Ÿ’…

In the example below, a single file stores different fabrication masks in separate layer and datatype configurations. Look at her go:

# Layer/datatype definitions for each step in the fabrication
ld_fulletch = {"layer": 1, "datatype": 3}
ld_partetch = {"layer": 2, "datatype": 3}
ld_liftoff = {"layer": 0, "datatype": 7}

p1 = gdspy.Rectangle((-3, -3), (3, 3), **ld_fulletch)
p2 = gdspy.Rectangle((-5, -3), (-3, 3), **ld_partetch)
p3 = gdspy.Rectangle((5, -3), (3, 3), **ld_partetch)
p4 = gdspy.Round((0, 0), 2.5, number_of_points=6, **ld_liftoff)

[image]

โœจ References โœจ

References are the whole reason the GDSII format gets its hierarchical features, no cap. The way they work is by reusing a cell's content in another cell โ€” without actually copying the whole entire geometry. Obsessed. ๐Ÿซถ

As a simplistic example: imagine we're designing a simple electronic circuit that uses hundreds of transistors, but they all have the same shape. We can draw the transistor just once and reference it throughout the circuit, rotating or mirroring each instance as necessary. Iconic behaviour honestly. ๐Ÿ‘‘

Besides creating single references with gdspy.CellReference <#gdspy.CellReference>, it is possible to create full 2D arrays with a single entity using gdspy.CellArray <#gdspy.CellArray>. Both are exemplified below:

# Create a cell with a component that is used repeatedly
contact = gdspy.Cell("CONTACT")
contact.add([p1, p2, p3, p4])

# Create a cell with the complete device
device = gdspy.Cell("DEVICE")
device.add(cutout)
# Add 2 references to the component changing size and orientation
ref1 = gdspy.CellReference(contact, (3.5, 1), magnification=0.25)
ref2 = gdspy.CellReference(contact, (1, 3.5), magnification=0.25, rotation=90)
device.add([ref1, ref2])

# The final layout has several repetitions of the complete device
main = gdspy.Cell("MAIN")
main.add(gdspy.CellArray(device, 3, 2, (6, 7)))

[image]

๐ŸŒˆ Paths ๐ŸŒˆ

Besides polygons, the GDSII format defines paths, witch are polygonal chains with associated width and end caps. And when we say the width is a single number, we mean it's constant throughout the path โ€” no sneaky tapering. The end caps can be flush, round, or extended by a custom distance. That's it, that's the whole menu. ๐ŸŽ€

There is no specification for the joins between adjacent segments, so it is up to the system using the GDSII file to specify those. Usually the joins are straight extensions of the path boundaries up to some beveling limit. Gdspy also uses this specification for the joins. (Consistency queen. ๐Ÿ’…)

It is possible to circumvent all of the above limitations within gdspy by storing paths as polygons in the GDSII file. The disadvantage of this solution is that other software will not be able to edit the geometry as paths, since that information is lost. So likeโ€ฆ you gain, you lose, iykyk. ๐Ÿ’€

The construction of paths (either GDSII paths or polygonal paths) in gdspy is quite rich, fr fr. There are 3 classes that can be used depending on the requirements of the desired path.

โœจ Polygonal-Only Paths โœจ

The class gdspy.Path <#gdspy.Path> is designed to allow the creation of path-like polygons in a piece-wise manner. It is the most computationally efficient class between the three because it does not calculate joins. That means the user is responsible for designing the joins. And yes, the paths can end up with discontinuities if care is not taken when creating them โ€” consider yourself warned. ๐Ÿ˜ญ

# Start a path at (0, 0) with width 1
path1 = gdspy.Path(1, (0, 0))

# Add a segment to the path goin in the '+y' direction
path1.segment(4, "+y")

# Further segments or turns will folow the current path direction
# to ensure continuity
path1.turn(2, "r")
path1.segment(1)
path1.turn(3, "rr")

[image]

Just as with Circles, all curved geometry is approximated by line segments. The number of segments is similarly controlled by a tolerance or a number_of_points argument. Curves also include fracturing to limit the number of points in each polygon. Slay.

More complex paths can be constructed with the methods gdspy.Path.bezier() <#gdspy.Path.bezier>, gdspy.Path.smooth() <#gdspy.Path.smooth>, and gdspy.Path.parametric() <#gdspy.Path.parametric>. The example below demonstrates a couple of possibilities:

path2 = gdspy.Path(0.5, (0, 0))

# Start the path with a smooth Bezier S-curve
path2.bezier([(0, 5), (5, 5), (5, 10)])

# We want to add a spiral curve to the path.  The spiral is defined
# as a parametric curve.  We make sure spiral(0) = (0, 0) so that
# the path is continuous.
def spiral(u):
    r = 4 - 3 * u
    theta = 5 * u * numpy.pi
    x = r * numpy.cos(theta) - 4
    y = r * numpy.sin(theta)
    return (x, y)

# It is recommended to also define the derivative of the parametric
# curve, otherwise this derivative must be calculated nummerically.
# The derivative is used to define the side boundaries of the path,
# so, in this case, to ensure continuity with the existing S-curve,
# we make sure the the direction at the start of the spiral is
# pointing exactly upwards, as if is radius were constant.
# Additionally, the exact magnitude of the derivative is not
# important; gdspy only uses its direction.
def dspiral_dt(u):
    theta = 5 * u * numpy.pi
    dx_dt = -numpy.sin(theta)
    dy_dt = numpy.cos(theta)
    return (dx_dt, dy_dt)

# Add the parametric spiral to the path
path2.parametric(spiral, dspiral_dt)

[image]

The width of the path does not have to be constant, bestie โ€” glow up your widths whenever. Each path component can linearly taper the width of the path by using the final_width argument. And in the case of a parametric curve, more complex width changes can be created by setting final_width to a function. Go off. ๐Ÿฆ‹

Finally, parallel paths can be created simultaneously with the help of arguments number_of_paths, distance, and final_distance. So many options, we love to see it:

# Start 3 parallel paths with center-to-center distance of 1.5
path3 = gdspy.Path(0.1, (-5.5, 3), number_of_paths=3, distance=1.5)

# Add a segment tapering the widths up to 0.5
path3.segment(2, "-y", final_width=0.5)

# Add a bezier curve decreasing the distance between paths to 0.75
path3.bezier([(0, -2), (1, -3), (3, -3)], final_distance=0.75)

# Add a parametric section to modulate the width with a sinusoidal
# shape.  Note that the algorithm that determines the number of
# evaluations of the parametric curve does not take the width into
# consideration, so we have to manually increase this parameter.
path3.parametric(
    lambda u: (5 * u, 0),
    lambda u: (1, 0),
    final_width=lambda u: 0.4 + 0.1 * numpy.cos(10 * numpy.pi * u),
    number_of_evaluations=256,
)

# Add a circular turn and a final tapering segment.
path3.turn(3, "l")
path3.segment(2, final_width=1, final_distance=1.5)

[image]

๐Ÿฆ‹ Flexible Paths ๐Ÿฆ‹

Although very efficient, gdspy.Path <#gdspy.Path> is limited in the type of path it can provide. So like, she's fast but she's picky. ๐Ÿ˜ญ For example, if we simply want a path going through a sequence of points, we need a class that can correctly compute the joins between segments. That's one of the advantages of class gdspy.FlexPath <#gdspy.FlexPath>. Other path construction methods are similar to those in gdspy.Path <#gdspy.Path>.

A few features of gdspy.FlexPath <#gdspy.FlexPath> are:

  • paths can be stored as proper GDSII paths;
  • end caps and joins can be specified by the user;
  • each parallel path can have a different width;
  • spacing between parallel paths is arbitrary; the user specifies the offset of each path individually.
# Path defined by a sequence of points and stored as a GDSII path
sp1 = gdspy.FlexPath(
    [(0, 0), (3, 0), (3, 2), (5, 3), (3, 4), (0, 4)], 1, gdsii_path=True
)

# Other construction methods can still be used
sp1.smooth([(0, 2), (2, 2), (4, 3), (5, 1)], relative=True)

# Multiple parallel paths separated by 0.5 with different widths,
# end caps, and joins.  Because of the join specification, they
# cannot be stared as GDSII paths, only as polygons.
sp2 = gdspy.FlexPath(
    [(12, 0), (8, 0), (8, 3), (10, 2)],
    [0.3, 0.2, 0.4],
    0.5,
    ends=["extended", "flush", "round"],
    corners=["bevel", "miter", "round"],
)
sp2.arc(2, -0.5 * numpy.pi, 0.5 * numpy.pi)
sp2.arc(1, 0.5 * numpy.pi, 1.5 * numpy.pi)

[image]

The following example shows other features, such as width tapering, arbitrary offsets, and custom joins and end caps. She's giving โœจmain character energyโœจ:

# Path corners and end caps can be custom functions.
# This corner function creates 'broken' joins.
def broken(p0, v0, p1, v1, p2, w):
    # Calculate intersection point p between lines defined by
    # p0 + u0 * v0 (for all u0) and p1 + u1 * v1 (for all u1)
    den = v1[1] * v0[0] - v1[0] * v0[1]
    lim = 1e-12 * (v0[0] ** 2 + v0[1] ** 2) * (v1[0] ** 2 + v1[1] ** 2)
    if den ** 2 < lim:
        # Lines are parallel: use mid-point
        u0 = u1 = 0
        p = 0.5 * (p0 + p1)
    else:
        dx = p1[0] - p0[0]
        dy = p1[1] - p0[1]
        u0 = (v1[1] * dx - v1[0] * dy) / den
        u1 = (v0[1] * dx - v0[0] * dy) / den
        p = 0.5 * (p0 + v0 * u0 + p1 + v1 * u1)
    if u0 <= 0 and u1 >= 0:
        # Inner corner
        return [p]
    # Outer corner
    return [p0, p2, p1]

# This end cap function creates pointy caps.
def pointy(p0, v0, p1, v1):
    r = 0.5 * numpy.sqrt(numpy.sum((p0 - p1) ** 2))
    v0 /= numpy.sqrt(numpy.sum(v0 ** 2))
    v1 /= numpy.sqrt(numpy.sum(v1 ** 2))
    return [p0, 0.5 * (p0 + p1) + 0.5 * (v0 - v1) * r, p1]

# Paths with arbitrary offsets from the center and multiple layers.
sp3 = gdspy.FlexPath(
    [(0, 0), (0, 1)],
    [0.1, 0.3, 0.5],
    offset=[-0.2, 0, 0.4],
    layer=[0, 1, 2],
    corners=broken,
    ends=pointy,
)
sp3.segment((3, 3), offset=[-0.5, -0.1, 0.5])
sp3.segment((4, 1), width=[0.2, 0.2, 0.2], offset=[-0.2, 0, 0.2])
sp3.segment((0, -1), relative=True)

[image]

The corner type 'circular bend' (together with the bend_radius argument) can be used to automatically curve the path. This feature is used in Example: Integrated Photonics. ๐ŸŒธ

# Path created with automatic bends of radius 5
points = [(0, 0), (0, 10), (20, 0), (18, 15), (8, 15)]
sp4 = gdspy.FlexPath(
    points, 0.5, corners="circular bend", bend_radius=5, gdsii_path=True
)

# Same path, generated with natural corners, for comparison
sp5 = gdspy.FlexPath(points, 0.5, layer=1, gdsii_path=True)

[image]


๐Ÿฆ‹ Robust Paths ๐Ÿฆ‹

okay bestie, sometimes gdspy.FlexPath just cannot figure out the joins and it's giving stressed ๐Ÿ’€. This happens a lot when the width or the offset of the path is big compared to the length of the segments you're joining. Curves that meet other curves or segments at sharp angles? Yeah, prime example of that whole situation.

Enter gdspy.RobustPath โ€” the class you reach for when robustness matters more than efficiency, especially with sharp corners or big offsets in your paths. โœจ

The downsides, so you know what you're getting into:

  • you lose computation efficiency (compared to the other 2 classes, so like, it's slower ๐Ÿ’…)
  • you ๐Ÿšซ can't specify corner shapes. periodt.

The upsides, tho? As we said: way more robustness when generating the final geometry, plus the freedom to use custom functions to parameterize the widths or the offsets of your paths in any construction method. iconic tbh. ๐Ÿ‘‘

# Create 4 parallel paths in different layers
lp = gdspy.RobustPath(
    (50, 0),
    [2, 0.5, 1, 1],
    [0, 0, -1, 1],
    ends=["extended", "round", "flush", "flush"],
    layer=[0, 2, 1, 1],
)
lp.segment((45, 0))
lp.segment(
    (5, 0),
    width=[lambda u: 2 + 16 * u * (1 - u), 0.5, 1, 1],
    offset=[
        0,
        lambda u: 8 * u * (1 - u) * numpy.cos(12 * numpy.pi * u),
        lambda u: -1 - 8 * u * (1 - u),
        lambda u: 1 + 8 * u * (1 - u),
    ],
)
lp.segment((0, 0))
lp.smooth(
    [(5, 10)],
    angles=[0.5 * numpy.pi, 0],
    width=0.5,
    offset=[-0.25, 0.25, -0.75, 0.75],
)
lp.parametric(
    lambda u: numpy.array((45 * u, 4 * numpy.sin(6 * numpy.pi * u))),
    offset=[
        lambda u: -0.25 * numpy.cos(24 * numpy.pi * u),
        lambda u: 0.25 * numpy.cos(24 * numpy.pi * u),
        -0.75,
        0.75,
    ],
)

[image]

heads up bestie: same energy as gdspy.FlexPath โ€” gdspy.RobustPath can be stored as a GDSII path as long as its width is kept constant. don't forget that part. ๐Ÿ™ˆ

๐ŸŒธ Text ๐ŸŒธ

So in the GDSII world, "text" shows up as labels โ€” those are ASCII annotations you plop down somewhere in the geometry of a given cell. And just like polygons, labels get tagged with layer and texttype values (texttype is literally the label equivalent of the polygon datatype, iykyk). They're handled by the class gdspy.Label. ๐ŸŽ€

But wait, there's more โœจ โ€” gdspy also lets you make text as polygons so it just becomes part of your geometry. The class gdspy.Text creates polygonal text that you use exactly like any other polygons in gdspy. And the font it uses to render characters has only horizontal and vertical edges โ€” which is super important for some laser writing systems. no cap. ๐Ÿ”ฅ

# Label anchored at (1, 3) by its north-west corner
label = gdspy.Label("Sample label", (1, 3), "nw")

# Horizontal text with height 2.25
htext = gdspy.Text("12345", 2.25, (0.25, 6))

# Vertical text with height 1.5
vtext = gdspy.Text("ABC", 1.5, (10.5, 4), horizontal=False)

rect = gdspy.Rectangle((0, 0), (10, 6), layer=10)

[image]


๐Ÿ’œ Geometry Operations ๐Ÿ’œ

gdspy hands you a whole bunch of functions and methods to mess with existing geometry, and the most useful ones are:

  • gdspy.boolean()
  • gdspy.slice()
  • gdspy.offset()
  • gdspy.PolygonSet.fillet()

let's go through them one at a time, bestie. ๐Ÿซถ


๐Ÿ’… Boolean Operations ๐Ÿ’…

Boolean operations (gdspy.boolean()) can be run on polygons, paths, and whole cells. There are four operations defined:

  • 'or' โ€” union
  • 'and' โ€” intersection
  • 'not' โ€” subtraction
  • 'xor' โ€” symmetric subtraction
real talk: these can be computationally expensive ๐Ÿ˜ญ, so it's usually advisable to avoid boolean operations whenever you can. And if you genuinely need them, keeping the number of vertices in all polygons as low as possible also helps a ton.
# Create some text
text = gdspy.Text("GDSPY", 4, (0, 0))
# Create a rectangle extending the text's bounding box by 1
bb = numpy.array(text.get_bounding_box())
rect = gdspy.Rectangle(bb[0] - 1, bb[1] + 1)

# Subtract the text from the rectangle
inv = gdspy.boolean(rect, text, "not")

[image]


๐Ÿ“ Slice Operation ๐Ÿ“

Like the name says, a slice operation subdivides a set of polygons along horizontal or vertical cut lines. โœ‚๏ธ

And here's a lil secret: in a few cases, a boolean operation can be swapped out for one or more slice operations. Because gdspy.slice() is usually way simpler than gdspy.boolean(), it's a good idea to use the former if you possibly can. ๐Ÿ’–

ring1 = gdspy.Round((-6, 0), 6, inner_radius=4)
ring2 = gdspy.Round((0, 0), 6, inner_radius=4)
ring3 = gdspy.Round((6, 0), 6, inner_radius=4)

# Slice the first ring across x=-3, the second ring across x=-3
# and x=3, and the third ring across x=3
slices1 = gdspy.slice(ring1, -3, axis=0)
slices2 = gdspy.slice(ring2, [-3, 3], axis=0)
slices3 = gdspy.slice(ring3, 3, axis=0)

slices = gdspy.Cell("SLICES")

# Keep only the left side of slices1, the center part of slices2
# and the right side of slices3
slices.add(slices1[0])
slices.add(slices2[1])
slices.add(slices3[1])

[image]


โœจ Offset Operation โœจ

The function gdspy.offset() expands or contracts polygons by a fixed amount. It works on individual polygons or on whole sets of them โ€” and in the set case, it may make sense to turn on the argument join_first so it treats the whole geometry as if a boolean 'or' was run on it beforehand. ๐Ÿ’ซ

rect1 = gdspy.Rectangle((-4, -4), (1, 1))
rect2 = gdspy.Rectangle((-1, -1), (4, 4))

# Offset both polygons
# Because we join them first, a single polygon is created.
outer = gdspy.offset([rect1, rect2], 0.5, join_first=True, layer=1)

[image]


๐ŸŽ€ Fillet Operation ๐ŸŽ€

The method gdspy.PolygonSet.fillet() rounds off polygon corners โ€” so cute, so smooth. ๐Ÿฆ‹

But ngl, it does not have a join_first argument the way gdspy.offset() does, so if you're gonna use it on a polygon, that polygon should probably not be fractured. keep that in mind bestie.

multi_path = gdspy.Path(2, (-3, -2))
multi_path.segment(4, "+x")
multi_path.turn(2, "l").turn(2, "r")
multi_path.segment(4)

# Create a copy with joined polygons and no fracturing
joined = gdspy.boolean(multi_path, None, "or", max_points=0)
joined.translate(0, -5)

# Fillet applied to each polygon in the path
multi_path.fillet(0.5)

# Fillet applied to the joined copy
joined.fillet(0.5)

[image]


๐Ÿ‘‘ GDSII Library ๐Ÿ‘‘

Everything you need to build a GDSII file lives inside an instance of GdsLibrary. And it's not just geometry and hierarchy in there โ€” this class also holds a name and the units for all entities.

The name can be any ASCII string. It literally just gets stored in the GDSII file and has no other purpose in gdspy. cute, low maintenance. ๐Ÿ’…

The units, tho? Those need some actual attention, because they can impact the resolution of the polygons in the library when the file gets written. ๐Ÿ‘€


๐ŸŒˆ Units in GDSII ๐ŸŒˆ

Two values are defined here: unit and precision.

unit sets the unit size โ€” in meters โ€” for all entities in the library. So for example, if unit = 1e-6 (that's 10โปโถ m, and it's the default value โœจ), a vertex at (1, 2) should be read as a vertex at real world position (1 ร— 10โปโถ m, 2 ร— 10โปโถ m). But if unit changes to 0.001, then that exact same vertex would be located (in real world coordinates) at (0.001 m, 0.002 m), or (1 mm, 2 mm). same numbers, whole different vibe.

precision is about the type used to store coordinates in the GDSII file: signed 4-byte integers. Because of that, you usually want a finer coordinate grid than 1 unit to define your coordinates. That grid is defined, in meters, by precision, which defaults to 1e-9 (10โปโน m).

When the GDSII file is written, all vertices are snapped to the grid defined by precision. Example time, bestie: with the default values of unit and precision, a vertex at (1.0512, 0.0001) represents real world coordinates (1.0512 ร— 10โปโถ m, 0.0001 ร— 10โปโถ m), or (1051.2 ร— 10โปโน m, 0.1 ร— 10โปโน m) โ€” which will be rounded to integers: (1051 ร— 10โปโน m, 0 ร— 10โปโน m), or (1.051 ร— 10โปโถ m, 0 ร— 10โปโถ m). ๐Ÿ˜ญ

The actual coordinate values written in the GDSII file will be the integers (1051, 0).

Now โ€” by reducing precision from 10โปโน m down to 10โปยนยฒ m, for example, your coordinates get 3 additional decimal places of precision, so the stored values would be (1051200, 100). slay.

โš ๏ธ the downside of increasing the number of decimal places in the file is that you shrink the range of coordinates that can be stored (in real world units). That's because the range of coordinate values writable in the file is [-(2ยณยฒ); 2ยณยน - 1] = [-2,147,483,648; 2,147,483,647]. For the default precision, this range is [-2.147483648 m; 2.147483647 m]. But if precision is set to 10โปยนยฒ m, the same range gets reduced by 1000 times: [-2.147483648 mm; 2.147483647 mm]. ๐Ÿ’€

๐Ÿ’– Saving a GDSII File ๐Ÿ’–

To save a GDSII file, the easiest way is to use gdspy.write_gds(), just like in the First GDSII. That function accepts the arguments unit and precision so you can change the default values, exactly as explained in the section above. ๐ŸŒŸ

Under the hood, it actually calls the gdspy.GdsLibrary.write_gds() method from a global gdspy.GdsLibrary instance: gdspy.current_library. And this instance automatically holds all cells created by gdspy โ€” unless you specifically tell it not to, with the argument exclude_from_current set to True in gdspy.Cell. ๐Ÿ™ˆ

Which means: after saving a file, if you wanna start a brand new GDSII library from scratch using that global instance, it's important to reinitialize it with:

gdspy.current_library = gdspy.GdsLibrary()

๐Ÿงš Loading a GDSII File ๐Ÿงš

To load an existing GDSII file (or to work with multiple libraries at the same time), you can create a new instance of GdsLibrary, or just reuse one you already have:

# Load a GDSII file into a new library
gdsii = gdspy.GdsLibrary(infile='filename.gds')

# Use the current global library to load the file
gdspy.current_library.read_gds('filename.gds')

Okay so like, either way, you GOTTA be careful when you merge the units from the library and the file, bestie ๐Ÿ˜ญ โ€” that whole thing is controlled by the units argument over in gdspy.GdsLibrary.read_gds() (or the keyword argument on gdspy.GdsLibrary itself). Do not sleep on this one.

And how do you actually get to the cells in the library you just loaded? Through the dictionary gdspy.GdsLibrary.cell_dict โ€” that's your cells indexed by name, periodt โœจ. Wanna find the top-level cells (the ones sitting on top of the hierarchy, i.e., cells that aren't referenced by any other cells)? That's gdspy.GdsLibrary.top_level() <#gdspy.GdsLibrary.top_level>. And if you wanna import one specific cell plus all its dependencies straight into gdspy.current_library, gdspy.GdsLibrary.extract() <#gdspy.GdsLibrary.extract> is your girl. ๐Ÿซถ


โœจ EXAMPLES โœจ

๐Ÿ’… Integrated Photonics ๐Ÿ’…

This one is giving glow-up energy โ€” it shows you how to use gdspy primitives to build way more complex structures. ๐Ÿ”ฅ

These structures are commonly used in the field of integrated photonics.

photonics.py

photonics.gds

######################################################################
#                                                                    #
#  Copyright 2009-2019 Lucas Heitzmann Gabrielli.                    #
#  This file is part of gdspy, distributed under the terms of the    #
#  Boost Software License - Version 1.0.  See the accompanying       #
#  LICENSE file or <http://www.boost.org/LICENSE_1_0.txt>            #
#                                                                    #
######################################################################

import numpy
import gdspy

def grating(period, number_of_teeth, fill_frac, width, position, direction, lda=1, sin_theta=0,
            focus_distance=-1, focus_width=-1, tolerance=0.001, layer=0, datatype=0):
    '''
    Straight or focusing grating.

    period          : grating period
    number_of_teeth : number of teeth in the grating
    fill_frac       : filling fraction of the teeth (w.r.t. the period)
    width           : width of the grating
    position        : grating position (feed point)
    direction       : one of {'+x', '-x', '+y', '-y'}
    lda             : free-space wavelength
    sin_theta       : sine of incidence angle
    focus_distance  : focus distance (negative for straight grating)
    focus_width     : if non-negative, the focusing area is included in
                      the result (usually for negative resists) and this
                      is the width of the waveguide connecting to the
                      grating
    tolerance       : same as in `path.parametric`
    layer           : GDSII layer number
    datatype        : GDSII datatype number

    Return `PolygonSet`
    '''
    if focus_distance < 0:
        p = gdspy.L1Path((position[0] - 0.5 * width,
                          position[1] + 0.5 * (number_of_teeth - 1 + fill_frac) * period),
                         '+x', period * fill_frac, [width], [], number_of_teeth, period,
                         layer=layer, datatype=datatype)
    else:
        neff = lda / float(period) + sin_theta
        qmin = int(focus_distance / float(period) + 0.5)
        p = gdspy.Path(period * fill_frac, position)
        c3 = neff**2 - sin_theta**2
        w = 0.5 * width
        for q in range(qmin, qmin + number_of_teeth):
            c1 = q * lda * sin_theta
            c2 = (q * lda)**2
            p.parametric(lambda t: (width * t - w,
                                    (c1 + neff * numpy.sqrt(c2 - c3 * (width * t - w)**2)) / c3),
                         tolerance=tolerance, max_points=0, layer=layer, datatype=datatype)
            p.x = position[0]
            p.y = position[1]
        sz = p.polygons[0].shape[0] // 2
        if focus_width == 0:
            p.polygons[0] = numpy.vstack((p.polygons[0][:sz, :], [position]))
        elif focus_width > 0:
            p.polygons[0] = numpy.vstack((p.polygons[0][:sz, :],
                                          [(position[0] + 0.5 * focus_width, position[1]),
                                           (position[0] - 0.5 * focus_width, position[1])]))
        p.fracture()
    if direction == '-x':
        return p.rotate(0.5 * numpy.pi, position)
    elif direction == '+x':
        return p.rotate(-0.5 * numpy.pi, position)
    elif direction == '-y':
        return p.rotate(numpy.pi, position)
    else:
        return p

if __name__ == '__main__':
    # Examples

    # Negative resist example
    width = 0.45
    bend_radius = 50.0
    ring_radius = 20.0
    taper_len = 50.0
    input_gap = 150.0
    io_gap = 500.0
    wg_gap = 20.0
    ring_gaps = [0.06 + 0.02 * i for i in range(8)]

    ring = gdspy.Cell('NRing')
    ring.add(gdspy.Round((ring_radius, 0), ring_radius, ring_radius - width, tolerance=0.001))

    grat = gdspy.Cell('NGrat')
    grat.add(grating(0.626, 28, 0.5, 19, (0, 0), '+y', 1.55,
                     numpy.sin(numpy.pi * 8 / 180), 21.5, width,
                     tolerance=0.001))

    taper = gdspy.Cell('NTaper')
    taper.add(gdspy.Path(0.12, (0, 0)).segment(taper_len, '+y', final_width=width))

    c = gdspy.Cell('Negative')
    for i, gap in enumerate(ring_gaps):
        path = gdspy.FlexPath([(input_gap * i, taper_len)], width=width,
                              corners='circular bend', bend_radius=bend_radius,
                              gdsii_path=True)
        path.segment((0, 600 - wg_gap * i), relative=True)
        path.segment((io_gap, 0), relative=True)
        path.segment((0, 300 + wg_gap * i), relative=True)
        c.add(path)
        c.add(gdspy.CellReference(ring, (input_gap * i + width / 2 + gap, 300)))
    c.add(gdspy.CellArray(taper, len(ring_gaps), 1, (input_gap, 0), (0, 0)))
    c.add(gdspy.CellArray(grat, len(ring_gaps), 1, (input_gap, 0), (io_gap, 900 + taper_len)))

    # Positive resist example
    width = 0.45
    ring_radius = 20.0
    big_margin = 10.0
    small_margin = 5.0
    taper_len = 50.0
    bus_len = 400.0
    input_gap = 150.0
    io_gap = 500.0
    wg_gap = 20.0
    ring_gaps = [0.06 + 0.02 * i for i in range(8)]

    ring_margin = gdspy.Rectangle((0, -ring_radius - big_margin),
                                  (2 * ring_radius + big_margin, ring_radius + big_margin))
    ring_hole = gdspy.Round((ring_radius, 0), ring_radius, ring_radius - width, tolerance=0.001)
    ring_bus = gdspy.Path(small_margin, (0, taper_len), number_of_paths=2,
                          distance=small_margin + width)
    ring_bus.segment(bus_len, '+y')

    p = gdspy.Path(small_margin, (0, 0), number_of_paths=2, distance=small_margin + width)
    p.segment(21.5, '+y', final_distance=small_margin + 19)
    grat = gdspy.Cell('PGrat').add(p)
    grat.add(grating(0.626, 28, 0.5, 19, (0, 0), '+y', 1.55,
                     numpy.sin(numpy.pi * 8 / 180), 21.5, tolerance=0.001))

    p = gdspy.Path(big_margin, (0, 0), number_of_paths=2, distance=big_margin + 0.12)
    p.segment(taper_len, '+y', final_width=small_margin, final_distance=small_margin + width)
    taper = gdspy.Cell('PTaper').add(p)

    c = gdspy.Cell('Positive')
    for i, gap in enumerate(ring_gaps):
        path = gdspy.FlexPath([(input_gap * i, taper_len + bus_len)],
                              width=[small_margin, small_margin],
                              offset=small_margin + width, gdsii_path=True)
        path.segment((0, 600 - bus_len - bend_radius - wg_gap * i), relative=True)
        path.turn(bend_radius, 'r')
        path.segment((io_gap - 2 * bend_radius, 0), relative=True)
        path.turn(bend_radius, 'l')
        path.segment((0, 300 - bend_radius + wg_gap * i), relative=True)
        c.add(path)
        dx = width / 2 + gap
        c.add(gdspy.boolean(
            gdspy.boolean(ring_bus, gdspy.copy(ring_margin, dx, 300), 'or', precision=1e-4),
            gdspy.copy(ring_hole, dx, 300), 'not', precision=1e-4).translate(input_gap * i, 0))
    c.add(gdspy.CellArray(taper, len(ring_gaps), 1, (input_gap, 0), (0, 0)))
    c.add(gdspy.CellArray(grat, len(ring_gaps), 1, (input_gap, 0), (io_gap, 900 + taper_len)))

    # Save to a gds file and check out the output
    gdspy.write_gds('photonics.gds')
    gdspy.LayoutViewer()

๐ŸŒˆ Using System Fonts ๐ŸŒˆ

This one uses matplotlib (https://matplotlib.org/) to render text with any typeface that lives on your system ๐Ÿ‘‘. Then the glyph paths get transformed into polygon arrays, and those become gdspy.PolygonSet objects. Ate, no crumbs. ๐Ÿ’–

fonts.py

fonts.gds

######################################################################
#                                                                    #
#  Copyright 2009-2019 Lucas Heitzmann Gabrielli.                    #
#  This file is part of gdspy, distributed under the terms of the    #
#  Boost Software License - Version 1.0.  See the accompanying       #
#  LICENSE file or <http://www.boost.org/LICENSE_1_0.txt>            #
#                                                                    #
######################################################################

from matplotlib.font_manager import FontProperties
from matplotlib.textpath import TextPath
import gdspy

Okay bestie, here's the rest of that example โ€” the function that actually turns your text into shapes, plus the little __main__ block that shows it off:

def render_text(text, size=None, position=(0, 0), font_prop=None, tolerance=0.1):
    path = TextPath(position, text, size=size, prop=font_prop)
    polys = []
    xmax = position[0]
    for points, code in path.iter_segments():
        if code == path.MOVETO:
            c = gdspy.Curve(*points, tolerance=tolerance)
        elif code == path.LINETO:
            c.L(*points)
        elif code == path.CURVE3:
            c.Q(*points)
        elif code == path.CURVE4:
            c.C(*points)
        elif code == path.CLOSEPOLY:
            poly = c.get_points()
            if poly.size > 0:
                if poly[:, 0].min() < xmax:
                    i = len(polys) - 1
                    while i >= 0:
                        if gdspy.inside(poly[:1], [polys[i]], precision=0.1 * tolerance)[0]:
                            p = polys.pop(i)
                            poly = gdspy.boolean([p], [poly], 'xor', precision=0.1 * tolerance,
                                                 max_points=0).polygons[0]
                            break
                        elif gdspy.inside(polys[i][:1], [poly], precision=0.1 * tolerance)[0]:
                            p = polys.pop(i)
                            poly = gdspy.boolean([p], [poly], 'xor', precision=0.1 * tolerance,
                                                 max_points=0).polygons[0]
                        i -= 1
                xmax = max(xmax, poly[:, 0].max())
                polys.append(poly)
    return polys

if __name__ == "__main__":
    fp = FontProperties(family='serif', style='italic')
    text = gdspy.PolygonSet(render_text('Text rendering', 10, font_prop=fp), layer=1)
    gdspy.Cell('TXT').add(text)
    gdspy.write_gds('fonts.gds')
    gdspy.LayoutViewer()

๐ŸŒŸ API REFERENCE ๐ŸŒŸ

๐Ÿฆ‹ Geometry Construction ๐Ÿฆ‹

๐ŸŽ€ PolygonSet ๐ŸŽ€

class gdspy.PolygonSet(polygons, layer=0, datatype=0)

Bases: object

It's literally just a whole set of polygonal objects, bestie. A glow-up bundle of shapes. ๐Ÿ’…

Parameters

  • polygons (iterable of array-like[N][2]) โ€” a list holding all the vertex coordinates for every polygon you're putting in there.
  • layer (integer) โ€” the GDSII layer number for this element.
  • datatype (integer) โ€” the GDSII datatype for this element, and it has to sit between 0 and 255.

Variables

  • polygons (list of numpy array[N][2]) โ€” the coordinates of the vertices of each polygon.
  • layers (list of integer) โ€” the GDSII layer number for each element.
  • datatypes (list of integer) โ€” the GDSII datatype for each element (between 0 and 255).

Notes

The last point should not be equal to the first one โ€” these polygons close themselves automatically, it's giving self-sufficient. ๐Ÿ™ˆ
Also, heads up: the original GDSII specification only supports a maximum of 199 vertices per polygon. That's the limit, no cap. ๐Ÿ’€

get_bounding_box()

Crunch the bounding box of all these polygons.

Returns out โ€” the bounding box of this polygon, shaped like [[x_min, y_min], [x_max, y_max]], or None if the polygon is just empty (sad).

Return type Numpy array[2, 2] or None


rotate(angle, center=(0, 0))

Spin this whole object around. Wheee. ๐ŸŒ€

Parameters

  • angle (number) โ€” the angle of rotation, in radians.
  • center (array-like[2]) โ€” the center point for the rotation.

Returns out โ€” this object.

Return type PolygonSet


scale(scalex, scaley=None, center=(0, 0))

Scale this object up or down, main character energy.

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it just does the same thing as scalex.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns out โ€” this object.

Return type PolygonSet


to_gds(multiplier)

Turn this object into a whole series of GDSII elements.

Parameters

  • multiplier (number) โ€” a number that multiplies all the dimensions written into the GDSII elements.

Returns out โ€” the GDSII binary string that represents this object.

Return type string


area(by_spec=False)

Add up the total area of this polygon set.

Parameters

  • by_spec (bool) โ€” if it's True, the return value is a dictionary shaped like {(layer, datatype): area}.

Returns out โ€” the area of this object.

Return type number, dictionary


fracture(max_points=199, precision=0.001)

Slice these polygons both horizontally and vertically so every resulting piece has at most max_points. Glow-up via chopping, periodt. ๐Ÿ”ฅ

Heads up: this operation happens in place.

Parameters

  • max_points (integer) โ€” the maximal number of points in each resulting polygon (it needs at least 5 for the fracture to actually occur).
  • precision (float) โ€” the desired precision for rounding vertice coordinates.

Returns out โ€” this object.

Return type PolygonSet


fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

Round off all the corners of these polygons (soft girl aesthetic โœจ) and, if necessary, fracture them into polygons with fewer vertices.

Parameters

  • radius (number, array-like) โ€” the radius of the corners. If it's a single number, all corners get filleted by that amount. If it's an array, you're specifying fillet radii on a per-polygon basis (the length has to be equal to the number of polygons in this PolygonSet). Each element in that array can be a number (so all corners filleted by the same amount) or yet another array of numbers, one per polygon vertex. Alternatively, the array can be flattened down to have one radius per PolygonSet vertex.
  • points_per_2pi (integer) โ€” how many vertices get used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number that corresponds to the angle that corner spans with respect to 2 pi.
  • max_points (integer) โ€” the maximal number of points in each resulting polygon (at least 5, otherwise the resulting polygon just isn't fractured).
  • precision (float) โ€” the desired precision for rounding vertice coordinates when fracturing happens.

Returns out โ€” this object.

Return type PolygonSet


translate(dx, dy)

Move this polygon wherever, bestie.

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns out โ€” this object.

Return type PolygonSet


mirror(p1, p2=(0, 0))

Mirror the polygons over a line that passes through points 1 and 2. Reflection era. ๐Ÿชž

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns out โ€” this object.

Return type PolygonSet


๐Ÿ“ Polygon ๐Ÿ“

class gdspy.Polygon(points, layer=0, datatype=0)

Bases: PolygonSet

A polygonal geometric object โ€” the classic, the icon, the blueprint of a shape. ๐Ÿ’–

Parameters

  • points (array-like[N][2]) โ€” the coordinates of the vertices of the polygon.
  • layer (integer) โ€” the GDSII layer number for this element.
  • datatype (integer) โ€” the GDSII datatype for this element (between 0 and 255).

Notes

The last point should not be the same as the first (polygons close themselves automatically).
And again for the people in the back: the original GDSII specification supports only a maximum of 199 vertices per polygon.

Examples

>>> triangle_pts = [(0, 40), (15, 40), (10, 50)]
>>> triangle = gdspy.Polygon(triangle_pts)
>>> myCell.add(triangle)

area(by_spec=False)

Add up the total area of this polygon set.

Parameters

  • by_spec (bool) โ€” if it's True, the return value is a dictionary shaped like {(layer, datatype): area}.

Returns out โ€” the area of this object.

Return type number, dictionary


fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

Round off the corners of these polygons (soft edges only, no sharp lines ๐ŸŽ€) and fracture them into polygons with fewer vertices if that's needed.

Parameters

and the parameters it's taking are:

  • radius (number, array-like) โ€” the corner radius, bestie. If you hand it a single number, every corner gets filleted by that amount. If you hand it an array, you're doing radii on a per-polygon basis (and the length must match the number of polygons in this PolygonSet, no cap). Each element in that array can itself be a number (all corners in that polygon get the same fillet) or another array of numbers, one per polygon vertex. OR โ€” plot twist โ€” you can flatten the whole thing so there's one radius per PolygonSet vertex.
  • points_per_2pi (integer) โ€” how many vertices we use to fake a full circle ๐ŸŒ€. A corner gets the fraction of this number that matches the angle it spans relative to 2 pi.
  • max_points (integer) โ€” max points allowed in each resulting polygon (gotta be at least 5, otherwise the thing just doesn't get fractured).
  • precision (float) โ€” the precision you want for rounding vertex coordinates when fracturing happens.

Returns

  • out โ€” this very object, so you can keep chaining.

Return type

PolygonSet


๐Ÿฐ fracture(max_points=199, precision=0.001)

Slices these polygons both horizontally and vertically until every resulting piece has at most max_points points. Heads up: this happens in place, so it mutates the object you call it on โœ‚๏ธ.

Parameters

  • max_points (integer) โ€” the most points any resulting polygon is allowed to have (at least 5, or no fracturing occurs at all).
  • precision (float) โ€” desired precision for rounding those vertex coordinates.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿ“ฆ get_bounding_box()

Crunches the numbers and gives you the bounding box of the polygons.

Returns

  • out โ€” the bounding box of this polygon shaped like [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty. Yes, None. Empty means empty, no box for you ๐Ÿ™ˆ.

Return type

Numpy array[2, 2] or None

๐Ÿชž mirror(p1, p2=(0, 0))

Mirrors the polygons across a line that runs through points 1 and 2. Reflection arc, love that for you ๐Ÿ’….

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐ŸŽก rotate(angle, center=(0, 0))

Spins this object. Rotation era.

Parameters

  • angle (number) โ€” the angle of rotation, and it's in radians (sorry, not degrees, we're doing math here).
  • center (array-like[2]) โ€” the center point it rotates around.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿซง scale(scalex, scaley=None, center=(0, 0))

Scales this object. Glow-up but geometric.

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it just uses scalex. Lazy but valid.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿ”Œ to_gds(multiplier)

Converts this object into a series of GDSII elements. The big export moment โœจ.

Parameters

  • multiplier (number) โ€” a number that multiplies every dimension written into the GDSII elements.

Returns

  • out โ€” the GDSII binary string representing this object.

Return type

string

๐Ÿšš translate(dx, dy)

Slides this polygon to a new spot, no rotating, no scaling, just movement bestie.

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type

PolygonSet


๐ŸŸฆ Rectangle ๐ŸŸฆ

class gdspy.Rectangle(point1, point2, layer=0, datatype=0)

Bases: PolygonSet

It's a rectangular geometric object. A rectangle. Beloved. Foundational. The moment ๐ŸŽ€.

Parameters

  • point1 (array-like[2]) โ€” coordinates of a corner of the rectangle.
  • point2 (array-like[2]) โ€” coordinates of the corner sitting opposite to point1.
  • layer (integer) โ€” the GDSII layer number for this element.
  • datatype (integer) โ€” the GDSII datatype for this element (has to be between 0 and 255).

Examples

>>> rectangle = gdspy.Rectangle((0, 0), (10, 20))
>>> myCell.add(rectangle)

๐Ÿ“ area(by_spec=False)

Adds up the total area of this polygon set.

Parameters

  • by_spec (bool) โ€” if True, what you get back is a dictionary shaped like {(layer, datatype): area}.

Returns

  • out โ€” the area of this object.

Return type

number, dictionary

๐Ÿฐ fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

Rounds off the corners of these polygons, and fractures them into polygons with fewer vertices if that's needed. Soft corners, soft heart ๐Ÿ’–.

Parameters

  • radius (number, array-like) โ€” the corner radius. If it's a number, all corners get filleted by that amount. If it's an array, you're specifying fillet radii on a per-polygon basis (length must equal the number of polygons in this PolygonSet). Each element of that array can be a number (all corners filleted the same amount) or another array of numbers, one per polygon vertex. Or alternatively, the array can be flattened so there's one radius per PolygonSet vertex.
  • points_per_2pi (integer) โ€” number of vertices used to approximate a full circle. Each corner of the polygon gets the fraction of this number that corresponds to the angle that corner spans relative to 2 pi.
  • max_points (integer) โ€” maximal number of points in each resulting polygon (at least 5, otherwise the resulting polygon doesn't get fractured).
  • precision (float) โ€” desired precision for rounding vertex coordinates in case of fracturing.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿฐ fracture(max_points=199, precision=0.001)

Slices these polygons horizontally and vertically so each resulting piece has at most max_points points. This operation occurs in place.

Parameters

  • max_points (integer) โ€” maximal number of points in each resulting polygon (at least 5 for the fracture to actually occur).
  • precision (float) โ€” desired precision for rounding vertex coordinates.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿ“ฆ get_bounding_box()

Calculates the bounding box of the polygons.

Returns

  • out โ€” bounding box of this polygon in the form [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.

Return type

Numpy array[2, 2] or None

๐Ÿชž mirror(p1, p2=(0, 0))

Mirrors the polygons over a line through points 1 and 2.

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐ŸŽก rotate(angle, center=(0, 0))

Rotates this object.

Parameters

  • angle (number) โ€” the angle of rotation, in radians.
  • center (array-like[2]) โ€” center point for the rotation.

Returns

  • out โ€” this object.

Return type

PolygonSet

๐Ÿซง scale(scalex, scaley=None, center=(0, 0))

Scales this object.

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If None, same as scalex.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns

  • out โ€” this object.

Return type

PolygonSet

to_gds(multiplier)

okay so this one converts the whole object into a lil series of GDSII elements ๐Ÿ’ซ

Parameters

  • multiplier (number) โ€” a number that multiplies all the dimensions written into those GDSII elements.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type: string

translate(dx, dy)

slides the polygon somewhere else on the plane, no drama ๐Ÿ›ผ

Parameters

  • dx (number) โ€” how far to move in the x-direction.
  • dy (number) โ€” how far to move in the y-direction.

Returns

  • out โ€” this object.

Return type: PolygonSet


โœจ Round โœจ

class gdspy.Round(center, radius, inner_radius=0, initial_angle=0, final_angle=0, tolerance=0.01, number_of_points=None, max_points=199, layer=0, datatype=0)

Bases: PolygonSet

bestie it's a circular geometric object ๐ŸŽก like, a circle, an ellipse, a ring, or a lil section of any of those. she does it all.

Parameters

  • center (array-like[2]) โ€” coordinates of the center of the circle/ring.
  • radius (number, array-like[2]) โ€” radius of the circle, or the outer radius if it's a ring. Wanna make an ellipse? Pass an array of 2 numbers instead โ€” those are the radii in the horizontal and vertical directions ๐Ÿ’…
  • inner_radius (number, array-like[2]) โ€” inner radius of the ring. And for an elliptical hole, same trick: an array of 2 numbers = the radii in the horizontal and vertical directions.
  • initial_angle (number) โ€” initial angle of the circular/ring section, in radians.
  • final_angle (number) โ€” final angle of the circular/ring section, in radians.
  • tolerance (float) โ€” approximate curvature resolution. The number of points gets auto-calculated for you ๐Ÿซถ
  • number_of_points (integer or None) โ€” manually set the number of vertices that form the object (that's the polygonal approximation). This overrides tolerance.
  • max_points (integer) โ€” if the number of points in the element is bigger than max_points, it gets fractured into smaller polygons with at most max_points each. And if max_points is zero, no fracture happens at all.
  • layer (integer) โ€” the GDSII layer number for this element.
  • datatype (integer) โ€” the GDSII datatype for this element (it lives between 0 and 255).

Notes

The original GDSII specification supports only a maximum of 199 vertices per polygon. ๐Ÿ˜ญ that's the limit, no cap.

Examples

>>> circle = gdspy.Round((30, 5), 8)
>>> ell_ring = gdspy.Round((50, 5), (8, 7), inner_radius=(5, 4))
>>> pie_slice = gdspy.Round((30, 25), 8, initial_angle=0,
...                             final_angle=-5.0*numpy.pi/6.0)
>>> arc = gdspy.Round((50, 25), 8, inner_radius=5,
...                       initial_angle=-5.0*numpy.pi/6.0,
...                       final_angle=0)

area(by_spec=False)

adds up the total area of this polygon set ๐Ÿ“

Parameters

  • by_spec (bool) โ€” if True, what comes back is a dictionary shaped like {(layer, datatype): area}.

Returns

  • out โ€” area of this object.

Return type: number, dictionary

fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

rounds off the corners of these polygons (so soft, so cute ๐ŸŽ€) and fractures them into polygons with fewer vertices if it has to.

Parameters

  • radius (number, array-like) โ€” radius of the corners. If it's a number: every corner gets filleted by that amount. If it's an array: you're setting fillet radii on a per-polygon basis (and the length has to equal the number of polygons in this PolygonSet). Each element in that array can be a number (all corners filleted the same amount) or another array of numbers, one per polygon vertex. Bonus option: the array can be flattened to have one radius per PolygonSet vertex.
  • points_per_2pi (integer) โ€” number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number that corresponds to the angle that corner covers, relative to 2 pi.
  • max_points (integer) โ€” max number of points in each resulting polygon (at least 5, otherwise the resulting polygon doesn't get fractured).
  • precision (float) โ€” the precision you want for rounding vertex coordinates in case of fracturing.

Returns

  • out โ€” this object.

Return type: PolygonSet

fracture(max_points=199, precision=0.001)

slices these polygons horizontally and vertically so every resulting piece has at most max_points. Heads up though โ€” this operation happens in place, she's not making a copy ๐Ÿ’€

Parameters

  • max_points (integer) โ€” max number of points in each resulting polygon (at least 5 for the fracture to actually occur).
  • precision (float) โ€” desired precision for rounding vertex coordinates.

Returns

  • out โ€” this object.

Return type: PolygonSet

get_bounding_box()

works out the bounding box of the polygons ๐Ÿ“ฆ

Returns

  • out โ€” bounding box of this polygon in the form [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.

Return type: Numpy array[2, 2] or None

mirror(p1, p2=(0, 0))

mirrors the polygons over a line drawn through points 1 and 2 ๐Ÿชž

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns

  • out โ€” this object.

Return type: PolygonSet

rotate(angle, center=(0, 0))

spins this object around ๐Ÿ”„

Parameters

  • angle (number) โ€” the angle of rotation, in radians.
  • center (array-like[2]) โ€” center point for the rotation.

Returns

  • out โ€” this object.

Return type: PolygonSet

scale(scalex, scaley=None, center=(0, 0))

scales this object up or down ๐Ÿฆ‹

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it's the same as scalex.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns

  • out โ€” this object.

Return type: PolygonSet

to_gds(multiplier)

converts this object into a series of GDSII elements โœจ

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII elements.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type: string

translate(dx, dy)

moves the polygon around, no thoughts just vibes ๐Ÿ›ผ

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type: PolygonSet


๐ŸŒธ Text ๐ŸŒธ

class gdspy.Text(text, size, position=(0, 0), horizontal=True, angle=0, layer=0, datatype=0)

Bases: PolygonSet

polygonal text object โ€” every single letter gets built out of a series of polygons ๐Ÿ”ค iconic honestly

Parameters

  • text (string) โ€” the text that gets converted into geometric objects.
  • size (number) โ€” height of the character. The width of a character and the distance between characters are this value multiplied by 5 / 9 and 8 / 9, respectively. And for vertical text, that distance is multiplied by 11 / 9.
  • position (array-like[2]) โ€” text position (that's the lower left corner).
  • horizontal (bool) โ€” if True, the text is written from left to right; if False, from top to bottom.
  • angle (number) โ€” the angle of rotation of the text.
  • layer (integer) โ€” the GDSII layer number for these elements.
  • datatype (integer) โ€” the GDSII datatype for this element (between 0 and 255).

Examples

>>> text = gdspy.Text('Sample text', 20, (-10, -100))
>>> myCell.add(text)

area(by_spec=False)

Calculate the total area of this polygon set.

  • by_spec (bool) โ€” if True, what you get back is a dictionary shaped like {(layer, datatype): area} ๐Ÿ˜Œ

Returns

  • out โ€” the area of this object.

Return type

  • number, dictionary

fillet(radius, points_per_2pi=128, max_points=199, precision=0.001) ๐Ÿ’…

okay bestie, this one rounds the corners of these polygons, and then fractures them into polygons with fewer vertices if it has to. soft edges, main character energy โœจ

Parameters

  • radius (number, array-like) โ€” the radius of the corners. If it's a number: every single corner gets filleted by that amount. If it's an array: you're specifying fillet radii on a per-polygon basis (and the length must be equal to the number of polygons in this PolygonSet, no cap ๐Ÿซถ). Each element in the array can be a number (all corners in that polygon filleted by the same amount) or another array of numbers, one per polygon vertex. Alternatively, you can flatten the array out so there's one radius per PolygonSet vertex.
  • points_per_2pi (integer) โ€” number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon is whatever fraction of this number corresponds to the angle that corner spans, with respect to 2 pi.
  • max_points (integer) โ€” the max number of points in each resulting polygon (at least 5, otherwise the resulting polygon is not fractured).
  • precision (float) โ€” desired precision for rounding vertice coordinates in case of fracturing.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

fracture(max_points=199, precision=0.001) ๐Ÿ”ช

Slices these polygons in the horizontal and vertical directions so that every resulting piece has at most max_points. Heads up: this operation happens in place, no take-backs.

Parameters

  • max_points (integer) โ€” the max number of points in each resulting polygon (at least 5 for the fracture to actually occur).
  • precision (float) โ€” desired precision for rounding vertice coordinates.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

get_bounding_box() ๐Ÿ“ฆ

Calculates the bounding box of the polygons. Just the box, nothing fancy.

Returns

  • out โ€” the bounding box of this polygon, shaped like [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.

Return type

  • Numpy array[2, 2] or None

mirror(p1, p2=(0, 0)) ๐Ÿชž

Mirrors the polygons over a line through points 1 and 2. yes queen, reflection.

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

rotate(angle, center=(0, 0)) ๐ŸŒ€

Rotate this object. spin it, bestie.

Parameters

  • angle (number) โ€” the angle of rotation, in radians.
  • center (array-like[2]) โ€” the center point for the rotation.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

scale(scalex, scaley=None, center=(0, 0)) ๐Ÿ“

Scale this object. bigger. smaller. whatever the vibe is.

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it's the same as scalex.
  • center (array-like[2]) โ€” the center point for the scaling operation.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

to_gds(multiplier) ๐ŸŒˆ

Converts this object into a series of GDSII elements.

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII elements.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type

  • string

translate(dx, dy) ๐Ÿšš

Translates this polygon. moving day!

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type

  • PolygonSet

๐Ÿฆ‹ Path ๐Ÿฆ‹

class gdspy.Path(width, initial_point=(0, 0), number_of_paths=1, distance=0)

Bases: PolygonSet

A series of geometric objects that form a path, or a whole little collection of parallel paths. it's giving runway ๐Ÿ’…

Parameters

  • width (number) โ€” the width of each path.
  • initial_point (array-like[2]) โ€” the starting position of the path.
  • number_of_paths (positive integer) โ€” how many parallel paths to create at the same time.
  • distance (number) โ€” the distance between the centers of adjacent paths.

Variables

  • x (number) โ€” current position of the path in the x direction.
  • y (number) โ€” current position of the path in the y direction.
  • w (number) โ€” half-width of each path.
  • n (integer) โ€” number of parallel paths.
  • direction ('+x', '-x', '+y', '-y' or number) โ€” the direction, or the angle in radians, that the path points to.
  • distance (number) โ€” distance between the centers of adjacent paths.
  • length (number) โ€” length of the central path axis. If only one path is created, then this is the real length of the path, periodt.

translate(dx, dy) ๐Ÿšš

Translate this object.

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type

  • Path

rotate(angle, center=(0, 0)) ๐ŸŒ€

Rotate this object.

Parameters

  • angle (number) โ€” the angle of rotation, in radians.
  • center (array-like[2]) โ€” the center point for the rotation.

Returns

  • out โ€” this object.

Return type

  • Path

scale(scalex, scaley=None, center=(0, 0)) ๐Ÿ“

Scale this object.

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it's the same as scalex.
  • center (array-like[2]) โ€” the center point for the scaling operation.

Returns

  • out โ€” this object.

Return type

  • Path

Notes

๐Ÿšจ The direction of the path is not modified by this method, and its width is scaled only by scalex. like, only scalex. not both. iykyk.

mirror(p1, p2=(0, 0)) ๐Ÿชž

Mirror the polygons over a line through points 1 and 2.

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns

  • out โ€” this object.

Return type

  • Path

segment(length, direction=None, final_width=None, final_distance=None, axis_offset=0, layer=0, datatype=0) ๐Ÿ›ฃ๏ธ

Adds a straight section to the path. just a lil stretch of road, bestie.

Parameters

  • length (number) โ€” length of the section to add.
  • direction ('+x', '-x', '+y', '-y' or number) โ€” the direction, or the angle in radians, of rotation of the segment.
  • final_width (number) โ€” if you set this, the paths of this segment will have their widths linearly changed from their current value to this one.
  • final_distance (number) โ€” if you set this, the distance between paths is linearly change from its current value to this one along this segment.
  • axis_offset (number) โ€” if you set this, the paths will be offset from their direction by this amount.
  • layer (integer, list) โ€” the GDSII layer numbers for the elements of each path. If the number of layers in the list is less than the number of paths, the list just gets repeated. ๐Ÿ”
  • datatype (integer, list) โ€” the GDSII datatype for each path's elements, and bb it's gotta be between 0 and 255. If your list has fewer datatypes than you have paths, don't stress โ€” the list just repeats itself. ๐Ÿ’ซ

Returns out โ€” this same object, bestie.

Return type Path


โœจ arc(radius, initial_angle, final_angle, tolerance=0.01, number_of_points=None, max_points=199, final_width=None, final_distance=None, layer=0, datatype=0)

Okay so this one adds a curved section to the path. It's giving swoopy. It's giving elegant. ๐ŸŒ€

Parameters

  • radius (number) โ€” the central radius of the section.
  • initial_angle (number) โ€” where the curve starts, angle-wise (in radians).
  • final_angle (number) โ€” where the curve ends, angle-wise (in radians).
  • tolerance (float) โ€” approximate curvature resolution. The number of points gets calculated for you automatically. ๐Ÿ’…
  • number_of_points (integer or None) โ€” manually set how many vertices form the object (polygonal approximation). This overrides tolerance.
  • max_points (integer) โ€” if the number of points in the element is greater than max_points, it gets fractured into smaller polygons with at most max_points each. And if max_points is zero? No fracture happens at all, periodt.
  • final_width (number) โ€” if you set this, the paths of this segment get their widths linearly changed from whatever they are now to this value.
  • final_distance (number) โ€” if you set this, the distance between paths linearly changes from its current value to this one along the segment.
  • layer (integer, list) โ€” the GDSII layer numbers for each path's elements. If your list has fewer layers than you have paths, the list repeats. ๐Ÿ”
  • datatype (integer, list) โ€” the GDSII datatype for each path's elements, between 0 and 255. Fewer datatypes than paths? The list repeats.

Returns out โ€” this object.

Return type Path

๐ŸŒธ Notes

The original GDSII specification supports only a maximum of 199 vertices per polygon. Don't test it, bestie. ๐Ÿ˜ญ

โœจ turn(radius, angle, tolerance=0.01, number_of_points=None, max_points=199, final_width=None, final_distance=None, layer=0, datatype=0)

Adds a curved section to the path โ€” yes it says the same thing as arc, no I'm not gonna lie to you. This is the turn-y one. ๐ŸŽ€

Parameters

  • radius (number) โ€” the central radius of the section.
  • angle ('r', 'l', 'rr', 'll' or number) โ€” the angle (in radians) of rotation of the path. The values 'r' and 'l' are 90-degree turns cw and ccw respectively; 'rr' and 'll' are the analogous 180-degree turns. So yes, 'rr' is literally the "make a U-turn" icon. ๐Ÿ”„
  • tolerance (float) โ€” approximate curvature resolution. Number of points is auto-calculated.
  • number_of_points (integer or None) โ€” manually define the number of vertices that form the object (polygonal approximation). Overrides tolerance.
  • max_points (integer) โ€” if the number of points in the element is greater than max_points, it gets fractured into smaller polygons with at most max_points each. If max_points is zero, no fracture occurs.
  • final_width (number) โ€” if set, the paths of this segment have their widths linearly changed from their current value to this one.
  • final_distance (number) โ€” if set, the distance between paths linearly changes from its current value to this one along this segment.
  • layer (integer, list) โ€” the GDSII layer numbers for each path's elements. Fewer layers than paths? The list repeats.
  • datatype (integer, list) โ€” the GDSII datatype for each path's elements, between 0 and 255. Fewer datatypes than paths? The list repeats.

Returns out โ€” this object.

Return type Path

๐ŸŒธ Notes

The original GDSII specification supports only a maximum of 199 vertices per polygon. Same warning, same energy. ๐Ÿ’€

โœจ parametric(curve_function, curve_derivative=None, tolerance=0.01, number_of_evaluations=5, max_points=199, final_width=None, final_distance=None, relative=True, layer=0, datatype=0)

Adds a parametric curve to the path. This is the "I brought my own math" option and honestly? Iconic behavior. ๐Ÿงš

curve_function gets evaluated uniformly in the interval [0, 1] at least number_of_points times. More points get added to the curve at the midpoint between evaluations if that point presents error larger than tolerance.

Parameters

  • curve_function (callable) โ€” the function that defines the curve. Gotta be a function of one argument (that varies from 0 to 1) that returns a 2-element array with the coordinates of the curve.
  • curve_derivative (callable) โ€” if set, it should be the derivative of the curve function. Must be a function of one argument (varying from 0 to 1) that returns a 2-element array. If None, the derivative gets calculated numerically.
  • tolerance (number) โ€” acceptable tolerance for approximating the curve function with a finite number of evaluations.
  • number_of_evaluations (integer) โ€” the initial number of points where the curve function gets evaluated. More evaluations happen according to tolerance.
  • max_points (integer) โ€” elements get fractured until each polygon has at most max_points. If max_points is less than 4, no fracture occurs.
  • final_width (number or function) โ€” if it's a number, the paths of this segment have their widths linearly changed from their current value to this one. If it's a function, it must take one argument (varying from 0 to 1) and return the width of the path.
  • final_distance (number or function) โ€” if it's a number, the distance between paths linearly changes from its current value to this one. If it's a function, it must take one argument (varying from 0 to 1) and return the width of the path.
  • relative (bool) โ€” if True, the return values of curve_function are used as offsets from the current path position, i.e., to ensure a continuous path, curve_function(0) must be (0, 0). Otherwise they're used as absolute coordinates. Slay. ๐Ÿ“
  • layer (integer, list) โ€” the GDSII layer numbers for each path's elements. Fewer layers than paths? The list repeats.
  • datatype (integer, list) โ€” the GDSII datatype for each path's elements, between 0 and 255. Fewer datatypes than paths? The list repeats.

Returns out โ€” this object.

Return type Path

๐ŸŒˆ Notes

The norm of the vector returned by curve_derivative is not important. Only the direction is used. Direction over magnitude, as always. ๐Ÿ’…
The original GDSII specification supports only a maximum of 199 vertices per polygon.

๐ŸŒŸ Examples

>>> def my_parametric_curve(t):
...         return (2**t, t**2)
>>> def my_parametric_curve_derivative(t):
...         return (0.69315 * 2**t, 2 * t)
>>> my_path.parametric(my_parametric_curve,
...                    my_parametric_curve_derivative)

โœจ bezier(points, tolerance=0.01, number_of_evaluations=5, max_points=199, final_width=None, final_distance=None, relative=True, layer=0, datatype=0)

Adds a Bezier curve to the path. Yes, the smooth swoopy pen-tool one. ๐Ÿฆ‹

A Bezier curve gets added to the path starting from its current position and finishing at the last point in the points array.

Parameters

  • points (array-like[N][2]) โ€” control points defining the Bezier curve.
  • *tolerance</strong> *(number) โ€” how much sloppiness is allowed when we approximate the curve function with a finite number of evaluations. Bestie, this is the "close enough" dial. ๐Ÿ’…
  • number_of_evaluations (integer) โ€” the starting number of points where the curve function gets evaluated. Depending on tolerance, more evaluations will be performed. It's giving "start here, we'll add more if we have to." โœจ
  • max_points (integer) โ€” elements get fractured until each polygon has at most max_points. If max_points is zero, no fracture happens at all, periodt. ๐Ÿ’€
  • final_width (number or function) โ€” set it to a number and the paths of this segment have their widths linearly changed from their current value to this one. Set it to a function and it must be a function of one argument (that varies from 0 to 1) and it returns the width of the path.
  • final_distance (number or function) โ€” set it to a number and the distance between paths is linearly changed from its current value to this one. Set it to a function and it must be a function of one argument (that varies from 0 to 1) and it returns the width of the path.
  • relative (bool) โ€” if True, all coordinates in the points array are used as offsets from the current path position, i.e., if the path is at (1, -2) and the last point in the array is (10, 25), the constructed Bezier will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates. no cap ๐Ÿซถ
  • layer (integer, list) โ€” the GDSII layer numbers for the elements of each path. If the number of layers in the list is less than the number of paths, the list is repeated. rinse and repeat, iconic behavior honestly. ๐ŸŒธ
  • datatype (integer, list) โ€” the GDSII datatype for the elements of each path (between 0 and 255). If the number of datatypes in the list is less than the number of paths, the list is repeated. ๐ŸŽ€

Returns

out โ€” this object. It's main character energy: it returns itself. ๐Ÿ‘‘

Return type

Path

Notes

The original GDSII specification supports only a maximum of 199 vertices per polygon. so like, know that before you go feral. ๐Ÿ˜ญ

smooth(points, angles=None, curl_start=1, curl_end=1, t_in=1, t_out=1, cycle=False, tolerance=0.01, number_of_evaluations=5, max_points=199, final_widths=None, final_distances=None, relative=True, layer=0, datatype=0)

smooth(points, angles=None, curl_start=1, curl_end=1, t_in=1, t_out=1, cycle=False, tolerance=0.01, number_of_evaluations=5, max_points=199, final_widths=None, final_distances=None, relative=True, layer=0, datatype=0)

Add a smooth interpolating curve through the given points. ๐Ÿ’–

Uses the Hobby algorithm [1]_ to calculate a smooth interpolating curve made of cubic Bezier segments between each pair of points. literally art. ๐ŸŽจ

Parameters

  • points (array-like[N][2]) โ€” vertices in the interpolating curve. ๐ŸŒˆ
  • angles (array-like[N + 1] or None) โ€” tangent angles at each point (in radians). Any angles defined as None are automatically calculated. the lazy-girl method, we love her. โœจ
  • curl_start (number) โ€” ratio between the mock curvatures at the first point and at its neighbor. A value of 1 renders the first segment a good approximation for a circular arc. A value of 0 will better approximate a straight segment. It has no effect for closed curves or when an angle is defined for the first point. ๐ŸŒ€
  • curl_end (number) โ€” ratio between the mock curvatures at the last point and at its neighbor. It has no effect for closed curves or when an angle is defined for the first point. ๐ŸŒ€
  • t_in (number or array-like[N + 1]) โ€” tension parameter when arriving at each point. One value per point or a single value used for all points.
  • t_out (number or array-like[N + 1]) โ€” tension parameter when leaving each point. One value per point or a single value used for all points.
  • cycle (bool) โ€” if True, calculates control points for a closed curve, with an additional segment connecting the first and last points. ๐Ÿ”
  • tolerance (number) โ€” acceptable tolerance for the approximation of the curve function by a finite number of evaluations.
  • number_of_evaluations (integer) โ€” initial number of points where the curve function will be evaluated. According to tolerance, more evaluations will be performed.
  • max_points (integer) โ€” elements will be fractured until each polygon has at most max_points. If max_points is zero no fracture will occur. ๐Ÿซถ
  • final_widths (array-like[M]) โ€” each element corresponds to the final width of a segment in the whole curve. If an element is a number, the paths of this segment will have their widths linearly changed to this value. If a function, it must be a function of one argument (that varies from 0 to 1) and returns the width of the path. The length of the array must be equal to the number of segments in the curve, i.e., M = N - 1 for an open curve and M = N for a closed one. count them bestie, the math is mathing. ๐Ÿ’…
  • final_distances (array-like[M]) โ€” each element corresponds to the final distance between paths of a segment in the whole curve. If an element is a number, the distance between paths is linearly changed to this value. If a function, it must be a function of one argument (that varies from 0 to 1) and returns the width of the path. The length of the array must be equal to the number of segments in the curve, i.e., M = N - 1 for an open curve and M = N for a closed one. ๐Ÿ“
  • relative (bool) โ€” if True, all coordinates in the points array are used as offsets from the current path position, i.e., if the path is at (1, -2) and the last point in the array is (10, 25), the constructed curve will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates. ๐ŸŒŸ
  • layer (integer, list) โ€” the GDSII layer numbers for the elements of each path. If the number of layers in the list is less than the number of paths, the list is repeated.
  • datatype (integer, list) โ€” the GDSII datatype for the elements of each path (between 0 and 255). If the number of datatypes in the list is less than the number of paths, the list is repeated.

Returns

out โ€” this object. yes queen, return yourself. ๐Ÿ‘‘

Return type

Path

Notes

The original GDSII specification supports only a maximum of 199 vertices per polygon. lowkey important, don't skip it. ๐Ÿ˜ญ

References

[1] Hobby, J.D. Discrete Comput. Geom. (1986) 1: 123. DOI: 10.1007/BF02187690 <https://doi.org/10.1007/BF02187690>


area(by_spec=False)

area(by_spec=False)

Calculate the total area of this polygon set. the way this one justโ€ฆ measures everything. obsessed. ๐Ÿ“๐Ÿ’–

Parameters

by_spec (bool) โ€” if True, the return value is a dictionary with {(layer, datatype): area}. iykyk, you get the receipts per layer. ๐Ÿงพ

Returns

out โ€” area of this object.

Return type

number, dictionary


fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

fillet(radius, points_per_2pi=128, max_points=199, precision=0.001)

Round the corners of these polygons and fractures them into polygons with less vertices if necessary. it's giving โœจ soft launch โœจ for your sharp angles. ๐ŸŽ€

Parameters

  • radius (number, array-like) โ€” radius of the corners. If number: all corners filleted by that amount. If array: specify fillet radii on a per-polygon basis (length must be equal to the number of polygons in this PolygonSet). Each element in the array can be a number (all corners filleted by the same amount) or another array of numbers, one per polygon vertex. Alternatively, the array can be flattened to have one radius per PolygonSet vertex. so many options, and all of them valid. ๐Ÿ’…
  • points_per_2pi (integer) โ€” number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number corresponding to the angle encompassed by that corner with respect to 2 pi. ๐ŸŒธ
  • max_points (integer) โ€” maximal number of points in each resulting polygon (at least 5, otherwise the resulting polygon is not fractured). โš ๏ธ
fr fr, that max_points floor of 5 is not a suggestion โ€” go below it and the resulting polygon just does not get fractured.

  • precision (float) โ€” when fracturing happens, this is the precision you want for rounding those vertice coords โœจ

Returns โ€” out โ€” literally just this same object, bestie. It hands itself back to you.

Return type โ€” PolygonSet


fracture(max_points=199, precision=0.001) โ€” slices these polygons up in the horizontal and vertical directions so every resulting piece has at most max_points. Heads up: this happens in place, so it's not giving you a fresh new object, it's editing the one you already have ๐Ÿ‘‘.

Parameters

  • max_points (integer) โ€” the max number of points any one resulting polygon can have (and it's gotta be at least 5 for the fracture to actually happen, no cap).
  • precision (float) โ€” the precision you want for rounding vertice coordinates ๐ŸŒธ

Returns โ€” out โ€” this object. Again. Itself. Iconic behaviour honestly.

Return type โ€” PolygonSet


get_bounding_box() โ€” calculates the bounding box of the polygons, aka the smallest box that contains the whole vibe ๐Ÿ’–

Returns โ€” out โ€” bounding box of this polygon, formatted like [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty (rip ๐Ÿซฅ).

Return type โ€” Numpy array[2, 2] or None


to_gds(multiplier) โ€” converts this object into a series of GDSII elements ๐ŸŽ€

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written into the GDSII elements.

Returns โ€” out โ€” the GDSII binary string that represents this object.

Return type โ€” string


๐Ÿ’œ PolyPath ๐Ÿ’œ

class gdspy.PolyPath(points, width, number_of_paths=1, distance=0,
corners='miter', ends='flush', max_points=199, layer=0, datatype=0)

Bases: PolygonSet

Okay so this is a series of geometric objects that form a polygonal path, or a whole collection of parallel polygonal paths ๐Ÿ›ค๏ธ

โš ๏ธ Deprecated since version 1.4: PolyPath is deprecated in favor of FlexPath and will be removed in a future version of Gdspy. So likeโ€ฆ it still works, but it's lowkey on its way out. Use FlexPath, bestie ๐Ÿ’…

Parameters

  • points (array-like[N][2]) โ€” the points running along the center of the path.
  • width (number or array-like[N]) โ€” width of the path. If you hand it an array instead, that's the width at each endpoint.
  • number_of_paths (positive integer) โ€” how many parallel paths you wanna create at the same time.
  • distance (number or array-like[N]) โ€” distance between the centers of adjacent paths. Array? Then it's the distance at each endpoint.
  • corners ('miter' or 'bevel') โ€” the type of joins.
  • ends ('flush', 'round', 'extended') โ€” the type of end caps for the paths.
  • max_points (integer) โ€” the paths get fractured into polygons with at most max_points points (and it must be at least 4). If max_points is zero then no fracture happens at all, periodt.
  • layer (integer, list) โ€” the GDSII layer numbers for the elements of each path. If the number of layers in the list is less than the number of paths, the list just gets repeated.
  • datatype (integer, list) โ€” the GDSII datatype for the elements of each path (between 0 and 255). Same deal: if there are fewer datatypes in the list than paths, the list repeats.

Notes

๐Ÿ™ˆ The bevel join will give strange results if the number of paths is greater than 1. Like, it's giving chaos. Just be aware.

area(by_spec=False) โ€” calculates the total area of this polygon set ๐Ÿ“

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary shaped like {(layer, datatype): area}.

Returns โ€” out โ€” area of this object.

Return type โ€” number, dictionary


fillet(radius, points_per_2pi=128, max_points=199, precision=0.001) โ€” rounds off the corners of these polygons, and fractures them into polygons with fewer vertices if it has to โœจ

Parameters

  • radius (number, array-like) โ€” radius of the corners. If it's a number: every corner gets filleted by that amount. If it's an array: you specify fillet radii on a per-polygon basis (the length has to equal the number of polygons in this PolygonSet). Each element of that array can be a number (all corners in that polygon filleted by the same amount) or another array of numbers, one per polygon vertex. Alternatively the whole array can be flattened so you have one radius per PolygonSet vertex. Pick your fighter ๐Ÿ’…
  • points_per_2pi (integer) โ€” the number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number corresponding to the angle that corner covers with respect to 2 pi.
  • max_points (integer) โ€” max number of points in each resulting polygon (at least 5, otherwise the resulting polygon doesn't get fractured).
  • precision (float) โ€” the precision you want for rounding vertice coordinates in case of fracturing.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


fracture(max_points=199, precision=0.001) โ€” slices these polygons in the horizontal and vertical directions so each resulting piece has at most max_points. This one also happens in place ๐Ÿ”ฅ

Parameters

  • max_points (integer) โ€” max number of points in each resulting polygon (at least 5 for the fracture to actually occur).
  • precision (float) โ€” desired precision for rounding vertice coordinates.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


get_bounding_box() โ€” calculates the bounding box of the polygons ๐ŸŒˆ

Returns โ€” out โ€” bounding box of this polygon in the form [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.

Return type โ€” Numpy array[2, 2] or None


mirror(p1, p2=(0, 0)) โ€” mirrors the polygons over a line that runs through points 1 and 2 ๐Ÿชž

Parameters

  • p1 (array-like[2]) โ€” first point defining the reflection line.
  • p2 (array-like[2]) โ€” second point defining the reflection line.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


rotate(angle, center=(0, 0)) โ€” rotates this object ๐ŸŒ€

Parameters

  • angle (number) โ€” the angle of rotation (in radians, bestie, radians not degrees).
  • center (array-like[2]) โ€” center point for the rotation.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


scale(scalex, scaley=None, center=(0, 0)) โ€” scales this object ๐Ÿ“

Parameters

  • scalex (number) โ€” scaling factor along the first axis.
  • scaley (number or None) โ€” scaling factor along the second axis. If it's None, it's the same as scalex.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


to_gds(multiplier) โ€” converts this object into a series of GDSII elements ๐ŸŽ€

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII elements.

Returns โ€” out โ€” the GDSII binary string that represents this object.

Return type โ€” string


translate(dx, dy) โ€” translates this polygon ๐Ÿฆ‹

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns โ€” out โ€” this object.

Return type โ€” PolygonSet


๐Ÿ’– L1Path ๐Ÿ’–

class gdspy.L1Path(initial_point, direction, width, length, turn,
number_of_paths=1, distance=0, max_points=199, layer=0, datatype=0)

Bases: PolygonSet

A series of geometric objects that form a path โ€” or a whole collection of parallel paths โ€” with Manhattan geometry ๐Ÿ™๏ธ (you know, all right angles, only horizontal and vertical moves, no diagonal nonsense).

โš ๏ธ Deprecated since version 1.4: L1Path is deprecated in favor of FlexPath and will be removed in a future version of Gdspy. Same energy as PolyPath, bestie โ€” it's still here for now but it's leaving ๐Ÿ˜ญ

Parameters

  • initial_point (array-like[2]) โ€” starting position of the path.
  • direction ('+x', '+y', '-x', '-y') โ€” starting direction of the path.
  • width (number) โ€” the initial width of each path.
  • length (array-like) โ€” lengths of each section to add.
  • turn (array-like) โ€” direction to turn before each section. The sign indicates the turn direction (ccw is positive), and the modulus is a multiplicative factor for the path width after each turn. Must have 1 element less then length.
  • number_of_paths (positive integer) -- how many parallel paths you're birthing at the exact same time. squad goals fr ๐Ÿซถ
  • distance (number) -- the gap between the centers of paths that are next to each other.
  • max_points (integer) -- the paths get fractured into polygons with at most max_points points each, and max_points has to be at least 6. If max_points is zero, no fracture happens at all โ€” nothing, nada. ๐Ÿ’€
  • layer (integer, list) -- the GDSII layer numbers for the elements of each path. If your list has fewer layers than you have paths, the list just gets repeated. recycle era โ™ป๏ธ
  • datatype (integer, list) -- the GDSII datatype for the elements of each path, and it lives between 0 and 255. If there are fewer datatypes in the list than paths, the list repeats. same energy.

Variables โœจ

  • x (number) -- where the path ends up in the x direction.
  • y (number) -- where the path ends up in the y direction.
  • direction ('+x', '-x', '+y', '-y' or number) -- the direction or angle (in radians) that the path points toward. okay so the numerical angle only comes back to you after a rotation of the object. iykyk ๐ŸŒธ

Examples ๐Ÿ’–

>>> length = [10, 30, 15, 15, 15, 15, 10]
>>> turn = [1, -1, -1, 3, -1, 1]
>>> l1path = gdspy.L1Path((0, 0), '+x', 2, length, turn)
>>> myCell.add(l1path)

rotate(angle, center=(0, 0)) ๐ŸŒ€

Rotate this object. spin it bestie.

Parameters

  • angle (number) -- the angle of rotation, in radians.
  • center (array-like[2]) -- the center point you're rotating around.

Returns

out -- this object.

Return type

L1Path


area(by_spec=False) ๐Ÿ“

Calculate the total area of this polygon set. get you a girl who can do both (geometry AND math).

Parameters

by_spec (bool) -- if True, the return value is a dictionary shaped like {(layer, datatype): area}.

Returns

out -- the area of this object.

Return type

number, dictionary


fillet(radius, points_per_2pi=128, max_points=199, precision=0.001) ๐ŸŽ€

Round off the corners of these polygons and fracture them into polygons with fewer vertices if it's necessary. soft launch but for corners.

Parameters

  • radius (number, array-like) -- the radius of the corners. If it's a number: every corner gets filleted by that amount. If it's an array: you're specifying fillet radii on a per-polygon basis (length must equal the number of polygons in this PolygonSet). Each element in the array can be a number (all corners in that polygon filleted by the same amount) or another array of numbers, one per polygon vertex. Alternatively, the array can be flattened so there's one radius per PolygonSet vertex.
  • points_per_2pi (integer) -- the number of vertices used to approximate a full circle. The number of vertices in each corner of the polygon will be the fraction of this number that matches the angle that corner covers with respect to 2 pi.
  • max_points (integer) -- the maximum number of points in each resulting polygon (at least 5, otherwise the resulting polygon does not get fractured).
  • precision (float) -- the precision you want when rounding vertex coordinates in case of fracturing.

Returns

out -- this object.

Return type

PolygonSet


fracture(max_points=199, precision=0.001) โœ‚๏ธ

Slice these polygons in the horizontal and vertical directions so every resulting piece has at most max_points points. heads up: this operation happens in place. no copy, no take-backs. ๐Ÿ˜ญ

Parameters

  • max_points (integer) -- the maximum number of points in each resulting polygon (at least 5 for the fracture to actually occur).
  • precision (float) -- the precision you want when rounding vertex coordinates.

Returns

out -- this object.

Return type

PolygonSet


get_bounding_box() ๐Ÿ“ฆ

Calculate the bounding box of the polygons.

Returns

out -- the bounding box of this polygon, shaped like [[x_min, y_min], [x_max, y_max]], or None if the polygon is empty.

Return type

Numpy array[2, 2] or None


mirror(p1, p2=(0, 0)) ๐Ÿชž

Mirror the polygons over a line that goes through points 1 and 2.

Parameters

  • p1 (array-like[2]) -- first point defining the reflection line
  • p2 (array-like[2]) -- second point defining the reflection line

Returns

out -- this object.

Return type

PolygonSet


scale(scalex, scaley=None, center=(0, 0)) ๐Ÿ“

Scale this object. glow up, size edition.

Parameters

  • scalex (number) -- the scaling factor along the first axis.
  • scaley (number or None) -- the scaling factor along the second axis. If it's None, it's the same as scalex.
  • center (array-like[2]) -- the center point for the scaling operation.

Returns

out -- this object.

Return type

PolygonSet


to_gds(multiplier) ๐Ÿ”ฅ

Convert this object into a whole series of GDSII elements.

Parameters

multiplier (number) -- a number that multiplies all the dimensions written into the GDSII elements.

Returns

out -- the GDSII binary string that represents this object.

Return type

string


translate(dx, dy) ๐Ÿšš

Translate this polygon. slide it, move it, relocate the whole vibe.

Parameters

  • dx (number) -- the distance to move in the x-direction.
  • dy (number) -- the distance to move in the y-direction.

Returns

out -- this object.

Return type

PolygonSet


๐ŸŒˆ FlexPath ๐ŸŒˆ

class gdspy.FlexPath(points, width, offset=0, corners='natural', ends='flush', bend_radius=None, tolerance=0.01, precision=0.001, max_points=199, gdsii_path=False, width_transform=True, layer=0, datatype=0)

Bases: object

Path object, but make it flexible. ๐Ÿ’…

This class keeps all the info about the constructive parameters of the path and only calculates its boundaries when you actually ask for them. girl is low maintenance until you need her.

It can be stored as a proper path element in the GDSII format, unlike Path. but there's a catch bestie: in that case, the width has to be constant along the whole path. no exceptions. ๐Ÿ‘‘

Parameters

  • points (array-like[N][2]) -- the points along the center of the path.
  • width (number, list) -- the width of each parallel path being created. The number of parallel paths being created is defined by the length of this list.
  • offset (number, list) -- the offsets of each parallel path from the center. If width is not a list, the length of this list is what determines how many parallel paths get created. Otherwise, offset must be a list with the same length as width, or a number, which is used as the distance between adjacent paths.
  • corners ('natural', 'miter', 'bevel', 'round', 'smooth', 'circular bend', callable, list) -- the type of joins. A callable has to receive 6 arguments (vertex and direction vector from both segments being joined, plus the center and width of the path) and return a list of vertices that make the join. A list can be used to define the join for each parallel path.
  • ends ('flush', 'extended', 'round', 'smooth', 2-tuple, callable, list) -- the type of end caps for the paths. A 2-element tuple represents the start and end extensions to the paths. A callable has to receive 4 arguments (vertex and direction vectors from both sides of the path) and return a list of vertices that make the end cap. A list can be used to define the end type for each parallel path.
  • bend_radius (number, list) -- bend radii for each path when corners is 'circular bend'. It does absolutely nothing for other corner types. zero effect, periodt.
  • tolerance (number) -- the tolerance used to draw the paths and calculate joins.
  • precision (number) -- the precision for rounding the coordinates of vertices when fracturing the final polygonal boundary.
  • max_points (integer) -- if the number of points in the polygonal path boundary is greater than max_points, it will be fractured into smaller polygons with at most max_points each. If max_points is zero, no fracture will occur.
  • gdsii_path (bool) -- if True, treat this object as a GDSII path element. Otherwise, it gets converted into polygonal boundaries whenever that's required.
  • width_transform (bool) โ€” if gdsii_path is True, this flag says whether the width of the path should transform when you scale this object. It does absolutely nothing when gdsii_path is False. Not a single thing. ๐Ÿ’…
  • layer (integer, list) โ€” the GDSII layer numbers for the elements of each path. And if your list has fewer layers in it than you have paths? Bestie, the list just gets repeated. ๐Ÿ”
  • datatype (integer, list) โ€” the GDSII datatype for the elements of each path, and it has to be between 0 and 255. Same rule as above: if the list runs short compared to the number of paths, it repeats itself. โœจ

๐Ÿ’œ Notes

The value of tolerance should not be smaller than precision, okay? Because if it is, you're just out here doing wasted computational effort calculating paths. We do not do wasted effort in this house. ๐Ÿ˜ญ

๐ŸŒธ get_polygons(by_spec=False)

Calculate the polygonal boundaries described by this path. Obsessed with a good boundary.

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the polygons of each individual pair (layer, datatype).

Returns

  • out โ€” list containing the coordinates of the vertices of each polygon, or dictionary with the list of polygons (if by_spec is True).

Return type: list of array-like[N][2] or dictionary

๐Ÿฆ‹ to_polygonset()

Create a PolygonSet representation of this object.

Heads up though โ€” the resulting object will be fractured according to the parameter max_points that you used when instantiating this object. It's giving glow-up but with rules. ๐ŸŽ€

Returns

  • out โ€” a PolygonSet that contains all boundaries for this path. If the path is empty, returns None.

Return type: PolygonSet or None

โญ to_gds(multiplier)

Convert this object to a series of GDSII elements.

Now, if FlexPath.gdsii_path is True, GDSII path elements are created instead of boundaries. Those paths do not support variable widths, but their memory footprint is smaller than full polygonal boundaries. The classic tradeoff, iykyk. ๐Ÿ™ˆ

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII elements.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type: string

๐Ÿ’– area(by_spec=False)

Calculate the total area of this object.

Lowkey important caveat: this function creates a PolygonSet from this object and then calculates its area, which means it is computationally expensive. She's thorough, not cheap. ๐Ÿ’€

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with {(layer, datatype): area}.

Returns

  • out โ€” area of this object.

Return type: number, dictionary

๐ŸŒˆ translate(dx, dy)

Translate this path. Moving her across the floor, main character energy.

Parameters

  • dx (number) โ€” distance to move in the x-direction
  • dy (number) โ€” distance to move in the y-direction

Returns

  • out โ€” this object.

Return type: FlexPath

๐Ÿงš rotate(angle, center=(0, 0))

Rotate this path. Spin her. ๐ŸŒ€

Parameters

  • angle (number) โ€” the angle of rotation (in radians).
  • center (array-like[2]) โ€” center point for the rotation.

Returns

  • out โ€” this object.

Return type: FlexPath

๐Ÿ“ scale(scale, center=(0, 0))

Scale this path. Bigger. Smaller. Whatever the assignment calls for.

Parameters

  • scale (number) โ€” scaling factor.
  • center (array-like[2]) โ€” center point for the scaling operation.

Returns

  • out โ€” this object.

Return type: FlexPath

๐Ÿ‘‘ transform(translation, rotation, scale, x_reflection, array_trans=None)

Apply a transform to this path.

Parameters

  • translation (Numpy array[2]) โ€” translation vector.
  • rotation (number) โ€” rotation angle.
  • scale (number) โ€” scaling factor.
  • x_reflection (bool) โ€” reflection around the first axis.
  • array_trans (Numpy array[2]) โ€” translation vector before rotation and reflection.

Returns

  • out โ€” this object.

Return type: FlexPath

Notes

Applies the transformations in the same order as a CellReference or a CellArray. And if width_transform is False, the widths are not scaled. Keep that in mind, bestie. ๐Ÿซถ

๐Ÿ’ซ segment(end_point, width=None, offset=None, relative=False)

Add a straight section to the path. A little straight-line moment for you.

Parameters

  • end_point (array-like[2]) โ€” end position of the straight segment.
  • width (number, list) โ€” if a number, all parallel paths are linearly tapered to this width along the segment. A list can be used where each element defines the width for one of the parallel paths in this object. This argument has no effect if the path was created with gdsii_path True. ๐Ÿ™…
  • offset (number, list) โ€” if a number, all parallel paths offsets are linearly increased by this amount (which can be negative). A list can be used where each element defines the absolute offset (not offset increase) for one of the parallel paths in this object.
  • relative (bool) โ€” if True, end_point is used as an offset from the current path position, i.e., if the path is at (1, -2) and the end_point is (10, 25), the segment will be constructed from (1, -2) to (1 + 10, -2 + 25) = (11, 23). Otherwise, end_point is used as an absolute coordinate.

Returns

  • out โ€” this object.

Return type: FlexPath

๐ŸŒ™ arc(radius, initial_angle, final_angle, width=None, offset=None)

Add a circular arc section to the path. Curvy queen. ๐ŸŽ€

Parameters

  • radius (number) โ€” radius of the circular arc.
  • initial_angle (number) โ€” initial angle of the arc.
  • final_angle (number) โ€” final angle of the arc.
  • width (number, list) โ€” if a number, all parallel paths are linearly tapered to this width along the segment. A list can be used where each element defines the width for one of the parallel paths in this object. This argument has no effect if the path was created with gdsii_path True.
  • offset (number, list) โ€” if a number, all parallel paths offsets are linearly increased by this amount (which can be negative). A list can be used where each element defines the absolute offset (not offset increase) for one of the parallel paths in this object.

Returns

  • out โ€” this object.

Return type: FlexPath

โญ turn(radius, angle, width=None, offset=None)

Add a circular turn to the path.

The initial angle of the arc is calculated from the last path segment. She knows where she came from. ๐Ÿ’…

Parameters

  • radius (number) โ€” radius of the circular arc.
  • angle ('r', 'l', 'rr', 'll' or number) โ€” angle (in radians) of rotation of the path. The values 'r' and 'l' represent 90-degree turns cw and ccw, respectively; the values 'rr' and 'll' represent analogous 180-degree turns.
  • width (number, list) โ€” if a number, all parallel paths are linearly tapered to this width along the segment. A list can be used where each element defines the width for one of the parallel paths in this object. This argument has no effect if the path was created with gdsii_path True.
  • offset (number, list) โ€” if a number, all parallel paths offsets are linearly increased by this amount (which can be negative). A list can be used where each element defines the absolute offset (not offset increase) for one of the parallel paths in this object.

Returns

  • out โ€” this object.

Return type: FlexPath

๐ŸŒˆ parametric(curve_function, width=None, offset=None, relative=True)

Add a parametric curve to the path. Custom curve, custom vibe, periodt.

Parameters

  • curve_function (callable) โ€” the function that draws the curve for you. It takes one argument (a number that runs from 0 to 1) and hands back a 2-element Numpy array holding the coordinates of the curve. That's it, that's the whole job ๐Ÿ’…
  • width (number, list) โ€” if it's a single number, every parallel path gets linearly tapered to this width along the segment. If it's a list, each element sets the width for one of the parallel paths in this object. Heads up bestie: this one does absolutely nothing if the path was made with gdsii_path True. ๐Ÿ™ˆ
  • offset (number, list) โ€” a single number means all parallel path offsets get linearly increased by this amount (negative totally allowed, go off). A list means each element is the absolute offset (not an offset increase!) for one of the parallel paths in this object.
  • relative (bool) โ€” if True, the return values of curve_function are used as offsets from the current path position, meaning that to keep the path continuous, curve_function(0) has to be (0, 0). Otherwise they're used as absolute coordinates. Choose wisely ๐Ÿซถ

Returns

  • out โ€” this object, right back at you.

Return type

FlexPath


๐Ÿฆ‹ bezier

bezier(points, width=None, offset=None, relative=True)

Add a Bezier curve to the path. It starts from wherever the path currently is and finishes at the last point in the points array. It's giving glow-up math โœจ

Parameters

  • points (array-like[N][2]) โ€” the control points that define the Bezier curve.
  • width (number, list) โ€” if it's a number, all parallel paths are linearly tapered to this width along the segment. A list can be used where each element defines the width for one of the parallel paths in this object. This argument has no effect if the path was created with gdsii_path True.
  • offset (number, list) โ€” if it's a number, all parallel paths offsets are linearly increased by this amount (which can be negative). A list can be used where each element defines the absolute offset (not offset increase) for one of the parallel paths in this object.
  • relative (bool) โ€” if True, all coordinates in the points array are used as offsets from the current path position. Like, if the path is at (1, -2) and the last point in the array is (10, 25), the constructed Bezier will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates. Math is mathing ๐Ÿ“

Returns

  • out โ€” this object.

Return type

FlexPath


๐ŸŽ€ smooth

smooth(points, angles=None, curl_start=1, curl_end=1, t_in=1,
t_out=1, cycle=False, width=None, offset=None, relative=True)

Add a smooth interpolating curve through the given points. It uses the Hobby algorithm [1]_ to calculate a smooth interpolating curve made of cubic Bezier segments between each pair of points. No cap, this one eats and leaves no crumbs ๐Ÿ”ฅ

Parameters

  • points (array-like[N][2]) โ€” vertices in the interpolating curve.
  • angles (array-like[N + 1] or None) โ€” tangent angles at each point (in radians). Any angles defined as None are automatically calculated. Let the algorithm cook ๐Ÿณ
  • curl_start (number) โ€” ratio between the mock curvatures at the first point and at its neighbor. A value of 1 renders the first segment a good approximation for a circular arc. A value of 0 will better approximate a straight segment. It has no effect for closed curves or when an angle is defined for the first point.
  • curl_end (number) โ€” ratio between the mock curvatures at the last point and at its neighbor. It has no effect for closed curves or when an angle is defined for the first point.
  • t_in (number or array-like[N + 1]) โ€” tension parameter when arriving at each point. One value per point, or a single value used for all points.
  • t_out (number or array-like[N + 1]) โ€” tension parameter when leaving each point. One value per point, or a single value used for all points.
  • cycle (bool) โ€” if True, it calculates control points for a closed curve, with an additional segment connecting the first and last points. Full circle moment ๐ŸŒˆ
  • width (number, list) โ€” if it's a number, all parallel paths are linearly tapered to this width along the segment. A list can be used where each element defines the width for one of the parallel paths in this object. This argument has no effect if the path was created with gdsii_path True.
  • offset (number, list) โ€” if it's a number, all parallel paths offsets are linearly increased by this amount (which can be negative). A list can be used where each element defines the absolute offset (not offset increase) for one of the parallel paths in this object.
  • relative (bool) โ€” if True, all coordinates in the points array are used as offsets from the current path position. So if the path is at (1, -2) and the last point in the array is (10, 25), the constructed curve will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates.

Returns

  • out โ€” this object.

Return type

FlexPath

Notes

Arguments width and offset are repeated for each cubic Bezier that composes this path element. Yes, every single one. Keep that in mind ๐Ÿ‘‘

References

[1] Hobby, J.D. Discrete Comput. Geom. (1986) 1: 123. DOI: 10.1007/BF02187690 &lt;https://doi.org/10.1007/BF02187690&gt;


๐Ÿงš RobustPath

class gdspy.RobustPath(initial_point, width, offset=0, ends='flush',
tolerance=0.01, precision=0.001, max_points=199, max_evals=1000,
gdsii_path=False, width_transform=True, layer=0, datatype=0)

Bases: object

Path object with lazy evaluation. ๐Ÿ’…

So here's the tea: this class just keeps information about the constructive parameters of the path and only calculates the boundaries when somebody actually asks for them. The benefits? Joins and path components can be calculated automatically to ensure continuity โ€” except in extreme cases, where even we can't save you ๐Ÿ˜ญ

It can be stored as a proper path element in the GDSII format, unlike Path. In that case though, the width must be constant along the whole path. No tapering, bestie, sorry.

The downside of RobustPath is that it is more computationally expensive than the other path classes. You're paying for the luxury, iconic behavior costs โœจ

Parameters

  • initial_point (array-like[2]) โ€” starting position of the path.
  • width (number, list) โ€” width of each parallel path being created. The number of parallel paths being created is defined by the length of this list.
  • offset (number, list) โ€” offsets of each parallel path from the center. If width is not a list, the length of this list is used to determine the number of parallel paths being created. Otherwise, offset must be a list with the same length as width, or a number, which is used as distance between adjacent paths.
  • ends ('flush', 'extended', 'round', 'smooth', 2-tuple, list) โ€” type of end caps for the paths. A 2-element tuple represents the start and end extensions to the paths. A list can be used to define the end type for each parallel path.
  • tolerance (number) โ€” tolerance used to draw the paths and calculate joins.
  • precision (number) โ€” precision for rounding the coordinates of vertices when fracturing the final polygonal boundary.
  • max_points (integer) โ€” if the number of points in the polygonal path boundary is greater than max_points, it will be fractured in smaller polygons with at most max_points each. If max_points is zero no fracture will occur. Zero = chaos, you've been warned ๐Ÿ’€
  • max_evals (integer) โ€” limit to the maximal number of evaluations when calculating each path component.
  • gdsii_path (bool) โ€” if True, treat this object as a GDSII path element. Otherwise, it will be converted into polygonal boundaries when required.
  • width_transform (bool) โ€” if gdsii_path is True, this flag indicates whether the width of the path should transform when scaling this object. It has no effect when gdsii_path is False.
  • layer (integer, list) โ€” the GDSII layer numbers, one per path element. โœจ If you give fewer layers in the list than there are paths, bestie, the list just gets repeated until everyone's covered. No path left behind. ๐Ÿ’…
  • datatype (integer, list) โ€” the GDSII datatype for the elements of each path, and it's gotta be between 0 and 255, okay? ๐ŸŽ€ Same deal: if your datatype list is shorter than the path count, it loops back around. Iconic, honestly.

Notes ๐Ÿ“

The value of tolerance should not be smaller than precision, otherwise there would be wasted computational effort in calculating the paths.

Translation: don't make tolerance tinier than precision or you're just making the computer do cardio for nothing. Respect its time. ๐Ÿ™ˆ

grad(u, arm=0, side='-') ๐Ÿ’ซ

Calculate the direction vector of each parallel path. It's giving compass energy. ๐Ÿงญ

Parameters

  • u (number) โ€” Position along the RobustPath to compute. This argument can range from 0 (start of the path) to len(self) (end of the path).
  • arm (-1, 0, 1) โ€” Wether to calculate one of the path boundaries (-1 or 1) or its central spine (0). (yes, "wether", the original said it, we don't gatekeep typos here ๐Ÿซถ)
  • side ('-' or '+') โ€” At path joins, whether to calculate the direction using the component before or after the join.

Returns: out โ€” Direction vectors for each of the N parallel paths in this object.

Return type: Numpy array[N, 2]

width(u) ๐Ÿ“

Calculate the width of each parallel path. Get those measurements, queen. ๐Ÿ‘‘

Parameters

  • u (number) โ€” Position along the RobustPath to compute. This argument can range from 0 (start of the path) to len(self) (end of the path).

Returns: out โ€” Width for each of the N parallel paths in this object.

Return type: Numpy array[N]

get_polygons(by_spec=False) ๐Ÿฆ‹

Calculate the polygonal boundaries described by this path. Shapes! Boundaries! The whole vibe. โœจ

Parameters

  • by_spec (bool) โ€” If True, the return value is a dictionary with the polygons of each individual pair (layer, datatype).

Returns: out โ€” List containing the coordinates of the vertices of each polygon, or dictionary with the list of polygons (if by_spec is True).

Return type: list of array-like[N][2] or dictionary

to_polygonset() ๐ŸŽ€

Create a PolygonSet representation of this object.

The resulting object will be fractured according to the parameter max_points used when instantiating this object. So like, it remembers how you set it up. Memory queen. ๐Ÿ’…

Returns: out โ€” A PolygonSet that contains all boundaries for this path. If the path is empty, returns None.

Return type: PolygonSet or None

to_gds(multiplier) ๐Ÿ’พ

Convert this object to a series of GDSII elements.

If RobustPath.gdsii_path is True, GDSII path elements are created instead of boundaries. Such paths do not support variable widths, but their memeory footprint is smaller than full polygonal boundaries. (memeory. the original said memeory. we love her. ๐Ÿ˜ญ) So like: cute and small, but no variable widths, bestie. Choose your fighter.

Parameters

  • multiplier (number) โ€” A number that multiplies all dimensions written in the GDSII elements.

Returns: out โ€” The GDSII binary string that represents this object.

Return type: string

area(by_spec=False) ๐Ÿ”ฅ

Calculate the total area of this object.

This functions creates a PolgonSet from this object and calculates its area, which means it is computationally expensive. Okay so heads up: this one's lowkey a workout for your CPU. Not the cheap little call you think it is. ๐Ÿฅต

Parameters

  • by_spec (bool) โ€” If True, the return value is a dictionary with {(layer, datatype): area}.

Returns: out โ€” Area of this object.

Return type: number, dictionary

translate(dx, dy) ๐Ÿšš

Translate this path. Scoot it. Slide it. Move it, move it. ๐Ÿ•บ

Parameters

  • dx (number) โ€” Distance to move in the x-direction
  • dy (number) โ€” Distance to move in the y-direction

Returns: out โ€” This object.

Return type: RobustPath

rotate(angle, center=(0, 0)) ๐ŸŒช๏ธ

Rotate this path. Spin it like it's the main character. ๐Ÿ‘‘

Parameters

  • angle (number) โ€” The angle of rotation (in radians).
  • center (array-like[2]) โ€” Center point for the rotation.

Returns: out โ€” This object.

Return type: RobustPath

scale(scale, center=(0, 0)) ๐ŸŒธ

Scale this path. Bigger. Smaller. Either way, slay. โœจ

Parameters

  • scale (number) โ€” Scaling factor.
  • center (array-like[2]) โ€” Center point for the scaling operation.

Returns: out โ€” This object.

Return type: RobustPath

transform(translation, rotation, scale, x_reflection, array_trans=None) ๐ŸŒ€

Apply a transform to this path. The full combo meal, bestie. ๐Ÿ“

Parameters

  • translation (Numpy array[2]) โ€” Translation vector.
  • rotation (number) โ€” Rotation angle.
  • scale (number) โ€” Scaling factor.
  • x_reflection (bool) โ€” Reflection around the first axis.
  • array_trans (Numpy aray[2]) โ€” Translation vector before rotation and reflection. (yes, "aray". she's doing her best. ๐Ÿ’œ)

Returns: out โ€” This object.

Return type: RobustPath

Notes ๐Ÿ“

Applies the transformations in the same order as a CellReference or a CellArray. If width_transform is False, the widths are not scaled.

So order matters, and if width_transform is False, your widths stay exactly as they are. Noted. Don't come crying later. ๐Ÿ˜Œ

segment(end_point, width=None, offset=None, relative=False) ๐Ÿ“

Add a straight section to the path. Straight line? We love a minimalist moment. ๐Ÿ’…

Parameters

  • end_point (array-like[2]) โ€” End position of the straight segment.
  • width (number, callable, list) โ€” If a number, all parallel paths are linearly tapered to this width along the segment. If this is callable, it must be a function of one argument (that varies from 0 to 1) that returns the width of the path. A list can be used where each element (number or callable) defines the width for one of the parallel paths in this object.
  • offset (number, callable, list) โ€” If a number, all parallel paths offsets are linearly increased by this amount (which can be negative). If this is callable, it must be a function of one argument (that varies from 0 to 1) that returns the offset increase. A list can be used where each element (number or callable) defines the absolute offset (not offset increase) for one of the parallel paths in this object. ๐Ÿ‘€ read that last part again โ€” the list version is not the same as the number version. Absolute, not increase. iykyk.
  • relative (bool) โ€” If True, end_point is used as an offset from the current path position, i.e., if the path is at (1, -2) and the end_point is (10, 25), the segment will be constructed from (1, -2) to (1 + 10, -2 + 25) = (11, 23). Otherwise, end_point is used as an absolute coordinate.

Returns: out โ€” This object.

Return type: RobustPath

arc(radius, initial_angle, final_angle, width=None, offset=None) ๐ŸŒˆ

Add a circular arc section to the path. Curves! Drama! The character development arc we deserve. ๐Ÿ’–

Parameters

  • radius (number) โ€” Radius of the circular arc.
  • initial_angle (number) โ€” Initial angle of the arc.
  • final_angle (number) โ€” Final angle of the arc.
  • width (number, callable, list) โ€” If a number, all parallel paths are linearly tapered to this width along the segment. If this is callable, it must be a function of one argument (that varies from 0 to 1) that returns the width of the path. A list can be used where each element (number or callable) defines the width for one of the parallel paths in this object.
  • offset (number, callable, list) โ€” If a number, all parallel paths offsets are linearly increased by this amount (which can be negative). If this is callable, it must be a function of one argument (that varies from 0 to 1) that returns the offset increase. A list can be used where each element (number or callable) defines the absolute offset (not offset increase) for one of the parallel paths in this object.

Returns: out โ€” This object.

Return type: RobustPath

turn(radius, angle, width=None, offset=None) โ†ฉ๏ธ

Add a circular turn to the path.

The initial angle of the arc is calculated from an average of the current directions of all the parallel paths crammed into this object. No cap, she just averages the vibes of everything going on in there. ๐ŸŒ€

Parameters

  • radius (number) โ€” Radius of the circular arc. ๐Ÿ’ซ
  • angle ('r', 'l', 'rr', 'll' or number) โ€” The angle (in radians) of rotation of the path. The values 'r' and 'l' are your 90-degree turns, cw and ccw respectively; 'rr' and 'll' are the matching 180-degree turns. Periodt. ๐Ÿ”„
  • width (number, callable, list) โ€” If it's a number, every parallel path gets linearly tapered to this width along the segment. If it's callable, it has to be a function of one argument (varying from 0 to 1) that returns the width of the path. And you can pass a list where each element (number or callable) sets the width for one of the parallel paths in this object. She's flexible like that. ๐Ÿ’…
  • offset (number, callable, list) โ€” If it's a number, all parallel path offsets get linearly increased by this amount (yes, it can be negative, we support the negativity here ๐Ÿซถ). If it's callable, it must be a function of one argument (varying from 0 to 1) that returns the offset increase. A list works too, where each element (number or callable) defines the absolute offset (not the offset increase, ok, important distinction bestie) for one of the parallel paths in this object.

Returns

out โ€” This object. She's giving "return of the queen" energy. ๐Ÿ‘‘

Return type

RobustPath


parametric(curve_function, curve_derivative=None, width=None, offset=None, relative=True)

Add a parametric curve to the path. โœจ

Parameters

  • curve_function (callable) โ€” Function that defines the curve. Must take one argument (varying from 0 to 1) and return a 2-element Numpy array with the coordinates of the curve.
  • curve_derivative (callable) โ€” If you set it, it should be the derivative of the curve function. Also a function of one argument (0 to 1) returning a 2-element Numpy array. If it's None, the derivative gets calculated numerically โ€” she'll figure it out herself, lowkey. ๐Ÿ˜Œ
  • width (number, callable, list) โ€” If it's a number, all parallel paths get linearly tapered to this width along the segment. If it's callable, it must be a function of one argument (varying from 0 to 1) that returns the width of the path. A list can be used where each element (number or callable) defines the width for one of the parallel paths in this object. ๐ŸŽ€
  • offset (number, callable, list) โ€” If it's a number, all parallel path offsets are linearly increased by this amount (which can be negative). If it's callable, it must be a function of one argument (varying from 0 to 1) that returns the offset increase. And a list can be used where each element (number or callable) defines the absolute offset (not offset increase!!) for one of the parallel paths in this object.
  • relative (bool) โ€” If True, the return values of curve_function are used as offsets from the current path position, i.e., to guarantee a continuous path, curve_function(0) must be (0, 0). Otherwise they're used as absolute coordinates. Simple as. ๐ŸŒˆ

Returns

out โ€” This object.

Return type

RobustPath


bezier(points, width=None, offset=None, relative=True)

Add a Bezier curve to the path. ๐Ÿฆ‹

A Bezier curve is added to the path starting from its current position and finishing at the last point in the points array. That's the whole thing, she starts where you are and ends at the final point. Iconic behaviour tbh.

Parameters

  • points (array-like[N][2]) โ€” Control points defining the Bezier curve. ๐ŸŽฏ
  • width (number, callable, list) โ€” If it's a number, all parallel paths are linearly tapered to this width along the segment. If callable, it must be a function of one argument (varying from 0 to 1) that returns the width of the path. A list can be used where each element (number or callable) defines the width for one of the parallel paths in this object.
  • offset (number, callable, list) โ€” If it's a number, all parallel path offsets are linearly increased by this amount (which can be negative). If callable, it must be a function of one argument (varying from 0 to 1) that returns the offset increase. A list can be used where each element (number or callable) defines the absolute offset (not offset increase) for one of the parallel paths in this object.
  • relative (bool) โ€” If True, all coordinates in the points array are used as offsets from the current path position, i.e., if the path is at (1, -2) and the last point in the array is (10, 25), the constructed Bezier will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates. Math girlies rise up, the arithmetic is right there. ๐Ÿงฎ

Returns

out โ€” This object.

Return type

RobustPath


smooth(points, angles=None, curl_start=1, curl_end=1, t_in=1, t_out=1, cycle=False, width=None, offset=None, relative=True)

Add a smooth interpolating curve through the given points. ๐Ÿงšโœจ

Uses the Hobby algorithm [1]_ to calculate a smooth interpolating curve made of cubic Bezier segments between each pair of points. She is not doing the messy thing, she is doing the elegant thing.

Parameters

  • points (array-like[N][2]) โ€” Vertices in the interpolating curve.
  • angles (array-like[N + 1] or None) โ€” Tangent angles at each point (in radians). Any angles defined as None get automatically calculated. Let her handle the ones you don't care about ๐Ÿ’œ
  • curl_start (number) โ€” Ratio between the mock curvatures at the first point and at its neighbor. A value of 1 renders the first segment a good approximation for a circular arc. A value of 0 will better approximate a straight segment. It has no effect for closed curves or when an angle is defined for the first point.
  • curl_end (number) โ€” Ratio between the mock curvatures at the last point and at its neighbor. It has no effect for closed curves or when an angle is defined for the first point. (Yes, it says "first point" โ€” we're keeping it exactly like that, don't @ me. ๐Ÿ’€)
  • t_in (number or array-like[N + 1]) โ€” Tension parameter when arriving at each point. One value per point, or a single value used for all points.
  • t_out (number or array-like[N + 1]) โ€” Tension parameter when leaving each point. One value per point, or a single value used for all points.
  • cycle (bool) โ€” If True, calculates control points for a closed curve, with an additional segment connecting the first and last points. Loop her up, she's a circle now ๐Ÿ”
  • width (number, callable, list) โ€” If it's a number, all parallel paths are linearly tapered to this width along the segment. If callable, it must be a function of one argument (varying from 0 to 1) that returns the width of the path. A list can be used where each element (number or callable) defines the width for one of the parallel paths in this object.
  • offset (number, callable, list) โ€” If it's a number, all parallel path offsets are linearly increased by this amount (which can be negative). If callable, it must be a function of one argument (varying from 0 to 1) that returns the offset increase. A list can be used where each element (number or callable) defines the absolute offset (not offset increase) for one of the parallel paths in this object.
  • relative (bool) โ€” If True, all coordinates in the points array are used as offsets from the current path position, i.e., if the path is at (1, -2) and the last point in the array is (10, 25), the constructed curve will end at (1 + 10, -2 + 25) = (11, 23). Otherwise, the points are used as absolute coordinates. ๐ŸŒธ

Returns

out โ€” This object.

Return type

RobustPath

Notes

Arguments width and offset are repeated for each cubic Bezier that composes this path element. Every single one. She's thorough like that, bestie. ๐Ÿซถ

๐ŸŒˆ References


๐ŸŒ€ Curve

class gdspy.Curve(x, y=0, tolerance=0.01)

Bases: object (yes bestie, it's a plain old object, we move)

Curve generation, loosely based on SVG paths ๐Ÿฆ‹ โ€” like, the vibe is very "what if SVG but you could just... generate it."

Okay so here's the quick cheat-sheet of everything you can throw at it:

  • L/l โ€” Line segments
  • H/h โ€” Horizontal line segments
  • V/v โ€” Vertical line segments
  • C/c โ€” Cubic Bezier curve
  • S/s โ€” Smooth cubic Bezier curve
  • Q/q โ€” Quadratic Bezier curve
  • T/t โ€” Smooth quadratic Bezier curve
  • B/b โ€” General degree Bezier curve
  • I/i โ€” Smooth interpolating curve
  • arc โ€” Elliptical arc

The uppercase versions are like "these coordinates are absolute, periodt" ๐ŸŒ, and the lowercase versions are all "coordinates are relative to wherever the current end point of the curve is at" ๐Ÿ”ฅ. Same letters, different energy. iykyk.

Parameters

  • x (number) โ€” X-coordinate of the starting point of the curve. ๐Ÿ‘€ And here's the fun part: if you hand it a complex number, the value of y just gets ignored entirely and the starting point becomes (x.real, x.imag).
  • y (number) โ€” Y-coordinate of the starting point of the curve.
  • tolerance (number) โ€” Tolerance used to calculate a polygonal approximation to the curve โœจ (basically "how close is close enough, bestie").

Notes

  • In all methods of this class that accept coordinate pairs, a single complex number can be passed and it gets split into its real and imaginary parts ๐Ÿ’…. This feature can be useful in expressing coordinates in polar form โ€” genuinely lowkey iconic.
  • All commands follow the SVG 2 specification, except for elliptical arcs and smooth interpolating curves, which are inspired by the Metapost syntax ๐ŸŽ€.

๐ŸŒธ Examples

>>> curve = gdspy.Curve(3, 4).H(1).q(0.5, 1, 2j).L(2 + 3j, 2, 2)
>>> pol = gdspy.Polygon(curve.get_points())

โญ get_points()

Get the polygonal points that approximate this curve ๐Ÿซถ.

Returns

  • out โ€” Vertices of the polygon.

Return type

  • Numpy array[N, 2]

โœจ L(*xy)

Add straight line segments to the curve โœ๏ธ.

Parameters

  • xy (numbers) โ€” Endpoint coordinates of the line segments.

Returns

  • out โ€” This curve.

Return type

  • Curve

โœจ l(*xy)

Add straight line segments to the curve โœ๏ธ.

Parameters

  • xy (numbers) โ€” Endpoint coordinates of the line segments relative to the current end point.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ’œ H(*x)

Add horizontal line segments to the curve โžก๏ธ.

Parameters

  • x (numbers) โ€” Endpoint x-coordinates of the line segments.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ’œ h(*x)

Add horizontal line segments to the curve โžก๏ธ.

Parameters

  • x (numbers) โ€” Endpoint x-coordinates of the line segments relative to the current end point.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿฆ‹ V(*y)

Add vertical line segments to the curve โฌ†๏ธ.

Parameters

  • y (numbers) โ€” Endpoint y-coordinates of the line segments.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿฆ‹ v(*y)

Add vertical line segments to the curve โฌ†๏ธ.

Parameters

  • y (numbers) โ€” Endpoint y-coordinates of the line segments relative to the current end point.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐ŸŒˆ arc(radius, initial_angle, final_angle, rotation=0)

Add an elliptical arc to the curve ๐ŸŽ€.

Parameters

  • radius (number, array-like[2]) โ€” Arc radius. You can make an elliptical arc by passing an array with 2 radii ๐Ÿ‘‘.
  • initial_angle (number) โ€” Initial angle of the arc (in radians).
  • final_angle (number) โ€” Final angle of the arc (in radians).
  • rotation (number) โ€” Rotation of the axis of the ellipse.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ”ฅ C(*xy)

Add cubic Bezier curves to the curve ๐Ÿ’–.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 3 pairs are interpreted as the control point at the beginning of the curve, the control point at the end of the curve and the endpoint of the curve.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ”ฅ c(*xy)

Add cubic Bezier curves to the curve ๐Ÿ’–.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 3 pairs are interpreted as the control point at the beginning of the curve, the control point at the end of the curve and the endpoint of the curve. All coordinates are relative to the current end point.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿซถ S(*xy)

Add smooth cubic Bezier curves to the curve โœจ.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 2 pairs are interpreted as the control point at the end of the curve and the endpoint of the curve. The control point at the beginning of the curve is assumed to be the reflection of the control point at the end of the last curve relative to the starting point of the curve. If the previous curve is not a cubic Bezier, the control point is coincident with the starting point ๐Ÿ‘€.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿซถ s(*xy)

Add smooth cubic Bezier curves to the curve โœจ.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 2 pairs are interpreted as the control point at the end of the curve and the endpoint of the curve. The control point at the beginning of the curve is assumed to be the reflection of the control point at the end of the last curve relative to the starting point of the curve. If the previous curve is not a cubic Bezier, the control point is coincident with the starting point ๐Ÿ‘€. All coordinates are relative to the current end point.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ“ Q(*xy)

Add quadratic Bezier curves to the curve ๐ŸŒธ.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 2 pairs are interpreted as the control point and the endpoint of the curve.

Returns

  • out โ€” This curve.

Return type

  • Curve

๐Ÿ“ q(*xy)

Add quadratic Bezier curves to the curve ๐ŸŒธ.

Parameters

  • xy (numbers) โ€” Coordinate pairs. Each set of 2 pairs are interpreted as the control point and the endpoint of the curve. All coordinates are relative to the current end point.

Returns

  • out โ€” This curve.

T(*xy)

Okay bestie, this one adds smooth quadratic Bezier curves to your curve. So chic. โœจ

Parameters

  • xy (numbers) โ€” the coordinates of the endpoints of the curves. The control point is assumed to be the reflection of the control point of the last curve, relative to the starting point of the curve. But heads up ๐Ÿ‘€ โ€” if the previous curve is not a quadratic Bezier, then the control point is just coincident with the starting point.

Returns

  • out โ€” this curve. (Yes, it hands you back the same curve, builder-style, so you can keep chaining.)

Return type

  • Curve

t(*xy)

Same energy as T, but relative. ๐Ÿ’… Adds smooth quadratic Bezier curves to the curve.

Parameters

  • xy (numbers) โ€” the coordinates of the endpoints of the curves. The control point is assumed to be the reflection of the control point of the last curve, relative to the starting point of the curve. If the previous curve is not a quadratic Bezier, the control point is coincident with the starting point. And all coordinates are relative to the current end point.

Returns

  • out โ€” this curve.

Return type

  • Curve

B(*xy)

Adds a general degree Bezier curve. Go off, Bรฉzier. ๐Ÿ˜Œ

Parameters

  • xy (numbers) โ€” coordinate pairs. The last coordinate is the endpoint of the curve, and all the others are control points.

Returns

  • out โ€” this curve.

Return type

  • Curve

b(*xy)

The relative twin of B. Adds a general degree Bezier curve.

Parameters

  • xy (numbers) โ€” coordinate pairs. The last coordinate is the endpoint of the curve, and all the others are control points. And again: all coordinates are relative to the current end point.

Returns

  • out โ€” this curve.

Return type

  • Curve

I(points, angles=None, curl_start=1, curl_end=1, t_in=1, t_out=1, cycle=False)

Okay this one is iconic. It adds a smooth interpolating curve through the given points. ๐Ÿฆ‹

It uses the Hobby algorithm [1] to calculate a smooth interpolating curve made of cubic Bezier segments between each pair of points.

Parameters

  • points (array-like[N][2]) โ€” vertices in the interpolating curve.
  • angles (array-like[N + 1] or None) โ€” tangent angles at each point (in radians). Any angles defined as None are automatically calculated. โœจ
  • curl_start (number) โ€” ratio between the mock curvatures at the first point and at its neighbor. A value of 1 renders the first segment a good approximation for a circular arc. A value of 0 will better approximate a straight segment. It has no effect for closed curves, or when an angle is defined for the first point.
  • curl_end (number) โ€” ratio between the mock curvatures at the last point and at its neighbor. It has no effect for closed curves, or when an angle is defined for the first point.
  • t_in (number or array-like[N + 1]) โ€” tension parameter when arriving at each point. One value per point, or a single value used for all points.
  • t_out (number or array-like[N + 1]) โ€” tension parameter when leaving each point. One value per point, or a single value used for all points.
  • cycle (bool) โ€” if True, calculates control points for a closed curve, with an additional segment connecting the first and last points.

Returns

  • out โ€” this curve.

Return type

  • Curve

Examples

>>> c1 = gdspy.Curve(0, 1).I([(1, 1), (2, 1), (1, 0)])
>>> c2 = gdspy.Curve(0, 2).I([(1, 2), (2, 2), (1, 1)],
...                          cycle=True)
>>> ps = gdspy.PolygonSet([c1.get_points(), c2.get_points()])

References


i(points, angles=None, curl_start=1, curl_end=1, t_in=1, t_out=1, cycle=False)

Same algorithm, but the relative version โ€” adds a smooth interpolating curve through the given points. ๐ŸŽ€

Uses the Hobby algorithm [1] to calculate a smooth interpolating curve made of cubic Bezier segments between each pair of points.

Parameters

  • points (array-like[N][2]) โ€” vertices in the interpolating curve (relative to the current endpoint).
  • angles (array-like[N + 1] or None) โ€” tangent angles at each point (in radians). Any angles defined as None are automatically calculated. โœจ
  • curl_start (number) โ€” ratio between the mock curvatures at the first point and at its neighbor. A value of 1 renders the first segment a good approximation for a circular arc. A value of 0 will better approximate a straight segment. It has no effect for closed curves, or when an angle is defined for the first point.
  • curl_end (number) โ€” ratio between the mock curvatures at the last point and at its neighbor. It has no effect for closed curves, or when an angle is defined for the first point.
  • t_in (number or array-like[N + 1]) โ€” tension parameter when arriving at each point. One value per point, or a single value used for all points.
  • t_out (number or array-like[N + 1]) โ€” tension parameter when leaving each point. One value per point, or a single value used for all points.
  • cycle (bool) โ€” if True, calculates control points for a closed curve, with an additional segment connecting the first and last points.

Returns

  • out โ€” this curve.

Return type

  • Curve

Examples

>>> c1 = gdspy.Curve(0, 1).i([(1, 0), (2, 0), (1, -1)])
>>> c2 = gdspy.Curve(0, 2).i([(1, 0), (2, 0), (1, -1)],
...                          cycle=True)
>>> ps = gdspy.PolygonSet([c1.get_points(), c2.get_points()])

References


๐ŸŒธ Label ๐ŸŒธ

class gdspy.Label(text, position, anchor='o', rotation=None,
magnification=None, x_reflection=False, layer=0, texttype=0)

Bases: object

So this one is your vibe annotator ๐Ÿ’œ โ€” text you can use to label parts of the geometry or display messages. And here's the tea: the text does NOT create additional geometry. It's meant for display and labeling purposes only. Purely decorative, purely iconic.

Parameters

  • text (string) โ€” the text of this label.
  • position (array-like[2]) โ€” text anchor position.
  • anchor ('n', 's', 'e', 'w', 'o', 'ne', 'nw'...) โ€” position of the anchor relative to the text.
  • rotation (number) โ€” angle of rotation of the label (in degrees).
  • magnification (number) โ€” magnification factor for the label.
  • x_reflection (bool) โ€” if True, the label is reflected parallel to the x direction before being rotated (not supported by LayoutViewer ๐Ÿ˜ญ).
  • layer (integer) โ€” the GDSII layer number for these elements.
  • texttype (integer) โ€” the GDSII text type for the label (between 0 and 63).

Variables

  • text (string) โ€” the text of this label.
  • position (array-like[2]) โ€” text anchor position.
  • anchor (int) โ€” position of the anchor relative to the text.
  • rotation (number) โ€” angle of rotation of the label (in degrees).
  • magnification (number) โ€” magnification factor for the label.
  • x_reflection (bool) โ€” if True, the label is reflected parallel to the x direction before being rotated (not supported by LayoutViewer).
  • layer (integer) โ€” the GDSII layer number for these elements.
  • texttype (integer) โ€” the GDSII text type for the label (between 0 and 63).

Examples

>>> label = gdspy.Label('Sample label', (10, 0), 'sw')
>>> myCell.add(label)

to_gds(multiplier)

Convert this label to a GDSII structure. Bestie, this is the glow-up from "label object" to "actual GDSII bytes." โœจ

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII structure.

Returns

  • out โ€” the GDSII binary string that represents this label.

Return type

  • string

translate(dx, dy)

Translate this label. Just scoot it. ๐Ÿ›ผ

Parameters

  • dx (number) โ€” distance to move in the x-direction
  • dy (number) โ€” distance to move in the y-direction

Return type : Label

Examples

>>> text = gdspy.Label((0, 0), (10, 20))
>>> text = text.translate(2, 0)
>>> myCell.add(text)

๐Ÿงฎ boolean

gdspy.boolean(operand1, operand2, operation, precision=0.001,
max_points=199, layer=0, datatype=0)

okay so this one runs literally any boolean operation between 2 polygons or polygon sets. math but make it fashion ๐Ÿ’…

Parameters

  • operand1 (PolygonSet, CellReference, CellArray or iterable) โ€” the first operand, bestie. If you hand it an iterable, every element in there has to be a PolygonSet, CellReference, CellArray, or an array-like[N][2] of polygon vertices.
  • operand2 (None, PolygonSet, CellReference, CellArray or iterable) โ€” the second operand. Same deal: if it's an iterable, each element must be a PolygonSet, CellReference, CellArray, or an array-like[N][2] of polygon vertices.
  • operation ({'or', 'and', 'xor', 'not'}) โ€” which boolean operation we're executing. Heads up tho: the 'not' operation returns the difference operand1 - operand2.
  • precision (float) โ€” the desired precision for rounding vertice coordinates.
  • max_points (integer) โ€” if this is greater than 4, the resulting polygons get fractured so they have at most max_points vertices. โš ๏ธ This is not a tessellating function, so keep this number as high as possible. Like, for polygons being drawn in GDSII files, it should be set to 199.
  • layer (integer) โ€” the GDSII layer number for the resulting element.
  • datatype (integer) โ€” the GDSII datatype for the resulting element (between 0 and 255).

Returns : out โ€” result of the boolean operation.

Return type : PolygonSet <#gdspy.PolygonSet> or None


๐Ÿซง offset

gdspy.offset(polygons, distance, join='miter', tolerance=2,
precision=0.001, join_first=False, max_points=199, layer=0, datatype=0)

shrink or expand a polygon or polygon set. it's giving glow-up or shrink-flation ๐Ÿฆ‹

Parameters

  • polygons (PolygonSet, CellReference, CellArray or iterable) โ€” the polygons to be offset. If it's an iterable, each element must be a PolygonSet, CellReference, CellArray, or an array-like[N][2] of polygon vertices.
  • distance (number) โ€” offset distance. Positive to expand, negative to shrink.
  • join ('miter', 'bevel', 'round') โ€” the type of join used to create the offset polygon.
  • tolerance (number) โ€” for miter joints, this number must be at least 2 and it represents the maximal distance in multiples of offset between new vertices and their original position before beveling, so you don't get spikes at acute joints ๐Ÿ‘€ For round joints, it indicates the curvature resolution in number of points per full circle.
  • precision (float) โ€” desired precision for rounding vertex coordinates.
  • join_first (bool) โ€” join all paths before offsetting, so we skip unnecessary joins in adjacent polygon sides.
  • max_points (integer) โ€” if greater than 4, fracture the resulting polygons so they have at most max_points vertices. Again: this is not a tessellating function, so this number should be as high as possible. For example, set it to 199 for polygons being drawn in GDSII files.
  • layer (integer) โ€” the GDSII layer number for the resulting element.
  • datatype (integer) โ€” the GDSII datatype for the resulting element (between 0 and 255).

Returns : out โ€” returns the offset shape as a set of polygons.

Return type : PolygonSet or None


๐Ÿ”ช slice

gdspy.slice(polygons, position, axis, precision=0.001, layer=0,
datatype=0)

slice polygons and polygon sets at given positions along an axis. chef's knife energy ๐Ÿ”ฅ

Parameters

  • polygons (PolygonSet, CellReference, CellArray or iterable) โ€” the operand of the slice operation. If it's an iterable, each element must be a PolygonSet, CellReference, CellArray, or an array-like[N][2] of polygon vertices.
  • position (number or list of numbers) โ€” the positions to perform the slicing operation along the specified axis.
  • axis (0 or 1) โ€” the axis along which the polygon will be sliced.
  • precision (float) โ€” desired precision for rounding vertice coordinates.
  • layer (integer, list) โ€” the GDSII layer numbers for the elements between each division. If the number of layers in the list is less than the number of divided regions, the list is repeated.
  • datatype (integer, list) โ€” the GDSII datatype for the resulting element (between 0 and 255). If the number of datatypes in the list is less than the number of divided regions, the list is repeated.

Returns : out โ€” result of the slicing operation, with N = len(positions) + 1. Each PolygonSet comprises all polygons between 2 adjacent slicing positions, in crescent order.

Return type : list[N] of PolygonSet or None

Examples

>>> ring = gdspy.Round((0, 0), 10, inner_radius = 5)
>>> result = gdspy.slice(ring, [-7, 7], 0)
>>> cell.add(result[1])

๐ŸŽฏ inside

gdspy.inside(points, polygons, short_circuit='any', precision=0.001)

test whether each of the points is within the given set of polygons. vibe check but spatial โœจ

Parameters

  • points (array-like[N][2] or sequence of array-like[N][2]) โ€” coordinates of the points to be tested, or groups of points to be tested together.
  • polygons (PolygonSet, CellReference, CellArray or iterable) โ€” the polygons to be tested against. If it's an iterable, each element must be a PolygonSet, CellReference, CellArray, or an array-like[N][2] of polygon vertices.
  • short_circuit ({'any', 'all'}) โ€” if points is a sequence of point groups, testing within each group gets short-circuited if any of the points in the group is inside ('any') or outside ('all') the polygons. If points is just a plain sequence of points, this parameter does absolutely nothing, bestie.
  • precision (float) โ€” desired precision for rounding vertice coordinates.

Returns : out โ€” tuple of booleans indicating if each of the points or point groups is inside the set of polygons.

Return type : tuple


๐Ÿ“‹ copy

gdspy.copy(obj, dx=0, dy=0)

create a copy of obj and translate it by (dx, dy). dupe but make it legal ๐Ÿ’…

Parameters

  • obj (translatable object) โ€” the object to be copied.
  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns : out โ€” translated copy of original obj

Return type : translatable object

Examples

>>> rectangle = gdspy.Rectangle((0, 0), (10, 20))
>>> rectangle2 = gdspy.copy(rectangle, 2,0)
>>> myCell.add(rectangle)
>>> myCell.add(rectangle2)

๐Ÿ“š GDSII Library


๐Ÿ  Cell

class gdspy.Cell(name, exclude_from_current=False)

Bases: object

a collection of polygons, paths, labels and raferences to other cells. yes it says "raferences", we don't gatekeep typos in this house ๐Ÿซถ

Parameters

  • name (string) โ€” the name of the cell.
  • exclude_from_current (bool) โ€” if True, the cell will not be automatically included in the current library.

Variables

  • name (string) โ€” the name of this cell.
  • polygons (list of PolygonSet) โ€” list of cell polygons.
  • paths (list of RobustPath or FlexPath) โ€” list of cell paths.
  • labels (list of Label) โ€” list of cell labels.
  • references (list of CellReference or CellArray) โ€” list of cell references.

๐Ÿ’พ to_gds

to_gds(multiplier, timestamp=None)

convert this cell to a GDSII structure. glow-up into raw bytes ๐Ÿ”ฅ

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII structure.
  • timestamp (datetime object) โ€” sets the GDSII timestamp. If None, the current time is used.

Returns : out โ€” the GDSII binary string that represents this cell.

Return type : string


๐Ÿชž copy

copy(name, exclude_from_current=False, deep_copy=False)

creates a copy of this cell. twin energy ๐Ÿ‘‘

Parameters

  • name (string) โ€” the name of the cell.
  • exclude_from_current (bool) โ€” if True, the cell will not be included in the global list of cells maintained by gdspy.
  • deep_copy (bool) โ€” if False, the new cell will contain only references to the existing elements. If True, copies of all elements are also created.

Returns : out โ€” the new copy of this cell.

Return type : Cell


โž• add

add(element)

add a new element or list of elements to this cell.

remove_polygons(test) โœ‚๏ธ

Bestie, yeet the polygons out of this cell. The function or callable test gets called on every single polygon in the cell, and if its return value evaluates to True, that polygon is gone from the cell.

Parameters

  • test (callable) โ€” your lil test function that decides whether a polygon should be removed. It gets called with the arguments: (points, layer, datatype).

Returns

  • out โ€” this cell. Yes, it hands you back the cell itself ๐Ÿซถ

Return type

  • Cell

Examples

Kill everything sitting on layer 1:

>>> cell.remove_polygons(lambda pts, layer, datatype:
...                      layer == 1)

Kill everything with negative x coords:

>>> cell.remove_polygons(lambda pts, layer, datatype:
...                      any(pts[:, 0] < 0))

remove_paths(test) ๐Ÿ›ค๏ธ

Same energy, but for paths. test is called for each FlexPath or RobustPath in the cell, and if the return value evaluates to True, the corresponding label is removed from the cell.

ngl the original really does say "label" there ๐Ÿ™ˆ we're just passing it along exactly as written, no cap.

Parameters

  • test (callable) โ€” test function to query whether a path should be removed. It's called with the path as the only argument.

Returns

  • out โ€” this cell.

Return type

  • Cell

remove_labels(test) ๐Ÿท๏ธ

And now for the labels themselves, iconique. test is called for each label in the cell; return value evaluates to True โ†’ that label is removed from the cell.

Parameters

  • test (callable) โ€” test function to query whether a label should be removed. Called with the label as the only argument.

Returns

  • out โ€” this cell.

Return type

  • Cell

Examples

Remove labels in layer 1:

>>> cell.remove_labels(lambda lbl: lbl.layer == 1)

area(by_spec=False) ๐Ÿ“

Crunches the total area of all the elements on this cell, and yes, that includes cell references and arrays โ€” full main character accounting ๐Ÿ’…

Parameters

  • by_spec (bool) โ€” if True, you get back a dictionary with the areas of each individual pair (layer, datatype) instead of one big number.

Returns

  • out โ€” area of this cell.

Return type

  • number, dictionary

get_layers() ๐ŸŽจ

Hands you back the set of layers in this cell.

Returns

  • out โ€” set of the layers used in this cell.

Return type

  • set

get_datatypes() ๐Ÿ”ข

Same but for datatypes, obviously.

Returns

  • out โ€” set of the datatypes used in this cell.

Return type

  • set

get_bounding_box() ๐Ÿ“ฆ

Calculates the bounding box for this cell, bestie. The vibes:

Returns

  • out โ€” bounding box of this cell [[x_min, y_min], [x_max, y_max]], or None if the cell is empty (sad, empty-cell era ๐Ÿ˜ญ).

Return type

  • Numpy array[2, 2] or None

get_polygons(by_spec=False, depth=None) ๐Ÿ”ท

Returns a list of polygons in this cell. Slay.

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the polygons of each individual pair (layer, datatype).
  • depth (integer or None) โ€” if not None, this defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be the name of this cell.

Returns

  • out โ€” list containing the coordinates of the vertices of each polygon, or a dictionary with the list of polygons (if by_spec is True).

Return type

  • list of array-like[N][2] or dictionary
Note: instances of FlexPath and RobustPath are also included in the result by computing their polygonal boundary. Not skipped, not forgotten, they're in there ๐Ÿซถ

get_polygonsets(depth=None) ๐Ÿ’ 

Returns a list with a copy of the polygons in this cell. Copies, bestie, so go wild.

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve polygons from.

Returns

  • out โ€” list containing the polygons in this cell and its references.

Return type

  • list of PolygonSet

get_paths(depth=None) ๐Ÿ›ฃ๏ธ

Returns a list with a copy of the paths in this cell.

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve paths from.

Returns

  • out โ€” list containing the paths in this cell and its references.

Return type

  • list of FlexPath or RobustPath

get_labels(depth=None) ๐Ÿ”–

Returns a list with a copy of the labels in this cell.

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve labels from.

Returns

  • out โ€” list containing the labels in this cell and its references.

Return type

  • list of Label

get_dependencies(recursive=False) ๐Ÿ”—

Returns a list of the cells included in this cell as references. It's giving family tree energy ๐ŸŒณ

Parameters

  • recursive (bool) โ€” if True returns cascading dependencies (all the way down, bestie).

Returns

  • out โ€” list of the cells referenced by this cell.

Return type

  • set of Cell

flatten(single_layer=None, single_datatype=None, single_texttype=None) ๐Ÿฆ‹

Converts all references into polygons, paths and labels. Basically collapsing the whole hierarchy down into real, physical geometry โœจ

Parameters

  • single_layer (integer or None) โ€” if not None, all polygons will be transferred to the layer indicated by this number.
  • single_datatype (integer or None) โ€” if not None, all polygons will be transferred to the datatype indicated by this number.
  • single_datatype โ€” if not None, all labels will be transferred to the texttype indicated by this number.

Returns

  • out โ€” this cell.

Return type

  • Cell

๐ŸŽ€ CellReference ๐ŸŽ€

class gdspy.CellReference(ref_cell, origin=(0, 0), rotation=None,
magnification=None, x_reflection=False, ignore_missing=False)
  • Bases: object

A simple reference to an existing cell. Like, "hey, that cell? put it here" ๐Ÿ’โ€โ™€๏ธ

Parameters

  • ref_cell (Cell or string) โ€” the referenced cell, or just its name.
  • origin (array-like[2]) โ€” position where the reference is inserted.
  • rotation (number) โ€” angle of rotation of the reference (in degrees).
  • magnification (number) โ€” magnification factor for the reference.
  • x_reflection (bool) โ€” if True the reference is reflected parallel to the x direction before being rotated.
  • ignore_missing (bool) โ€” if False a warning is issued when the referenced cell is not found. (So True = "no warning, I know what I'm doing" ๐Ÿ’…)

to_gds(multiplier) ๐Ÿ’พ

Converts this object to a GDSII element.

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII element.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type

  • string

area(by_spec=False) ๐Ÿ“

Calculates the total area of the referenced cell, with the magnification factor included. Yes, the magnification is baked in, we're not forgetting it ๐Ÿซถ

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the areas of each individual pair (layer, datatype).

Returns

  • out โ€” area of this cell.

Return type

  • number, dictionary

get_polygons(by_spec=False, depth=None) ๐Ÿ”ท

Returns the list of polygons created by this reference.

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the polygons of each individual pair (layer, datatype).
  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be the name of the referenced cell.

Returns

  • out โ€” a list holding the coordinates of the vertices of every polygon, or a dictionary with the list of polygons (when by_spec is True).

Return type: list of array-like[N][2] or dictionary

โœจ Note: FlexPath and RobustPath instances are also in the mix โ€” they get included in the result by computing their polygonal boundary. no one gets left out bestie ๐Ÿซถ

get_polygonsets(depth=None)

Hands you back the list of polygons this reference made ๐Ÿ’…

Parameters

  • depth (integer or None) โ€” if it's not None, this says from how many reference levels to grab polygons from.

Returns

  • out โ€” a list holding the polygons in this cell and its references.

Return type: list of PolygonSet


get_paths(depth=None)

Same energy as above but for paths ๐ŸŒธ

Parameters

  • depth (integer or None) โ€” if it's not None, this defines from how many reference levels to retrieve paths from.

Returns

  • out โ€” a list holding the paths in this cell and its references.

Return type: list of FlexPath or RobustPath


get_labels(depth=None)

And the labels too, because they matter ๐Ÿ’–

Parameters

  • depth (integer or None) โ€” if it's not None, this defines from how many reference levels to retrieve labels from.

Returns

  • out โ€” a list holding the labels in this cell and its references.

Return type: list of Label


get_bounding_box()

Does the math to find the bounding box for this reference ๐ŸŽ€

Returns

  • out โ€” bounding box of this cell, [[x_min, y_min], [x_max, y_max]], or None if the cell is empty.

Return type: Numpy array[2, 2] or None


translate(dx, dy)

Slides this reference around ๐Ÿฆ‹

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type: CellReference


โœจ CellArray โœจ

class gdspy.CellArray(ref_cell, columns, rows, spacing, origin=(0, 0),
rotation=None, magnification=None, x_reflection=False,
ignore_missing=False)

Bases: object

It's literally multiple references to an existing cell, all lined up in an array format. It's giving grid energy ๐Ÿ’…

Parameters

  • ref_cell (Cell or string) โ€” the referenced cell, or its name.
  • columns (positive integer) โ€” number of columns in the array.
  • rows (positive integer) โ€” number of columns in the array.
  • spacing (array-like[2]) โ€” distances between adjacent columns and adjacent rows.
  • origin (array-like[2]) โ€” position where the cell is inserted.
  • rotation (number) โ€” angle of rotation of the reference (in degrees).
  • magnification (number) โ€” magnification factor for the reference.
  • x_reflection (bool) โ€” if True, the reference is reflected parallel to the x direction before being rotated.
  • ignore_missing (bool) โ€” if False a warning is issued when the referenced cell is not found.

to_gds(multiplier)

Turns this object into a GDSII element ๐Ÿ”ฅ

Parameters

  • multiplier (number) โ€” a number that multiplies all dimensions written in the GDSII element.

Returns

  • out โ€” the GDSII binary string that represents this object.

Return type: string


area(by_spec=False)

Calculates the total area of the cell array, with the magnification factor included โœจ

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the areas of each individual pair (layer, datatype).

Returns

  • out โ€” area of this cell.

Return type: number, dictionary


get_polygons(by_spec=False, depth=None)

Returns the list of polygons created by this reference ๐Ÿ‘‘

Parameters

  • by_spec (bool) โ€” if True, the return value is a dictionary with the polygons of each individual pair (layer, datatype).
  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. If by_spec is True the key will be name of the referenced cell.

Returns

  • out โ€” a list holding the coordinates of the vertices of every polygon, or a dictionary with the list of polygons (when by_spec is True).

Return type: list of array-like[N][2] or dictionary

๐Ÿ’– Note: FlexPath and RobustPath instances are also included in the result by computing their polygonal boundary.

get_polygonsets(depth=None)

Returns the list of polygons created by this reference ๐ŸŽ€

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve polygons from.

Returns

  • out โ€” a list holding the polygons in this cell and its references.

Return type: list of PolygonSet


get_paths(depth=None)

Returns the list of paths created by this reference ๐Ÿฆ‹

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve paths from.

Returns

  • out โ€” a list holding the paths in this cell and its references.

Return type: list of FlexPath or RobustPath


get_labels(depth=None)

Returns the list of labels created by this reference ๐ŸŒธ

Parameters

  • depth (integer or None) โ€” if not None, defines from how many reference levels to retrieve labels from.

Returns

  • out โ€” a list holding the labels in this cell and its references.

Return type: list of Label


get_bounding_box()

Calculates the bounding box for this reference โญ

Returns

  • out โ€” bounding box of this cell, [[x_min, y_min], [x_max, y_max]], or None if the cell is empty.

Return type: Numpy array[2, 2] or None


translate(dx, dy)

Moves this reference ๐Ÿ’ซ

Parameters

  • dx (number) โ€” distance to move in the x-direction.
  • dy (number) โ€” distance to move in the y-direction.

Returns

  • out โ€” this object.

Return type: CellArray


๐Ÿ‘‘ GdsLibrary ๐Ÿ‘‘

class gdspy.GdsLibrary(name='library', infile=None, unit=1e-06,
precision=1e-09, **kwargs)

Bases: object

It's a GDSII library (file) ๐Ÿ“ โ€” basically a whole GDSII library holding a dictionary of cells.

Parameters

  • name (string) โ€” name of the GDSII library. Ignored if a name is defined in infile.
  • infile (file or string) โ€” GDSII stream file (or path) to be imported. It must be opened for reading in binary format.
  • kwargs (keyword arguments) โ€” arguments passed to read_gds.

Variables

  • name (string) โ€” name of the GDSII library.
  • cell_dict (dictionary) โ€” dictionary of cells in this library, indexed by name.
  • unit (number) โ€” unit size for the objects in the library (in meters).
  • precision (number) โ€” precision for the dimensions of the objects in the library (in meters).

add(cell, overwrite_duplicate=False)

Adds one or more cells to the library ๐Ÿ’…

Parameters

  • cell (Cell or iterable) โ€” cells to be included in the library.
  • overwrite_duplicate (bool) โ€” if True an existing cell with the same name in the library will be overwritten.

Returns

  • out โ€” this object.

Return type: GdsLibrary

Notes

  • CellReference or CellArray instances that referred to an overwritten cell are not automatically updated. ๐Ÿ‘€ so heads up bestie, they do NOT fix themselves.

write_gds(outfile, cells=None, timestamp=None, binary_cells=None)

Writes the GDSII library to a file ๐ŸŽ€

So the actual dimensions that land in the GDSII file are the dimensions of your objects times the ratio unit/precision. Say it with me: if you make a circle with radius 1.5 and you set GdsLibrary.unit to 1.0e-6 (1 um) and GdsLibrary.precision to 1.0e-9 (1 nm), that circle is 1.5 um big and the GDSII file stores the dimension 1500 nm. math is mathing ๐Ÿ’…

Parameters

  • outfile (file or string) โ€” the file (or the path) where the GDSII stream gets written. It must be opened for writing operations in binary format, okay? no text mode, bestie ๐Ÿ™ˆ
  • cells (iterable) โ€” the cells or cell names that go in the library. If it's None, then all cells are used.
  • timestamp (datetime object) โ€” sets the GDSII timestamp. If None, it just uses the current time. clocking in โฐ
  • binary_cells (iterable of bytes) โ€” an iterable with binary data for GDSII cells (from get_binary_cells, for example).

Notes

Only the specified cells get written. Ensuring all cell dependencies are satisfied is on you, the user. the library is not gonna hold your hand for that one ๐Ÿซถ

read_gds(infile, units='skip', rename={}, rename_template='{name}', layers={}, datatypes={}, texttypes={})

Reads a GDSII file into this library. import era โœจ

Parameters

  • infile (file or string) โ€” the GDSII stream file (or path) you want imported. It must be opened for reading in binary format.
  • units ({'convert', 'import', 'skip'}) โ€” controls how to scale and use the units in the imported file. Here's the tea:
    • 'convert': the imported geometry gets scaled to this library's units.
    • 'import': the unit and precision in this library get replaced by the ones from the imported file.
    • 'skip': the imported geometry is not scaled and units are not replaced; the geometry just comes in using the user units of the file.
  • rename (dictionary) โ€” dictionary used to rename the imported cells. Keys and values must be strings.
  • rename_template (string) โ€” template string used to rename the imported cells. Applied only if the cell name is not already in the rename dictionary. Examples: 'prefix-{name}', '{name}-suffix'. cute little name makeovers ๐ŸŽ€
  • layers (dictionary) โ€” dictionary used to convert the layers in the imported cells. Keys and values must be integers.
  • datatypes (dictionary) โ€” dictionary used to convert the datatypes in the imported cells. Keys and values must be integers.
  • texttypes (dictionary) โ€” dictionary used to convert the text types in the imported cells. Keys and values must be integers.

Returns

  • out โ€” this object.

Return type

GdsLibrary

Notes

Not all features from the GDSII specification are currently supported. If any unsupported features show up in the imported file, a warning will be produced. heads up so you're not confused later ๐Ÿ’€

extract(cell, overwrite_duplicate=False)

Extracts a cell from this GDSII file and pulls it into the current global library, referenced dependencies included. bringing the whole friend group along ๐Ÿซถ

Parameters

  • cell (Cell or string) โ€” the cell, or the name of the cell, to be extracted from the imported file. Referenced cells get automatically extracted too.
  • overwrite_duplicate (bool) โ€” if True, an existing cell with the same name in the current global library will be overwritten.

Returns

  • out โ€” the extracted cell.

Return type

Cell

Notes

CellReference or CellArray instances that referred to an overwritten cell are not automatically updated. so likeโ€ฆ watch out, they'll be pointing at nothing fr fr ๐Ÿ˜ญ

top_level()

Outputs the top level cells from the GDSII data.

Top level cells are those that are not referenced by any other cells. the main characters, honestly ๐Ÿ‘‘

Returns

  • out โ€” list of top level cells.

Return type

list


๐ŸŒธ GdsWriter ๐ŸŒธ

class gdspy.GdsWriter(outfile, name='library', unit=1e-06,
precision=1e-09, timestamp=None)

Bases: object

GDSII strem library writer. (yes, "strem" โ€” the original said it, I'm keeping it, don't come for me ๐Ÿ™ˆ)

The dimensions actually written on the GDSII file will be the dimensions of the objects created times the ratio unit/precision. For example, if a circle with radius 1.5 is created and we set unit to 1.0e-6 (1 um) and precision to 1.0e-9 (1 nm), the radius of the circle will be 1.5 um and the GDSII file will contain the dimension 1500 nm.

Parameters

  • outfile (file or string) โ€” the file (or path) where the GDSII stream will be written. It must be opened for writing operations in binary format.
  • name (string) โ€” name of the GDSII library (file).
  • unit (number) โ€” unit size for the objects in the library (in meters).
  • precision (number) โ€” precision for the dimensions of the objects in the library (in meters).
  • timestamp (datetime object) โ€” sets the GDSII timestamp. If None, the current time is used.

Notes

This class can be used for incremental output of the geometry in case the complete layout is too large to be kept in memory all at once. Big layout? no problem, we stream it ๐Ÿ’…

Examples

>>> writer = gdspy.GdsWriter('out-file.gds', unit=1.0e-6,
...                          precision=1.0e-9)
>>> for i in range(10):
...     cell = gdspy.Cell('C{}'.format(i), True)
...     # Add the contents of this cell...
...     writer.write_cell(cell)
...     # Clear the memory: erase Cell objects and any other objects
...     # that won't be needed.
...     del cell
>>> writer.close()

write_cell(cell, timestamp=None)

Writes the specified cell to the file.

Parameters

  • cell (Cell) โ€” cell to be written.
  • timestamp (datetime object) โ€” sets the GDSII timestamp. If None, the current time is used.

Notes

Only the specified cell is written. Dependencies must be manually included. so remember to bring them yourself ๐Ÿ˜Œ

Returns

  • out โ€” this object.

Return type

GdsWriter


write_binary_cells(binary_cells)

Writes the specified binary cells to the file.

Parameters

  • binary_cells (iterable of bytes) โ€” iterable with binary data for GDSII cells (from get_binary_cells, for example).

Returns

  • out โ€” this object.

Return type

GdsWriter


close()

Finalizes the GDSII stream library. close the door, we're done here โœจ


๐ŸŽ€ LayoutViewer ๐ŸŽ€

write_gds

gdspy.write_gds(outfile, cells=None, name='library', unit=1e-06,
precision=1e-09, timestamp=None, binary_cells=None)

Writes the current GDSII library to a file.

The dimensions actually written on the GDSII file will be the dimensions of the objects created times the ratio unit/precision. For example, if a circle with radius 1.5 is created and we set unit to 1.0e-6 (1 um) and precision to 1.0e-9 (1 nm), the radius of the circle will be 1.5 um and the GDSII file will contain the dimension 1500 nm. (yes I said it three times, blame the source, it slaps though ๐Ÿ”ฅ)

Parameters

  • outfile (file or string) โ€” the file (or path) where the GDSII stream will be written. It must be opened for writing operations in binary format.
  • cells (array-like) โ€” the sequence of cells or cell names to be included in the library. If None, all cells are used.
  • name (string) โ€” name of the GDSII library.
  • unit (number) โ€” unit size for the objects in the library (in meters).
  • precision (number) โ€” precision for the dimensions of the objects in the library (in meters).
  • timestamp (datetime object) โ€” sets the GDSII timestamp. If None, the current time is used.
  • binary_cells (iterable of bytes) โ€” iterable with binary data for GDSII cells (from get_binary_cells, for example).

๐Ÿฆ‹ gdsii_hash ๐Ÿฆ‹

gdspy.gdsii_hash(filename, engine=None)

Calculates a hash value for a GDSII file. fingerprint time ๐Ÿ”

The hash is generated based only on the contents of the cells in the GDSII library, ignoring any timestamp records present in the file structure. so the timestamps don't throw off the vibe check โœจ

Parameters

  • filename (string) โ€” full path to the GDSII file.
  • engine (hashlib-like engine) โ€” the engine that executes the hashing algorithm. It must provide the methods update and hexdigest as defined in the hashlib module. If None, the default hashlib.sha1() is used.

Returns

  • out โ€” the hash corresponding to the library contents in hex format.

Return type

string

๐ŸŒˆ get_binary_cells

gdspy.get_binary_cells(infile)

Bestie, this one loads all the cells out of a GDSII stream file in binary format. All of them. Every last one. Pure chaos, but make it efficient ๐Ÿ’–

Parameters

  • infile (file or string) โ€” the GDSII stream file you wanna load (or just a path to it). Heads up though: it must be opened for reading in binary format, okay? Not text. Binary. Don't play yourself ๐Ÿ™ˆ

Returns

  • out โ€” a dictionary of binary cell representations, indexed by name.

Return type

  • dictionary
๐Ÿ“ Notes The returned cells inherit the units of the loaded file. So if you go and use them in a new library, that new library has to use compatible units. No mixing and matching, fr fr.

๐ŸŒธ get_gds_units

gdspy.get_gds_units(infile)

Returns the unit and precision used in the GDS stream file. Like, the vibe check for your measurements โœจ

Parameters

  • infile (file or string) โ€” the GDSII stream file being queried.

Returns

  • out โ€” returns (unit, precision) from the file.

Return type

  • 2-tuple

๐Ÿฆ‹ current_library

gdspy.current_library = <gdspy.GdsLibrary object>

The current GdsLibrary instance, just sitting there ready for automatic creation of GDSII files. It's giving main character energy for real ๐Ÿ‘‘

You can freely overwrite this variable whenever you want with a new instance of GdsLibrary. It's yours. Do what you want with it ๐Ÿ’…

Examples

>>> gdspy.Cell('MAIN')
>>> gdspy.current_library = GdsLibrary()  # Reset current library
>>> gdspy.Cell('MAIN')  # A new MAIN cell is created without error

See that? Reset it, and boom โ€” a fresh MAIN cell, no error, no drama, slay.


๐Ÿ’– SUPPORT

Help support the development of gdspy by donating! Genuinely, if this tool has been eating for you, consider feeding it back ๐Ÿซถ

Click here <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JD2EUE2WPPBQQ>


โœจ Author

Lucas H. Gabrielli

2009-2019, Lucas H. Gabrielli


And that's the whole page, bestie!! ๐Ÿฅณ You made it all the way through gdspy's manual โ€” no cap, that's iconic. This is version 1.4.2, dated Mar 20, 2026, and it's the GDSPY(1) manual page. Go forth and build your little silicon dreams ๐Ÿ’œ๐Ÿงš๐ŸŒธ

๐Ÿ“ฆ where this page comes from