Examples¶
Tutorial¶
The following code is a sample to quickly demonstrate some features of gdspy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | ######################################################################
# #
# Copyright 2009-2018 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
print('Using gdspy module version ' + gdspy.__version__)
# ------------------------------------------------------------------ #
# POLYGONS
# ------------------------------------------------------------------ #
# First we need a cell to add the polygons to.
poly_cell = gdspy.Cell('POLYGONS')
# We define the polygon through its vertices.
points = [(0, 0), (2, 2), (2, 6), (-6, 6), (-6, -6), (-4, -4), (-4, 4), (0, 4)]
# Create the polygon on layer 1.
poly1 = gdspy.Polygon(points, 1)
# Add the new polygon to the cell.
poly_cell.add(poly1)
# Create another polygon from the same set of points, but rotate it
# 180 degrees and add it to the cell.
poly2 = gdspy.Polygon(points, 1).rotate(numpy.pi)
poly_cell.add(poly2)
# To create rectangles we don't need to give the 4 corners, only 2.
# Note that we don't need to create a variable if we are not going to
# use it, just add the rectangle directly to the cell. Create a
# rectangle in layer 2.
poly_cell.add(gdspy.Rectangle((18, 1), (22, 2), 2))
# There are no circles in the GDSII specification, so rounded shapes
# are actually many-sided polygons. Create a circle in layer 2,
# centered at (27, 2), and with radius 2.
poly_cell.add(gdspy.Round((27, 2), 2, layer=2))
# The Round class is quite versatile: it provides circles, pie slices,
# rings and ring sections, like this one in layer 2.
poly_cell.add(
gdspy.Round(
(23.5, 7),
15,
inner_radius=14,
initial_angle=-2.0 * numpy.pi / 3.0,
final_angle=-numpy.pi / 3.0,
layer=2))
# ------------------------------------------------------------------ #
# PATHS
# ------------------------------------------------------------------ #
path_cell = gdspy.Cell('PATHS')
# Start a path from the origin with width 1.
path1 = gdspy.Path(1, (0, 0))
# Add a straight segment to the path in layer 1, datatype 1, with length
# 3, going in the '+x' direction. Since we'll use this layer/datatype
# configuration again, we can setup a dict containing this info.
spec = {'layer': 1, 'datatype': 1}
path1.segment(3, '+x', **spec)
# Add a curve to the path by specifying its radius as 2 and its initial
# and final angles.
path1.arc(2, -numpy.pi / 2.0, numpy.pi / 6.0, **spec)
# Add another segment to the path in layer 1, with length 4 and
# pointing in the direction defined by the last piece we added above.
path1.segment(4, **spec)
# Add a curve using the turn command. We specify the radius 2 and
# turning angle. The agnle can also be specified with 'l' and 'r' for
# left and right turns of 90 degrees, or 'll' and 'rr' for 180 degrees.
path1.turn(2, -2.0 * numpy.pi / 3.0, **spec)
# Final piece of the path. Add a straight segment and tapper the path
# width from the original 1 to 0.5.
path1.segment(3, final_width=0.5, **spec)
path_cell.add(path1)
# We can also create parallel paths simultaneously. Start 2 paths with
# width 0.5 each,nd pitch 1, originating where our last path ended.
path2 = gdspy.Path(0.5, (path1.x, path1.y), number_of_paths=2, distance=1)
# Add a straight segment to the paths gradually increasing their
# distance to 1.5, in the direction in which the last path ended.
spec['layer'] = 2
path2.segment(3, path1.direction, final_distance=1.5, **spec)
# Path commands can be concatenated. Add a turn and a tapper segment
# in one expression, followed by a final turn.
path2.turn(2, -2.0 * numpy.pi / 3.0, **spec).segment(
4, final_distance=1, **spec)
path2.turn(4, numpy.pi / 6.0, **spec)
path_cell.add(path2)
# Create another single path 0.5 wide, starting where the path above
# ended, and add to it a line segment in the 3rd layer in the '-y'
# direction.
path3 = gdspy.Path(0.5, (path2.x, path2.y))
path3.segment(1, '-y', layer=3)
# We can create paths based on parametric curves. First we need to
# define the curve function, with 1 argument. This argument will vary
# from 0 to 1 and the return value should be the (x, y) coordinates of
# the path. This could be a lambda-expression if the function is
# simple enough. We will create a spiral path. Note that the function
# returns (0, 0) when t=0, so that our path is connected.
def spiral(t):
r = 4 - 3 * t
theta = 5 * t * numpy.pi
x = 4 - r * numpy.cos(theta)
y = -r * numpy.sin(theta)
return (x, y)
# We can also create the derivative of the curve to pass to out path
# path member, otherwise it will be numerically calculated. In the
# spiral case we don't want the exact derivative, but the derivative of
# the spiral as if its radius was constant. This will ensure that our
# path is connected at the start (geometric problem of this kind of
# spiral).
def dspiral_dt(t):
theta = 5 * t * numpy.pi
dx_dt = numpy.sin(theta)
dy_dt = -numpy.cos(theta)
return (dx_dt, dy_dt)
# Add the parametric spiral to the path in layer 3. Note that we can
# still tapper the width (linearly or with a taper function). To make
# the curve smoother, we increase the number of evaluations of the
# function (fracture will be performed automatically to ensure polygons
# with less than 200 points).
path3.parametric(
spiral,
dspiral_dt,
final_width=lambda t: 0.1 + abs(0.4 * (1 - 2 * t)**3),
number_of_evaluations=600,
layer=3)
path_cell.add(path3)
# Polygonal paths are defined by the points they pass through. The
# width of the path can be given as a number, representing the path
# width along is whole extension, or as a list, where each element is
# the width of the path at one point. Our path will have width 0.5 in
# all points, except the last, where it will tapper up to 1.5. More
# than 1 path can be defined in parallel as well (useful for buses).
# The distance between the paths work the same way as the width: it's
# either a constant number, or a list. We create 5 parallel paths that
# are larger and further apart on the last point. The paths are put in
# layers 4 and 5. Since we have 5 paths, the list of layers will be
# run more than once, so the 5 paths will actually be in layers 4, 5, 4,
# 5, and 4.
points = [(20, 12), (24, 8), (24, 4), (24, -2)]
widths = [0.5] * (len(points) - 1) + [1.5]
distances = [0.8] * (len(points) - 1) + [2.4]
polypath = gdspy.PolyPath(
points, widths, number_of_paths=5, distance=distances, layer=[4, 5])
# We can round the corners of any Polygon or PolygonSet with the fillet
# method. Here we use a radius of 0.2.
# polypath.fillet(0.2)
path_cell.add(polypath)
# L1Paths use only segments in 'x' and 'y' directions, useful for some
# lithography mask writers. We specify a path composed of 16 segments
# of length 4. The turns after each segment can be either 90 degrees
# CCW (positive) or CW (negative). The absolute value of the turns
# produces a scaling of the path width and distance between paths in
# segments immediately after the turn.
lengths = [4] * 8
turns = [-1, -1, 1, 1, -1, -2, 1, 0.5]
l1path = gdspy.L1Path(
(-1, -11),
'+y',
0.5,
lengths,
turns,
number_of_paths=3,
distance=0.7,
layer=6)
path_cell.add(l1path)
# ------------------------------------------------------------------ #
# POLYGON OPERATIONS
# ------------------------------------------------------------------ #
# Boolean operations can be executed with either gdspy polygons or
# point lists). The operations are union, intersection, subtraction,
# symmetric subtracion (respectively 'or', 'and', 'not', 'xor').
oper_cell = gdspy.Cell('OPERATIONS')
# Here we subtract the previously created spiral from a rectangle with
# the 'not' operation.
oper_cell.add(
gdspy.fast_boolean(
gdspy.Rectangle((10, -4), (17, 4)), path3, 'not', layer=1))
# Polygon offset (inset and outset) can be used, for instance, to
# define safety margins around shapes.
spec = {'layer': 7}
path4 = gdspy.Path(0.5, (21, -5)).segment(3, '+x', **spec)\
.turn(4, 'r', **spec).turn(4, 'rr', **spec)\
.segment(3, **spec)
oper_cell.add(path4)
# Merge all parts into a single polygon.
merged = gdspy.fast_boolean(path4, None, 'or', max_points=0)
# Offset the path shape by 0.5 and add it to the cell.
oper_cell.add(gdspy.offset(merged, 1, layer=8))
# ------------------------------------------------------------------ #
# SLICING POLYGONS
# ------------------------------------------------------------------ #
# If there is the need to cut a polygon or set of polygons, it's better
# to use the slice function than set up a boolean operation, since it
# runs much faster. Slices are multiple cuts perpendicular to an axis.
slice_cell = gdspy.Cell('SLICE')
original = gdspy.Round((0, 0), 10, inner_radius=5)
# Slice the original ring along x = -7 and x = 7.
result = gdspy.slice(original, [-7, 7], 0, layer=1)
# The result is a tuple of polygon sets, one for each slice. To keep
# add the region between our 2 cuts, we chose result[1].
slice_cell.add(result[1])
# If the cut needs to be at an angle we can rotate the geometry, slice
# it, and rotate back.
original = gdspy.PolyPath([(12, 0), (12, 8), (28, 8), (28, -8), (12, -8),
(12, 0)], 1, 3, 2)
original.rotate(numpy.pi / 3, center=(20, 0))
result = gdspy.slice(original, 7, 1, layer=2)
result[0].rotate(-numpy.pi / 3, center=(20, 0))
slice_cell.add(result[0])
# ------------------------------------------------------------------ #
# REFERENCES AND TEXT
# ------------------------------------------------------------------ #
# Cells can contain references to other cells.
ref_cell = gdspy.Cell('REFS')
ref_cell.add(gdspy.CellReference(poly_cell, (0, 30), x_reflection=True))
ref_cell.add(gdspy.CellReference(poly_cell, (25, 0), rotation=180))
# References can be whole arrays. Add an array of the operations cell
# with 2 lines and 3 columns and 1st element at (25, 10).
ref_cell.add(
gdspy.CellArray('OPERATIONS', 3, 2, (35, 30), (25, 10), magnification=1.5))
# Text are also sets of polygons. They have edges parallel to 'x' and
# 'y' only.
ref_cell.add(
gdspy.Text(
'Created with gsdpy ' + gdspy.__version__, 7, (-7, -35), layer=6))
# Labels are special text objects which don't define any actual
# geometry, but can be used to annotate the drawing. Rotation,
# magnification and reflection of the text are not supported by the
# included GUI, but they are included in the resulting GDSII file.
ref_cell.add(
gdspy.Label(
'Created with gdspy ' + gdspy.__version__, (-7, -36), 'nw', layer=6))
# ------------------------------------------------------------------ #
# Translation
# ------------------------------------------------------------------ #
trans_cell = gdspy.Cell('TRANS')
# Any geometric object can be translated by providing the distance to
# translate in the x-direction and y-direction: translate(dx, dy)
rect1 = gdspy.Rectangle((80, 0), (81, 1), 1)
rect1.translate(2, 0)
trans_cell.add(rect1)
# Translatable objects can also be copied & translated in the same way.
rect2 = gdspy.Rectangle((80, 0), (81, 1), 2)
rect3 = gdspy.copy(rect2, 0, 3)
trans_cell.add(rect2)
trans_cell.add(rect3)
# Reference Cells are also translatable, and thus copyable.
ref1 = gdspy.CellReference(poly_cell, (25, 0), rotation=180)
ref2 = gdspy.copy(ref1, 30, 30)
trans_cell.add(ref1)
trans_cell.add(ref2)
# Same goes for Labels & Text
text1 = gdspy.Text(
'Created with gdspy ' + gdspy.__version__, 7, (-7, -35), layer=6)
text2 = gdspy.copy(text1, 0, -20)
label1 = gdspy.Label(
'Created with gdspy ' + gdspy.__version__, (-7, -36), 'nw', layer=6)
label2 = gdspy.copy(label1, 0, -20)
trans_cell.add(text1)
trans_cell.add(text2)
trans_cell.add(label1)
trans_cell.add(label2)
# ------------------------------------------------------------------ #
# OUTPUT
# ------------------------------------------------------------------ #
# Output the layout to a GDSII file (default to all created cells).
# Set the units we used to micrometers and the precision to nanometers.
gdspy.write_gds('tutorial.gds', unit=1.0e-6, precision=1.0e-9)
# ------------------------------------------------------------------ #
# IMPORT
# ------------------------------------------------------------------ #
# Import the file we just created, and extract the cell 'POLYGONS'. To
# avoid naming conflict, we will rename all cells.
gdsii = gdspy.GdsLibrary()
gdsii.read_gds(
'tutorial.gds',
rename={
'POLYGONS': 'IMPORT_POLY',
'PATHS': 'IMPORT_PATHS',
'OPERATIONS': 'IMPORT_OPER',
'SLICE': 'IMPORT_SLICE',
'REFS': 'IMPORT_REFS',
'TRANS': 'IMPORT_TRANS'
},
layers={
1: 7,
2: 8,
3: 9
})
# Now we extract the cells we want to actually include in our current
# structure. Note that the referenced cells will be automatically
# extracted as well.
gdsii.extract('IMPORT_REFS')
# ------------------------------------------------------------------ #
# VIEWER
# ------------------------------------------------------------------ #
# View the layout using a GUI. Full description of the controls can
# be found in the online help at http://gdspy.sourceforge.net/
gdspy.LayoutViewer()
|
Integrated Photonics¶
This example demonstrates the use of gdspy primitives to create more complex structures.
These structures are commonly used in the field of integrated photonics.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | ######################################################################
# #
# Copyright 2009-2018 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 waveguide(path,
points,
finish,
bend_radius,
number_of_points=0.01,
direction=None,
layer=0,
datatype=0):
'''
Easy waveguide creation tool with absolute positioning.
path : starting `gdspy.Path`
points : coordinates along which the waveguide will travel
finish : end point of the waveguide
bend_radius : radius of the turns in the waveguide
number_of_points : same as in `path.turn`
direction : starting direction
layer : GDSII layer number
datatype : GDSII datatype number
Return `path`.
'''
if direction is not None:
path.direction = direction
axis = 0 if path.direction[1] == 'x' else 1
points.append(finish[(axis + len(points)) % 2])
n = len(points)
if points[0] > (path.x, path.y)[axis]:
path.direction = ['+x', '+y'][axis]
else:
path.direction = ['-x', '-y'][axis]
for i in range(n):
path.segment(
abs(points[i] - (path.x, path.y)[axis]) - bend_radius,
layer=layer,
datatype=datatype)
axis = 1 - axis
if i < n - 1:
goto = points[i + 1]
else:
goto = finish[axis]
if (goto > (path.x, path.y)[axis]) ^ ((path.direction[0] == '+') ^
(path.direction[1] == 'x')):
bend = 'l'
else:
bend = 'r'
path.turn(
bend_radius,
bend,
number_of_points=number_of_points,
layer=layer,
datatype=datatype)
return path.segment(
abs(finish[axis] - (path.x, path.y)[axis]),
layer=layer,
datatype=datatype)
def taper(path,
length,
final_width,
final_distance,
direction=None,
layer=0,
datatype=0):
'''
Linear tapers for the lazy.
path : `gdspy.Path` to append the taper
length : total length
final_width : final width of th taper
direction : taper direction
layer : GDSII layer number (int or list)
datatype : GDSII datatype number (int or list)
Parameters `layer` and `datatype` must be of the same type. If they
are lists, they must have the same length. Their length indicate the
number of pieces that compose the taper.
Return `path`.
'''
if layer.__class__ == datatype.__class__ == [].__class__:
assert len(layer) == len(datatype)
elif isinstance(layer, int) and isinstance(datatype, int):
layer = [layer]
datatype = [datatype]
else:
raise ValueError('Parameters layer and datatype must have the same '
'type (either int or list) and length.')
n = len(layer)
w = numpy.linspace(2 * path.w, final_width, n + 1)[1:]
d = numpy.linspace(path.distance, final_distance, n + 1)[1:]
l = float(length) / n
for i in range(n):
path.segment(
l, direction, w[i], d[i], layer=layer[i], datatype=datatype[i])
return path
def grating(period,
number_of_teeth,
fill_frac,
width,
position,
direction,
lda=1,
sin_theta=0,
focus_distance=-1,
focus_width=-1,
evaluations=99,
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
evaluations : number of evaluations of `path.parametric`
layer : GDSII layer number
datatype : GDSII datatype number
Return `PolygonSet`
'''
if focus_distance < 0:
path = 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)
path = gdspy.Path(period * fill_frac, position)
max_points = 199 if focus_width < 0 else 2 * evaluations
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
path.parametric(
lambda t: (width * t - w, (c1 + neff * numpy.sqrt(c2 - c3 * (
width * t - w)**2)) / c3),
number_of_evaluations=evaluations,
max_points=max_points,
layer=layer,
datatype=datatype)
path.x = position[0]
path.y = position[1]
if focus_width >= 0:
path.polygons[0] = numpy.vstack(
(path.polygons[0][:evaluations, :],
([position] if focus_width == 0 else
[(position[0] + 0.5 * focus_width, position[1]),
(position[0] - 0.5 * focus_width, position[1])])))
path.fracture()
if direction == '-x':
return path.rotate(0.5 * numpy.pi, position)
elif direction == '+x':
return path.rotate(-0.5 * numpy.pi, position)
elif direction == '-y':
return path.rotate(numpy.pi, position)
else:
return path
if __name__ == '__main__':
# Examples
c1 = gdspy.Cell('Example1')
# Waveguide starts at (0, 0)...
path = gdspy.Path(0.450, (0, 0))
# ... then starts in the +y direction up to y=200, then through x=500,
# and stops at (800, 400). All bends have radius 50.
waveguide(path, [200, 500], (800, 400), 50, direction='+y')
c1.add(path)
# More useful example including a taper and a grating:
c2 = gdspy.Cell('Example2')
for i in range(3):
path = gdspy.Path(0.120, (50 * i, 0))
taper(
path,
75,
0.450,
0,
'+y',
layer=list(range(5, 1, -1)),
datatype=list(range(1, 5)))
waveguide(
path, [300 - 20 * i], (500 + 50 * i, 425), 50, layer=1, datatype=1)
c2.add(path)
c2.add(
grating(
0.626,
28,
0.5,
19, (path.x, path.y),
path.direction,
1.55,
numpy.sin(numpy.pi * 8 / 180),
21.5,
2 * path.w,
layer=1,
datatype=1))
# Straight grating and positive resist example
c3 = gdspy.Cell('Example3')
spec = {'layer': 4, 'datatype': 3}
lda = 1.55
gr_width = 10
gr_per = 0.626
gr_teeth = 20
wg_clad = 4
wg_width = 0.45
tp_len = 700
c3.add(
grating(gr_per, gr_teeth, 0.5, gr_width, (gr_per * gr_teeth, 0), '-x',
lda, numpy.sin(numpy.pi * 8 / 180), **spec))
path = gdspy.Path(
wg_clad, (0, 0), number_of_paths=2, distance=gr_width + wg_clad)
path.segment(gr_per * gr_teeth, '+x', **spec)
taper(path, tp_len, wg_clad, wg_width + wg_clad, **spec)
waveguide(path, [800], (tp_len + gr_per * gr_teeth, 200), 50, 0.1, **spec)
taper(path, tp_len, wg_clad, gr_width + wg_clad, **spec)
c3.add(
grating(gr_per, gr_teeth, 0.5, gr_width, (path.x, path.y),
path.direction, lda, numpy.sin(numpy.pi * 8 / 180), **spec))
path.segment(gr_per * gr_teeth, **spec)
c3.add(path)
gdspy.LayoutViewer()
|