File: makeimages.py

package info (click to toggle)
gdspy 1.4.2-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 2,020 kB
  • sloc: python: 9,180; cpp: 4,716; sh: 39; makefile: 20
file content (443 lines) | stat: -rw-r--r-- 15,588 bytes parent folder | download
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
import gdspy
import numpy
import colorsys
from PIL import Image, ImageDraw, ImageFont


class ColorDict(dict):
    def __missing__(self, key):
        layer, datatype = key
        rgb = tuple(
            int(255 * c + 0.5)
            for c in colorsys.hsv_to_rgb(
                (layer % 3) / 3.0 + (layer % 6 // 3) / 6.0 + (layer // 6) / 11.0,
                1 - ((layer + datatype) % 8) / 12.0,
                1 - (datatype % 3) / 4.0,
            )
        )
        self[key] = rgb
        return rgb


color = ColorDict()


def draw(cell, name=None, width=600, height=400, margin=20):
    global color
    (ax, ay), (bx, by) = cell.get_bounding_box()
    ax = min(0, ax)
    ay = min(0, ay)
    bx = max(1, bx)
    by = max(1, by)
    sx = 3 * width - 2 * margin
    sy = 3 * height - 2 * margin
    bx -= ax
    by -= ay
    if bx * sy > by * sx:
        scale = sx / bx
        sy = int(sx * by / bx + 0.5)
    else:
        scale = sy / by
        sx = int(sy * bx / by + 0.5)
    ox = margin
    oy = sy + margin
    width = int((sx + 2 * margin) / 3 + 0.5)
    height = int((sy + 2 * margin) / 3 + 0.5)
    img = Image.new("RGBA", (3 * width, 3 * height), (0, 0, 0, 0))
    for key, polys in cell.get_polygons(by_spec=True).items():
        lc = color[key]
        fc = color[key] + (128,)
        for p in polys:
            p[:, 0] = ox + scale * (p[:, 0] - ax)
            p[:, 1] = oy - scale * (p[:, 1] - ay)
            pts = list(p.flatten())
            tmp = Image.new("RGBA", img.size, (0, 0, 0, 0))
            dr = ImageDraw.Draw(tmp)
            dr.polygon(pts, fill=fc, outline=lc)
            img = Image.alpha_composite(img, tmp)
    z = ox - scale * ax, oy + scale * ay
    p = ox + scale * (1 - ax), oy - scale * (1 - ay)
    # n = ox + scale * (-1 - ax), oy - scale * (-1 - ay)
    dr = ImageDraw.Draw(img)
    dr.line([z[0], z[1], p[0], z[1]], fill=(0, 0, 0, 255), width=3)
    dr.line([z[0], z[1], z[0], p[1]], fill=(0, 0, 0, 255), width=3)
    labels = cell.get_labels()
    if len(labels) > 0:
        font = ImageFont.truetype("/user/share/fonts/TTF/DejaVuSans.ttf", 72)
        for l in labels:
            x = ox + scale * (l.position[0] - ax)
            y = oy - scale * (l.position[1] - ay)
            dr.text((x, y), l.text, font=font, fill=color[(l.layer, l.texttype)])
    img = img.resize((width, height), Image.ANTIALIAS)
    name = "docs/_static/" + (cell.name if name is None else name) + ".png"
    print("Saving", name)
    img.save(name)


if __name__ == "__main__":
    # Polygons
    # 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)
    draw(gdspy.Cell("polygons").add(poly))

    # Holes
    # 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)]
    )
    draw(gdspy.Cell("holes").add(cutout))

    # Circles
    # 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,
    )
    draw(gdspy.Cell("circles").add([circle, ellipse, arc]))

    # Curves
    # 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())
    draw(gdspy.Cell("curves").add([p1, p2]))

    # Curves 1
    # 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())
    draw(gdspy.Cell("curves_1").add(p3))

    # Curves 2
    # 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())
    draw(gdspy.Cell("curves_2").add([p4, p5, p6]))

    # Transformations
    poly = gdspy.Rectangle((-2, -2), (2, 2))
    poly.rotate(numpy.pi / 4)
    poly.scale(1, 0.5)
    draw(gdspy.Cell("transformations").add(poly))

    # Layer and Datatype
    # 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)
    draw(gdspy.Cell("layer_and_datatype").add([p1, p2, p3, p4]))

    # References
    # 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)))
    draw(main, "references")

    # Polygonal-Only Paths
    # 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")
    draw(gdspy.Cell("polygonal-only_paths").add(path1))

    # Polygonal-Only Paths 1
    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)
    draw(gdspy.Cell("polygonal-only_paths_1").add(path2))

    # Polygonal-Only Paths 2
    # 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)

    draw(gdspy.Cell("polygonal-only_paths_2").add(path3))

    # Flexible Paths
    # 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)
    draw(gdspy.Cell("flexible_paths").add([sp1, sp2]))

    # Flexible Paths 1
    # 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)
    draw(gdspy.Cell("flexible_paths_1").add(sp3))

    # Flexible Paths 2
    # 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)
    draw(gdspy.Cell("flexible_paths_2").add([sp4, sp5]))

    # Robust Paths
    # 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,
        ],
    )
    draw(gdspy.Cell("robust_paths").add(lp))

    # Boolean Operations
    # 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")
    draw(gdspy.Cell("boolean_operations").add(inv))

    # Slice Operation
    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])

    draw(slices, "slice_operation")

    # Offset Operation
    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)
    draw(gdspy.Cell("offset_operation").add([outer, rect1, rect2]))

    # Fillet Operation
    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)
    draw(gdspy.Cell("fillet_operation").add([joined, multi_path]))

    # Text
    # 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)
    draw(gdspy.Cell("text").add([htext, vtext, label, rect]))