GDSPY(1)
๐ 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:
http://boolean.klaasholwerda.nl/interface/bnf/gdsformat.htmlhttp://www.artwork.com/gdsii/gdsii/http://www.buchanan1.net/stream_description.html
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 segmentsH/hโ Horizontal line segmentsV/vโ Vertical line segmentsC/cโ Cubic Bezier curveS/sโ Smooth cubic Bezier curveQ/qโ Quadratic Bezier curveT/tโ Smooth quadratic Bezier curveB/bโ General degree Bezier curveI/iโ Smooth interpolating curvearcโ 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 asgdspy.FlexPathโgdspy.RobustPathcan 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 ifprecisionis 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'sNone, it just does the same thing asscalex.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'sTrue, 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 thisPolygonSet). 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 perPolygonSetvertex.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'sTrue, 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 thisPolygonSet, 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 perPolygonSetvertex.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 to2 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]], orNoneif 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'sNone, it just usesscalex. 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 topoint1.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) โ ifTrue, 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 thisPolygonSet). 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 perPolygonSetvertex.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 to2 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]], orNoneif 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. IfNone, same asscalex.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 (
integerorNone) โ manually set the number of vertices that form the object (that's the polygonal approximation). This overridestolerance. - max_points (
integer) โ if the number of points in the element is bigger thanmax_points, it gets fractured into smaller polygons with at mostmax_pointseach. And ifmax_pointsis 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 between0and255).
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) โ ifTrue, 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 thisPolygonSet). 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 perPolygonSetvertex. - 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 least5, 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 least5for 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]], orNoneif 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 (
numberorNone) โ scaling factor along the second axis. If it'sNone, it's the same asscalex. - 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 by5 / 9and8 / 9, respectively. And for vertical text, that distance is multiplied by11 / 9. - position (
array-like[2]) โ text position (that's the lower left corner). - horizontal (
bool) โ ifTrue, the text is written from left to right; ifFalse, 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 (between0and255).
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) โ ifTrue, 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 thisPolygonSet, 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 perPolygonSetvertex.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]], orNoneif the polygon is empty.
Return type
Numpy array[2, 2]orNone
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'sNone, it's the same asscalex.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'sNone, it's the same asscalex.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 byscalex. like, onlyscalex. 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 between0and255. 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 overridestolerance.max_points(integer) โ if the number of points in the element is greater thanmax_points, it gets fractured into smaller polygons with at mostmax_pointseach. And ifmax_pointsis 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, between0and255. 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). Overridestolerance.max_points(integer) โ if the number of points in the element is greater thanmax_points, it gets fractured into smaller polygons with at mostmax_pointseach. Ifmax_pointsis 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, between0and255. 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 from0to1) 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 from0to1) that returns a 2-element array. IfNone, 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 totolerance.max_points(integer) โ elements get fractured until each polygon has at mostmax_points. Ifmax_pointsis less than4, 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 from0to1) 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 from0to1) and return the width of the path.relative(bool) โ ifTrue, the return values ofcurve_functionare 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, between0and255. 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 ontolerance, 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 mostmax_points. Ifmax_pointsis 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) โ ifTrue, 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 (between0and255). 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 asNoneare 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 of1renders the first segment a good approximation for a circular arc. A value of0will 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) โ ifTrue, 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 totolerance, more evaluations will be performed.max_points(integer) โ elements will be fractured until each polygon has at mostmax_points. Ifmax_pointsis 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 - 1for an open curve andM = Nfor 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 - 1for an open curve andM = Nfor a closed one. ๐relative(bool) โ ifTrue, 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 (between0and255). 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 thisPolygonSet). 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 perPolygonSetvertex. 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 to2 pi. ๐ธmax_points(integer) โ maximal number of points in each resulting polygon (at least5, otherwise the resulting polygon is not fractured). โ ๏ธ
fr fr, thatmax_pointsfloor of5is 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:PolyPathis deprecated in favor ofFlexPathand will be removed in a future version of Gdspy. So likeโฆ it still works, but it's lowkey on its way out. UseFlexPath, 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 mostmax_pointspoints (and it must be at least 4). Ifmax_pointsis 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) โ ifTrue, 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 thisPolygonSet). 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 perPolygonSetvertex. 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 orNone) โ scaling factor along the second axis. If it'sNone, it's the same asscalex.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:L1Pathis deprecated in favor ofFlexPathand will be removed in a future version of Gdspy. Same energy asPolyPath, 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 thenlength.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 mostmax_pointspoints each, andmax_pointshas to be at least6. Ifmax_pointsis 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 between0and255. 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 thisPolygonSet). 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 perPolygonSetvertex.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 least5, 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 least5for 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 linep2(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'sNone, it's the same asscalex.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. Ifwidthis not a list, the length of this list is what determines how many parallel paths get created. Otherwise,offsetmust be a list with the same length aswidth, 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 whencornersis'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 thanmax_points, it will be fractured into smaller polygons with at mostmax_pointseach. Ifmax_pointsis zero, no fracture will occur.gdsii_path(bool) -- ifTrue, treat this object as a GDSII path element. Otherwise, it gets converted into polygonal boundaries whenever that's required.width_transform(bool) โ ifgdsii_pathisTrue, this flag says whether the width of the path should transform when you scale this object. It does absolutely nothing whengdsii_pathisFalse. 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 between0and255. Same rule as above: if the list runs short compared to the number of paths, it repeats itself. โจ
๐ Notes
The value oftoleranceshould not be smaller thanprecision, 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) โ ifTrue, 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_specisTrue).
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
PolygonSetthat contains all boundaries for this path. If the path is empty, returnsNone.
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) โ ifTrue, 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-directiondy(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 aCellReferenceor aCellArray. And ifwidth_transformisFalse, 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 withgdsii_pathTrue. ๐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) โ ifTrue,end_pointis used as an offset from the current path position, i.e., if the path is at(1, -2)and theend_pointis(10, 25), the segment will be constructed from(1, -2)to(1 + 10, -2 + 25)=(11, 23). Otherwise,end_pointis 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 withgdsii_pathTrue.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 withgdsii_pathTrue.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 from0to1) 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 withgdsii_pathTrue. ๐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) โ ifTrue, the return values ofcurve_functionare 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 withgdsii_pathTrue.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) โ ifTrue, all coordinates in thepointsarray 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 asNoneare automatically calculated. Let the algorithm cook ๐ณcurl_start(number) โ ratio between the mock curvatures at the first point and at its neighbor. A value of1renders the first segment a good approximation for a circular arc. A value of0will 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) โ ifTrue, 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 withgdsii_pathTrue.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) โ ifTrue, all coordinates in thepointsarray 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 <https://doi.org/10.1007/BF02187690>
๐ง 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. Ifwidthis not a list, the length of this list is used to determine the number of parallel paths being created. Otherwise,offsetmust be a list with the same length aswidth, 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 thanmax_points, it will be fractured in smaller polygons with at mostmax_pointseach. Ifmax_pointsis 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) โ ifTrue, treat this object as a GDSII path element. Otherwise, it will be converted into polygonal boundaries when required.width_transform(bool) โ ifgdsii_pathisTrue, this flag indicates whether the width of the path should transform when scaling this object. It has no effect whengdsii_pathisFalse.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 oftoleranceshould not be smaller thanprecision, 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 theRobustPathto compute. This argument can range from0(start of the path) tolen(self)(end of the path).arm(-1,0,1) โ Wether to calculate one of the path boundaries (-1or1) 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 theRobustPathto compute. This argument can range from0(start of the path) tolen(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) โ IfTrue, 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) โ IfTrue, 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-directiondy(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 aCellReferenceor aCellArray. Ifwidth_transformisFalse, 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 from0to1) 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 from0to1) 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) โ IfTrue,end_pointis used as an offset from the current path position, i.e., if the path is at(1, -2)and theend_pointis(10, 25), the segment will be constructed from(1, -2)to(1 + 10, -2 + 25)=(11, 23). Otherwise,end_pointis 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 from0to1) 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 from0to1) 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'sNone, 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) โ IfTrue, the return values ofcurve_functionare 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) โ IfTrue, all coordinates in thepointsarray 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 asNoneget 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 of1renders the first segment a good approximation for a circular arc. A value of0will 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) โ IfTrue, 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) โ IfTrue, all coordinates in thepointsarray 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
- [1] Hobby, J.D. Discrete Comput. Geom. (1986) 1: 123. DOI:
10.1007/BF02187690โ also lives athttps://doi.org/10.1007/BF02187690๐
๐ 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 segmentsH/hโ Horizontal line segmentsV/vโ Vertical line segmentsC/cโ Cubic Bezier curveS/sโ Smooth cubic Bezier curveQ/qโ Quadratic Bezier curveT/tโ Smooth quadratic Bezier curveB/bโ General degree Bezier curveI/iโ Smooth interpolating curvearcโ 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 ofyjust 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] orNone) โ tangent angles at each point (in radians). Any angles defined asNoneare automatically calculated. โจ - curl_start (number) โ ratio between the mock curvatures at the first point and at its neighbor. A value of
1renders the first segment a good approximation for a circular arc. A value of0will 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
- [1] Hobby, J.D. Discrete Comput. Geom. (1986) 1: 123. DOI:
10.1007/BF02187690 <https://doi.org/10.1007/BF02187690>
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] orNone) โ tangent angles at each point (in radians). Any angles defined asNoneare automatically calculated. โจ - curl_start (number) โ ratio between the mock curvatures at the first point and at its neighbor. A value of
1renders the first segment a good approximation for a circular arc. A value of0will 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
- [1] Hobby, J.D. Discrete Comput. Geom. (1986) 1: 123. DOI:
10.1007/BF02187690 <https://doi.org/10.1007/BF02187690>
๐ธ 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 byLayoutViewer๐ญ). - layer (integer) โ the GDSII layer number for these elements.
- texttype (integer) โ the GDSII text type for the label (between
0and63).
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 byLayoutViewer). - layer (integer) โ the GDSII layer number for these elements.
- texttype (integer) โ the GDSII text type for the label (between
0and63).
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,CellArrayor iterable) โ the first operand, bestie. If you hand it an iterable, every element in there has to be aPolygonSet,CellReference,CellArray, or an array-like[N][2]of polygon vertices. - operand2 (
None,PolygonSet,CellReference,CellArrayor iterable) โ the second operand. Same deal: if it's an iterable, each element must be aPolygonSet,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 differenceoperand1-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_pointsvertices. โ ๏ธ 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 to199. - layer (integer) โ the GDSII layer number for the resulting element.
- datatype (integer) โ the GDSII datatype for the resulting element (between
0and255).
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,CellArrayor iterable) โ the polygons to be offset. If it's an iterable, each element must be aPolygonSet,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
2and 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_pointsvertices. Again: this is not a tessellating function, so this number should be as high as possible. For example, set it to199for 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
0and255).
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,CellArrayor iterable) โ the operand of the slice operation. If it's an iterable, each element must be aPolygonSet,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 (
0or1) โ 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
0and255). 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,CellArrayor iterable) โ the polygons to be tested against. If it's an iterable, each element must be aPolygonSet,CellReference,CellArray, or an array-like[N][2]of polygon vertices. - short_circuit ({
'any','all'}) โ ifpointsis 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. Ifpointsis 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
RobustPathorFlexPath) โ list of cell paths. - labels (list of
Label) โ list of cell labels. - references (list of
CellReferenceorCellArray) โ 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 bygdspy. - deep_copy (bool) โ if
False, the new cell will contain only references to the existing elements. IfTrue, 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) โ ifTrue, 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]], orNoneif the cell is empty (sad, empty-cell era ๐ญ).
Return type
Numpy array[2, 2]orNone
get_polygons(by_spec=False, depth=None) ๐ท
Returns a list of polygons in this cell. Slay.
Parameters
by_spec(bool) โ ifTrue, the return value is a dictionary with the polygons of each individual pair(layer, datatype).depth(integer or None) โ if notNone, this defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. Ifby_specisTruethe 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 (ifby_specisTrue).
Return type
list of array-like[N][2]ordictionary
Note: instances ofFlexPathandRobustPathare 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 notNone, 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 notNone, 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 FlexPathorRobustPath
get_labels(depth=None) ๐
Returns a list with a copy of the labels in this cell.
Parameters
depth(integer or None) โ if notNone, 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) โ ifTruereturns 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 notNone, all polygons will be transferred to the layer indicated by this number.single_datatype(integer or None) โ if notNone, all polygons will be transferred to the datatype indicated by this number.single_datatypeโ if notNone, 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(Cellor 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) โ ifTruethe reference is reflected parallel to the x direction before being rotated.ignore_missing(bool) โ ifFalsea warning is issued when the referenced cell is not found. (SoTrue= "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) โ ifTrue, 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) โ ifTrue, the return value is a dictionary with the polygons of each individual pair(layer, datatype).depth(integer or None) โ if notNone, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. Ifby_specisTruethe 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 (whenby_specisTrue).
Return type: list of array-like[N][2] or dictionary
โจ Note:FlexPathandRobustPathinstances 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 notNone, 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 notNone, 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 notNone, 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]], orNoneif 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) โ ifTrue, the reference is reflected parallel to the x direction before being rotated.ignore_missing(bool) โ ifFalsea 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) โ ifTrue, 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) โ ifTrue, the return value is a dictionary with the polygons of each individual pair(layer, datatype).depth(integer or None) โ if notNone, defines from how many reference levels to retrieve polygons. References below this level will result in a bounding box. Ifby_specisTruethe 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 (whenby_specisTrue).
Return type: list of array-like[N][2] or dictionary
๐ Note:FlexPathandRobustPathinstances 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 notNone, 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 notNone, 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 notNone, 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]], orNoneif 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 ininfile.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 toread_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) โ ifTruean existing cell with the same name in the library will be overwritten.
Returns
outโ this object.
Return type: GdsLibrary
Notes
CellReferenceorCellArrayinstances 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'sNone, then all cells are used.timestamp(datetime object) โ sets the GDSII timestamp. IfNone, it just uses the current time. clocking in โฐbinary_cells(iterable of bytes) โ an iterable with binary data for GDSII cells (fromget_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 therenamedictionary. 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) โ ifTrue, an existing cell with the same name in the current global library will be overwritten.
Returns
outโ the extracted cell.
Return type
Cell
Notes
CellReferenceorCellArrayinstances 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. IfNone, 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. IfNone, 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 (fromget_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. IfNone, 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. IfNone, the current time is used.binary_cells(iterable of bytes) โ iterable with binary data for GDSII cells (fromget_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 methodsupdateandhexdigestas defined in thehashlibmodule. IfNone, the defaulthashlib.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
๐ Copyright
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
- ubuntu resolute: gdspy-doc 1.4.2-2build12 ยท Boost-1.0
- ubuntu stonking: gdspy-doc 1.4.2-2build12 ยท Boost-1.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms