/
chip.py
610 lines (518 loc) · 28.1 KB
/
chip.py
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
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
import itertools
import json
import numpy as np
from typing import List, Optional
from shapely.affinity import translate, rotate
from shapely.geometry import box
from gdshelpers.geometry.shapely_adapter import convert_to_layout_objs, bounds_union, transform_bounds
from gdshelpers.export.gdsii_export import write_cell_to_gdsii_file
from gdshelpers.geometry import geometric_union
from gdshelpers.parts.port import Port
import gdshelpers.helpers.layers as std_layers
class Cell:
def __init__(self, name: str):
"""
Creates a new Cell named `name` at `origin`
:param name: Name of the cell, needs to be unique
"""
self.name = name
self.cells = []
self.layer_dict = {}
self.dlw_data = {}
self.desc = {'dlw': self.dlw_data, 'desc': {}, 'ebl': []}
self.cell_gdspy = None
self.cell_oasis = None
self._bounds = None
# Only contains the bounds of items in `layer_dict`.
# Children cells have to be queried each time, since there is no way of knowing when they got changed.
# self._bounds is None if the bounds need to be recalculated or if the cell is empty (in which case
# recalculating them is cheap and we don't need to cache them)
@property
def bounds(self):
"""
The outer bounding box of the cell. Returns `None` if it is empty.
"""
return self.get_bounds(layers=None)
def get_bounds(self, layers: Optional[List[int]] = None):
"""
Calculates and returns the envelope for the given layers.
Returns `None` if it is empty.
"""
# Go through all layers
bounds = []
if layers is None and self._bounds is not None:
bounds += [self._bounds]
else:
for layer in layers or self.layer_dict.keys():
for geo in self.layer_dict.get(layer, []):
geo_bounds = geo.get_shapely_object().bounds if hasattr(geo, 'get_shapely_object') else geo.bounds
if geo_bounds != (): # Some shapely geometries (collections) can return empty bounds
bounds.append(geo_bounds)
# Cache envelope if we have the global envelope
if layers is None:
self._bounds = bounds_union(bounds) if len(bounds) > 0 else None
# Merge envelopes of children cells
for cell in self.cells:
cell_bounds = cell['cell'].get_bounds(layers)
if cell_bounds is not None:
bounds.append(transform_bounds(cell_bounds, cell['origin'], rotation=cell['angle'] or 0))
return bounds_union(bounds) if len(bounds) > 0 else None
@property
def size(self):
"""
Returns the size of the cell
"""
bounds = self.bounds
if bounds is None:
return 0, 0
else:
return bounds[2] - bounds[0], bounds[3] - bounds[1]
def add_to_layer(self, layer: int, *geometry):
"""
Adds a shapely geometry to a the layer
:param layer: id of the layer
:param geometry: shapely geometry
"""
self._bounds = None
if layer not in self.layer_dict:
self.layer_dict[layer] = []
self.layer_dict[layer] += geometry
def add_dlw_data(self, dlw_type, dlw_id, data):
"""
Adds data for 3D-hybrid-integration to the Cell. This is usually only done by using the Device
:param dlw_type: type of the represented object
:param dlw_id: id of the represented object
:param data: data of the object
"""
if dlw_type not in self.dlw_data:
self.dlw_data[dlw_type] = {}
if dlw_id in self.dlw_data[dlw_type]:
raise ValueError('ID "{:s}" already used'.format(dlw_id))
self.dlw_data[dlw_type][dlw_id] = data
def add_cell(self, cell, origin=(0, 0), angle: Optional[float] = None, columns=1, rows=1, spacing=None):
"""
Adds a Cell to this cell
:param cell: Cell to add
:param origin: position where to add the cell
:param angle: defines the rotation of the cell
:param columns: Number of columns
:param rows: Number of rows
:param spacing: Spacing between the cells, should be an array in the form [x_spacing, y_spacing]
"""
if cell.name in [cell_dict['cell'].name for cell_dict in self.cells]:
import warnings
warnings.warn(
'Cell name "{cell_name:s}" added multiple times to {self_name:s}.'
' Can be problematic for desc/dlw-files'.format(cell_name=cell.name, self_name=self.name))
self.cells.append(
dict(cell=cell, origin=origin, angle=angle, magnification=None, x_reflection=False, columns=columns,
rows=rows, spacing=spacing))
def add_region_layer(self, region_layer: int = std_layers.regionlayer, layers: Optional[List[int]] = None):
"""
Generate a region layer around all objects on `layers` and place it on layer `region_layer`.
If `layers` is None, all layers are used.
"""
self.add_to_layer(region_layer, box(*self.get_bounds(layers)))
def add_frame(self, padding=30., line_width=1., frame_layer: int = std_layers.framelayer, bounds=None):
"""
Generates a rectangular frame around the contents of the cell.
:param padding: Add a padding of the given value around the contents of the cell
:param line_width: Width of the frame line
:param frame_layer: Layer to put the frame on.
:param bounds: Optionally, an explicit extent in the form (min_x, min_y, max_x, max_y) can be passed to
the function. If `None` (default), the current extent of the cell will be chosen.
"""
padding = padding + line_width
bounds = bounds or self.bounds
frame = box(bounds[0] - padding, bounds[1] - padding, bounds[2] + padding, bounds[3] + padding)
frame = frame.difference(frame.buffer(-line_width))
self.add_to_layer(frame_layer, frame)
def add_ebl_marker(self, layer: int, marker):
"""
Adds an Marker to the layout
:param layer: layer on which the marker should be positioned
:param marker: marker, that should be added (from gdshelpers.parts.markers)
"""
self.add_to_layer(layer, marker)
self.desc['ebl'].append(list(marker.origin))
def add_ebl_frame(self, layer: int, frame_generator, bounds=None, **kwargs):
"""
Adds global markers to the layout
:param layer: layer on which the markers should be positioned
:param frame_generator: either a method, which returns a list of the markers, which should be added or the name
of a generator from the gdshelpers.geometry.ebl_frame_generators package
:param bounds: Optionally the bounds to use can be provided in the form (min_x, min_y, max_x, max_y). If None,
the standard cell bounds will be used.
:param kwargs: Parameters which are directly passed to the frame generator (other than the bounds parameter)
"""
from gdshelpers.geometry import ebl_frame_generators
frame_generator = frame_generator if callable(frame_generator) else getattr(ebl_frame_generators,
frame_generator)
bounds = bounds or self.bounds
for marker in frame_generator(bounds, **kwargs):
self.add_ebl_marker(layer, marker)
def add_to_desc(self, key, data):
"""
Adds data to the .desc-file can be used for any data related to the cells, e.g. the swept parameters/...
:param key: name of the entry
:param data: data which are added to the .desc-file, the data have to be serializable by json
"""
self.desc['desc'][key] = data
def get_dlw_data(self):
dlw_data = self.dlw_data.copy()
for sub_cell in self.cells:
cell, origin = sub_cell['cell'], sub_cell['origin']
for dlw_type, dlw_type_data in cell.get_dlw_data().items():
for dlw_id, data in dlw_type_data.items():
data = data.copy()
data['origin'] = (np.array(origin) + data['origin']).tolist()
if dlw_type not in dlw_data:
dlw_data[dlw_type] = {}
dlw_data[dlw_type][cell.name + '.' + dlw_id] = data
return dlw_data
def get_desc(self):
desc = self.desc.copy()
desc['cells'] = {cell['cell'].name: dict(offset=tuple(cell['origin']), angle=cell['angle'] or 0,
**cell['cell'].get_desc()) for cell in self.cells}
return desc
def get_fractured_layer_dict(self, max_points=4000, max_line_points=4000):
from gdshelpers.geometry.shapely_adapter import shapely_collection_to_basic_objs, fracture_intelligently
fractured_layer_dict = {}
for layer, geometries in self.layer_dict.items():
fractured_geometries = []
for geometry in geometries:
geometry = geometry.get_shapely_object() if hasattr(geometry, 'get_shapely_object') else geometry
if type(geometry) in [list, tuple]:
geometry = geometric_union(geometry)
geometry = shapely_collection_to_basic_objs(geometry)
geometry = itertools.chain(
*[fracture_intelligently(geo, max_points, max_line_points) for geo in geometry if not geo.is_empty])
fractured_geometries.append(geometry)
fractured_layer_dict[layer] = itertools.chain(*fractured_geometries)
return fractured_layer_dict
def get_gdspy_cell(self, executor=None):
import gdspy
if self.cell_gdspy is None:
self.cell_gdspy = gdspy.Cell(self.name)
for sub_cell in self.cells:
angle = np.rad2deg(sub_cell['angle']) if sub_cell['angle'] is not None else None
if sub_cell['columns'] == 1 and sub_cell['rows'] == 1 and not sub_cell['spacing']:
self.cell_gdspy.add(
gdspy.CellReference(sub_cell['cell'].get_gdspy_cell(executor), origin=sub_cell['origin'],
rotation=angle, magnification=sub_cell['magnification'],
x_reflection=sub_cell['x_reflection']))
else:
self.cell_gdspy.add(
gdspy.CellArray(sub_cell['cell'].get_gdspy_cell(executor), origin=sub_cell['origin'],
rotation=angle, magnification=sub_cell['magnification'],
x_reflection=sub_cell['x_reflection'], columns=sub_cell['columns'],
rows=sub_cell['rows'], spacing=sub_cell['spacing']))
for layer, geometries in self.layer_dict.items():
for geometry in geometries:
if executor:
executor.submit(convert_to_layout_objs, geometry, layer, library='gdspy') \
.add_done_callback(lambda future: self.cell_gdspy.add(future.result()))
else:
self.cell_gdspy.add(convert_to_layout_objs(geometry, layer, library='gdspy'))
return self.cell_gdspy
def get_oasis_cells(self, grid_steps_per_micron=1000, executor=None):
import fatamorgana
import fatamorgana.records
if self.cell_oasis is None:
self.cell_oasis = fatamorgana.Cell(fatamorgana.NString(self.name))
for sub_cell in self.cells:
x, y = sub_cell['origin']
x, y = round(x * grid_steps_per_micron), round(y * grid_steps_per_micron)
angle = np.rad2deg(sub_cell['angle']) if sub_cell['angle'] is not None else None
repetition = None
if not (sub_cell['columns'] == 1 and sub_cell['rows'] == 1 and not sub_cell['spacing']):
repetition = fatamorgana.basic.GridRepetition(
[round(sub_cell['spacing'][0] * grid_steps_per_micron), 0],
sub_cell['columns'],
[0, round(sub_cell['spacing'][1] * grid_steps_per_micron)],
sub_cell['rows'])
self.cell_oasis.placements.append(
fatamorgana.records.Placement(False, name=fatamorgana.NString(sub_cell['cell'].name), x=x, y=y,
angle=angle, repetition=repetition))
for layer, geometries in self.layer_dict.items():
for geometry in geometries:
if executor:
executor.submit(convert_to_layout_objs, geometry, layer, library='oasis',
grid_steps_per_micron=grid_steps_per_micron, max_points=np.inf,
max_points_line=np.inf) \
.add_done_callback(lambda future: self.cell_oasis.geometry.extend(future.result()))
else:
self.cell_oasis.geometry.extend(
convert_to_layout_objs(geometry, layer, library='oasis',
grid_steps_per_micron=grid_steps_per_micron,
max_points=np.inf, max_points_line=np.inf))
return [self.cell_oasis] + [oasis_cell for sub_cell in self.cells for oasis_cell in
sub_cell['cell'].get_oasis_cells(grid_steps_per_micron, executor)]
def get_gdspy_lib(self):
import gdspy
self.get_gdspy_cell()
return gdspy.current_library
def start_viewer(self):
import gdspy
gdspy.LayoutViewer(library=self.get_gdspy_lib(), depth=10)
def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=False):
"""
Exports the layout and creates an DLW-file, if DLW-features are used.
:param name: The filename of the saved file. The ending of the filename defines the format.
Currently .gds, .oasis and .dxf are supported.
:param library: Name of the used library.
Should stay `None` in order to select the library depending on the file-ending.
The use of this parameter is deprecated and this parameter will be removed in a future release.
:param grid_steps_per_micron: Defines the resolution
:param parallel: Defines if parallelization is used (only supported in Python 3).
Standard value will be changed to True in a future version.
Deactivating can be useful for debugging reasons.
"""
if library is not None:
import warnings
warnings.warn('The use of the `library` parameter is deprecated. '
'Instead, define the format by the file ending of the filename.', DeprecationWarning)
if not name:
name = self.name
elif name.endswith('.gds'):
name = name[:-4]
library = library or 'gdshelpers'
elif name.endswith('.oasis'):
name = name[:-6]
library = library or 'fatamorgana'
elif name.endswith('.dxf'):
name = name[:-4]
library = library or 'ezdxf'
library = library or 'gdshelpers'
if library == 'gdshelpers':
from tempfile import NamedTemporaryFile
import shutil
with NamedTemporaryFile('wb', delete=False) as tmp:
write_cell_to_gdsii_file(tmp, self, grid_steps_per_unit=grid_steps_per_micron, parallel=parallel)
shutil.move(tmp.name, name + '.gds')
elif library == 'gdspy':
import gdspy
if parallel:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
self.get_gdspy_cell(pool)
else:
self.get_gdspy_cell()
self.get_gdspy_lib().precision = self.get_gdspy_lib().unit / grid_steps_per_micron
gdspy_cells = self.get_gdspy_lib().cell_dict.values()
if parallel:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
binary_cells = pool.map(gdspy.Cell.to_gds, gdspy_cells, [grid_steps_per_micron] * len(gdspy_cells))
else:
binary_cells = map(gdspy.Cell.to_gds, gdspy_cells, [grid_steps_per_micron] * len(gdspy_cells))
self.get_gdspy_lib().write_gds(name + '.gds', cells=[], binary_cells=binary_cells)
elif library == 'fatamorgana':
import fatamorgana
layout = fatamorgana.OasisLayout(grid_steps_per_micron)
if parallel:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as pool:
cells = self.get_oasis_cells(grid_steps_per_micron, pool)
else:
cells = self.get_oasis_cells(grid_steps_per_micron)
layout.cells = [cells[0]] + list(set(cells[1:]))
# noinspection PyUnresolvedReferences
def replace_names_by_ids(oasis_layout):
name_id = {}
for cell_id, cell in enumerate(oasis_layout.cells):
if cell.name.string in name_id:
raise RuntimeError(
'Each cell name should be unique, name "' + cell.name.string + '" is used multiple times')
name_id[cell.name.string] = cell_id
cell.name = cell_id
for cell in oasis_layout.cells:
for placement in cell.placements:
placement.name = name_id[placement.name.string]
oasis_layout.cellnames = {v: k for k, v in name_id.items()}
# improves performance for reading oasis file and workaround for fatamorgana-bug
replace_names_by_ids(layout)
with open(name + '.oas', 'wb') as f:
layout.write(f)
elif library == 'ezdxf':
from gdshelpers.export.dxf_export import write_cell_to_dxf_file
with open(name + '.dxf', 'w') as f:
write_cell_to_dxf_file(f, self, grid_steps_per_micron, parallel=parallel)
else:
raise ValueError('library must be either "gdshelpers", "gdspy", "fatamorgana" or "ezdxf"')
dlw_data = self.get_dlw_data()
if dlw_data:
with open(name + '.dlw', 'w') as f:
json.dump(dlw_data, f, indent=True)
def save_desc(self, filename: str):
"""
Saves a description file for the layout. The file format is not final yet and might change in a future release.
:param filename: name of the file the description data will be written to
"""
if not filename.endswith('.desc'):
filename += '.desc'
with open(filename + '.desc', 'w') as f:
json.dump(self.get_desc(), f, indent=True)
def get_reduced_layer(self, layer: int):
"""
Returns a single shapely object containing the structures on a certain layer from this cell and all added cells.
:param layer: the layer whose structures will be returned
:return: a single shapely-geometry
"""
def translate_and_rotate(geometry, offset, angle):
if not geometry:
return geometry
return translate(rotate(geometry, angle if angle else 0, use_radians=True, origin=(0, 0)), *offset)
return geometric_union(
(self.layer_dict[layer] if layer in self.layer_dict else []) +
[translate_and_rotate(cell['cell'].get_reduced_layer(layer), cell['origin'], cell['angle'])
for cell in self.cells])
def export_mesh(self, filename: str, layer_defs):
"""
Saves the current geometry as a mesh-file.
:param filename: Name of the file which will be created. The file ending determines the format.
:param layer_defs: Definition of the layers, should be a list like [(layer,(z_min,z_max)),...]
"""
from functools import reduce
from trimesh.primitives import Extrusion
from trimesh.transformations import translation_matrix
reduce(lambda a, b: a + b, (Extrusion(polygon=geometry, height=min_max[1] - min_max[0],
transform=translation_matrix((0, 0, min_max[0])))
for layer, min_max in layer_defs.items()
for geometry in (lambda x: x if hasattr(x, '__iter__') else [x, ])(
self.get_reduced_layer(layer)))).export(filename)
def get_patches(self, origin=(0, 0), angle_sum=0, angle=0, layers: Optional[List[int]] = None):
from descartes import PolygonPatch
def rotate_pos(pos, rotation_angle):
if rotation_angle is None:
return pos
c, s = np.cos(rotation_angle), np.sin(rotation_angle)
result = np.array([[c, -s], [s, c]]).dot(pos)
return result
own_patches = []
for layer, geometry in self.layer_dict.items():
if layers is not None and layer not in layers:
continue
geometry = geometric_union(geometry)
if geometry.is_empty:
continue
geometry = translate(rotate(geometry, angle_sum, use_radians=True, origin=(0, 0)), *origin)
own_patches.append(
PolygonPatch(geometry, color=['red', 'green', 'blue', 'teal', 'pink'][(layer - 1) % 5], linewidth=0))
sub_cells_patches = [p for cell_dict in self.cells for p in
cell_dict['cell'].get_patches(
np.array(origin) + rotate_pos(cell_dict['origin'], angle),
angle_sum=angle_sum + (cell_dict['angle'] or 0), angle=cell_dict['angle'],
layers=layers)]
return own_patches + sub_cells_patches
def save_image(self, filename: str, layers: Optional[List[int]] = None, antialiased=True, resolution=1.,
ylim=(None, None), xlim=(None, None), scale=1.):
"""
Save cell object as an image.
You can either use a rasterized file format such as png but also formats such as SVG or PDF.
:param filename: Name of the image file.
:param layers: Layers to show im the image
:param resolution: Rasterization resolution in GDSII units.
:param antialiased: Whether to use a anti-aliasing or not.
:param ylim: Tuple of (min_x, max_x) to export.
:param xlim: Tuple of (min_y, max_y) to export.
:param scale: Defines the scale of the image
"""
import matplotlib.pyplot as plt
# For vector graphics, map 1um to {resolution} mm instead of inch.
is_vector = filename.split('.')[-1] in ('svg', 'svgz', 'eps', 'ps', 'emf', 'pdf')
scale *= 5 / 127. if is_vector else 1.
fig, ax = plt.subplots()
for patch in self.get_patches(layers=layers):
patch.set_antialiased(antialiased)
ax.add_patch(patch)
# Autoscale, then change the axis limits and read back what is actually displayed
ax.autoscale(True, tight=True)
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
actual_ylim, actual_xlim = ax.get_ylim(), ax.get_xlim()
fig.set_size_inches(np.asarray((actual_xlim[1] - actual_xlim[0], actual_ylim[1] - actual_ylim[0])) * scale)
ax.set_aspect(1)
ax.axis('off')
fig.set_dpi(1 / resolution)
plt.savefig(filename, transparent=True, bbox_inches='tight', dpi=1 / resolution)
plt.close()
def show(self, layers: Optional[List[int]] = None, padding=5):
"""
Shows the current cell
:param layers: List of the layers to be shown, passing None shows all layers
:param padding: padding around the structure
"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
for patch in self.get_patches(layers=layers):
ax.add_patch(patch)
bounds = self.get_bounds(layers)
ax.set_xlim(bounds[0] - padding, bounds[2] + padding)
ax.set_ylim(bounds[1] - padding, bounds[3] + padding)
ax.set_aspect(1)
fig.show()
def add_dlw_marker(self, label: str, layer: int, origin):
"""
Adds a marker for 3D-hybrid integration
:param label: Name of the marker, needs to be unique within the device
:param layer: Layer at which the marker and markers should be written
:param origin: Position of the marker
"""
from gdshelpers.parts.marker import DLWMarker
from gdshelpers.parts.text import Text
self.add_to_layer(layer, DLWMarker(origin))
self.add_to_layer(std_layers.parnamelayer1, Text(origin, 2, label, alignment='center-center'))
self.add_dlw_data('marker', label, {'origin': list(origin)})
def add_dlw_taper_at_port(self, label: str, layer: int, port: Port, taper_length: float, tip_width=.01,
with_markers=True):
"""
Adds a taper for 3D-hybrid-integration at a certain port
:param label: Name of the port, needs to be unique within the device
:param layer: Layer at which the taper and markers should be written
:param port: Port to which the taper should be attached
:param taper_length: length of the taper
:param tip_width: final width of the tip
:param with_markers: for recognizing the taper markers near to the taper are necessary.
In certain designs the standard positions are not appropriate and
can therefore be disabled and manually added
"""
from gdshelpers.parts.text import Text
taper_port = port.longitudinal_offset(taper_length)
if taper_length > 0:
from gdshelpers.parts.waveguide import Waveguide
wg = Waveguide.make_at_port(port)
wg.add_straight_segment(taper_length, final_width=tip_width)
self.add_to_layer(layer, wg.get_shapely_object())
self.add_to_layer(std_layers.parnamelayer1, Text(taper_port.origin, 2, label, alignment='center-center'))
self.add_dlw_data('taper', str(label), {'origin': taper_port.origin.tolist(), 'angle': port.angle,
'starting_width': port.width, 'taper_length': taper_length})
if with_markers:
for i, (v, l) in enumerate(itertools.product((-20, 20), (taper_length, 0))):
self.add_dlw_marker(str(label) + '-' + str(i), layer,
port.parallel_offset(v).longitudinal_offset(l).origin)
if __name__ == '__main__':
from gdshelpers.parts.port import Port
from gdshelpers.parts.waveguide import Waveguide
# Create a cell-like object that offers a save output command '.save' which creates the .gds or .oas file by using
# gdspy or fatamorgana
device_cell = Cell('my_cell')
# Create a port to connect waveguide structures to
start_port = Port(origin=(0, 0), width=1, angle=0)
waveguide = Waveguide.make_at_port(start_port)
for i_bend in range(9):
waveguide.add_bend(angle=np.pi, radius=60 + i_bend * 40)
# Add direct laser writing taper and alignment marker for postprocessing with a dlw printer to the cell-like object.
# The cell dlw files will be saved with the cell.
device_cell.add_dlw_taper_at_port('A0', 1, start_port.inverted_direction, 30)
device_cell.add_dlw_taper_at_port('A1', 1, waveguide.current_port, 30)
device_cell.add_to_layer(1, waveguide)
device_cell.show()
device_cell.save_image('chip.pdf')
# Creates the output file by using gdspy or fatamorgana. To use the implemented parallel processing, set
# parallel=True.
device_cell.save(name='my_design', parallel=True)
device_cell.export_mesh('my_design.stl', layer_defs={1: (0, 1)})
array_cell = Cell('Array')
array_cell.add_cell(device_cell, rows=2, columns=2, spacing=(1000, 1000))
array_cell.save()