Skip to content

Commit 6ac2711

Browse files
authored
Merge branch 'HelgeGehring:master' into new_spiral
2 parents 1bca589 + c62cd98 commit 6ac2711

14 files changed

+481
-53
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
Changelog
22
=========
33

4-
Unreleased
4+
1.2.0
55
----------
6+
* Added meep integration
7+
* GDSII-export: Added support for LineStrings
8+
9+
1.1.4
10+
-----
611
* Waveguide: added add_left_bend and add_right_bend to make code easier readable
712
* added alphanumeric_to_id as an inverse of id_to_alphanumeric
13+
* allow to limit the numbers of workers for parallel export
14+
* fixed oasis export
815

916
1.1.3
1017
-----

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ The structures are organized in cells, which allow:
5656
* Automatized generation of region layers
5757
* Parallelized export
5858

59+
Additionally the structures can conveniently be simulated by:
60+
61+
* Using the meep integration (FDTD)
62+
5963
Finally, there are also different formats in which the pattern can be exported:
6064

6165
* The GDSII-format, which is quite often used for (electron beam/...)-lithography

docs/changelog/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. include:: ../../CHANGELOG.md

docs/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'sphinx.ext.imgmath',
2121
'matplotlib.sphinxext.plot_directive',
2222
'sphinx.ext.graphviz',
23+
'm2r2'
2324
]
2425

2526
plot_rcparams = {'savefig.bbox': 'tight'}

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ _________________
123123
parts/parts
124124
modifier/modifier
125125
api/modules
126+
changelog/changelog
126127

127128

128129
.. only:: html

docs/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ shapely
44
scipy
55
qrcode
66
descartes
7+
m2r2

gdshelpers/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111

1212
configuration = _Configuration()
1313

14-
__version__ = '1.1.3'
14+
__version__ = '1.2.0'

gdshelpers/export/gdsii_export.py

+25-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from io import BytesIO
1010
import numpy as np
1111

12-
from shapely.geometry import Polygon
12+
from shapely.geometry import Polygon, LineString
1313

1414

1515
def _real_to_8byte(value):
@@ -28,25 +28,31 @@ def _cell_to_gdsii_binary(cell, grid_steps_per_unit, max_points, max_line_points
2828
b.write(pack('>2H', 4 + len(name), 0x0606) + name.encode('ascii')) # STRNAME STRING cell_name
2929

3030
for layer, polygons in cell.get_fractured_layer_dict(max_points, max_line_points).items():
31-
for polygon in polygons:
32-
if not isinstance(polygon, Polygon):
31+
for shapely_object in polygons:
32+
if isinstance(shapely_object, Polygon):
33+
if shapely_object.interiors:
34+
raise AssertionError('GDSII only supports polygons without holes')
35+
coords = list(shapely_object.exterior.coords) + [shapely_object.exterior.coords[0]]
36+
b.write(pack('>8H', 4, 0x0800, # BOUNDARY NO_DATA
37+
6, 0x0D02, layer, # LAYER INTEGER_2 layer
38+
6, 0x0E02, layer)) # DATATYPE INTEGER_2 datatype
39+
elif isinstance(shapely_object, LineString):
40+
coords = shapely_object.coords
41+
b.write(pack('>8H', 4, 0x0900, # PATH NO_DATA
42+
6, 0x0D02, layer, # LAYER INTEGER_2 layer
43+
6, 0x0E02, layer)) # DATATYPE INTEGER_2 datatype
44+
else:
3345
import warnings
3446
warnings.warn(
35-
'Shapely object of type ' + str(type(polygon)) + ' not convertible to GDSII, skipping...')
47+
'Shapely object of type ' + str(
48+
type(shapely_object)) + ' not convertible to GDSII, skipping...')
3649
continue
37-
if polygon.interiors:
38-
raise AssertionError('GDSII only supports polygons without holes')
39-
xy = np.round(
40-
np.array(
41-
list(polygon.exterior.coords) + [polygon.exterior.coords[0]]) * grid_steps_per_unit).astype(
42-
'>i4')
43-
b.write(pack('>8H', 4, 0x0800, # BOUNDARY NO_DATA
44-
6, 0x0D02, layer, # LAYER INTEGER_2 layer
45-
6, 0x0E02, layer)) # DATATYPE INTEGER_2 datatype
50+
51+
xy = np.round(np.array(coords) * grid_steps_per_unit).astype('>i4')
4652
for start in range(0, xy.shape[0], 8191): # Split in Blocks of 8191 points
4753
stop = min(start + 8191, xy.shape[0])
4854
b.write(pack('>2H', 4 + 8 * (stop - start), 0x1003)) # XY INTEGER_4
49-
b.write(xy[start:stop].tobytes()) # coords of polygon
55+
b.write(xy[start:stop].tobytes()) # coords of shapely_object
5056
b.write(pack('>2H', 4, 0x1100)) # ENDEL NO_DATA
5157

5258
for ref in cell.cells:
@@ -62,7 +68,7 @@ def _cell_to_gdsii_binary(cell, grid_steps_per_unit, max_points, max_line_points
6268
if ref['magnification'] is not None:
6369
b.write(pack('>2H', 12, 0x1B05) + _real_to_8byte(ref['magnification'])) # MAG REAL_8
6470
if ref['angle'] is not None:
65-
b.write(pack('>2H', 12, 0x1C05) + _real_to_8byte(np.rad2deg(ref['angle']))) # ANGLE REAL_8
71+
b.write(pack('>2H', 12, 0x1C05) + _real_to_8byte(np.rad2deg(ref['angle']) % 360.)) # ANGLE REAL_8
6672
if aref:
6773
b.write(pack('>2H2h', 8, 0x1302, ref['columns'], ref['rows'])) # COLROW INTEGER_2 spacing
6874
b.write(pack('>2H', 28 if aref else 12, 0x1003) + np.round(
@@ -84,7 +90,7 @@ def _cell_to_gdsii_binary(cell, grid_steps_per_unit, max_points, max_line_points
8490

8591

8692
def write_cell_to_gdsii_file(outfile, cell, unit=1e-6, grid_steps_per_unit=1000, max_points=4000, max_line_points=4000,
87-
timestamp=None, parallel=False):
93+
timestamp=None, parallel=False, max_workers=None):
8894
name = 'gdshelpers_exported_library'
8995
grid_step_unit = unit / grid_steps_per_unit
9096
timestamp = datetime.datetime.now() if timestamp is None else timestamp
@@ -113,7 +119,7 @@ def add_cells_to_unique_list(start_cell):
113119
# UNITS REAL_8 1/grid_steps_per_unit grid_step_unit
114120
if parallel:
115121
from concurrent.futures import ProcessPoolExecutor
116-
with ProcessPoolExecutor() as pool:
122+
with ProcessPoolExecutor(max_workers=max_workers) as pool:
117123
num = len(cells)
118124
for binary in pool.map(_cell_to_gdsii_binary, cells, (grid_steps_per_unit,) * num, (max_points,) * num,
119125
(max_line_points,) * num, (timestamp,) * num):
@@ -141,6 +147,8 @@ def add_cells_to_unique_list(start_cell):
141147
sub_cell = Cell('sub_cell')
142148
sub_cell.add_to_layer(1, waveguide)
143149

150+
sub_cell.add_to_layer(3, LineString(((0, 0), (100, 100))))
151+
144152
device_cell.add_cell(sub_cell, origin=(10, 10), angle=np.pi / 2)
145153

146154
with open('gdsii_export.gds', 'wb') as file:

gdshelpers/geometry/chip.py

+22-32
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ def add_cell(self, cell, origin=(0, 0), angle: Optional[float] = None, columns=1
115115
:param rows: Number of rows
116116
:param spacing: Spacing between the cells, should be an array in the form [x_spacing, y_spacing]
117117
"""
118-
if cell.name in [cell_dict['cell'].name for cell_dict in self.cells]:
119-
import warnings
120-
warnings.warn(
118+
if cell.get_dlw_data() and cell.name in [cell_dict['cell'].name for cell_dict in self.cells]:
119+
raise ValueError(
121120
'Cell name "{cell_name:s}" added multiple times to {self_name:s}.'
122-
' Can be problematic for desc/dlw-files'.format(cell_name=cell.name, self_name=self.name))
121+
' This is not allowed for cells containing DLW data.'.format(
122+
cell_name=cell.name, self_name=self.name
123+
)
124+
)
123125
self.cells.append(
124126
dict(cell=cell, origin=origin, angle=angle, magnification=None, x_reflection=False, columns=columns,
125127
rows=rows, spacing=spacing))
@@ -190,10 +192,13 @@ def get_dlw_data(self):
190192
dlw_data = self.dlw_data.copy()
191193
for sub_cell in self.cells:
192194
cell, origin = sub_cell['cell'], sub_cell['origin']
193-
194195
for dlw_type, dlw_type_data in cell.get_dlw_data().items():
195196
for dlw_id, data in dlw_type_data.items():
196197
data = data.copy()
198+
if sub_cell['angle'] is not None:
199+
c, s = np.cos(sub_cell['angle']), np.sin(sub_cell['angle'])
200+
data['origin'] = np.array([[c, -s], [s, c]]).dot(data['origin'])
201+
data['angle'] += sub_cell['angle']
197202
data['origin'] = (np.array(origin) + data['origin']).tolist()
198203
if dlw_type not in dlw_data:
199204
dlw_data[dlw_type] = {}
@@ -292,7 +297,7 @@ def start_viewer(self):
292297
import gdspy
293298
gdspy.LayoutViewer(library=self.get_gdspy_lib(), depth=10)
294299

295-
def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=False):
300+
def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=False, max_workers=None):
296301
"""
297302
Exports the layout and creates an DLW-file, if DLW-features are used.
298303
@@ -305,6 +310,8 @@ def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=Fal
305310
:param parallel: Defines if parallelization is used (only supported in Python 3).
306311
Standard value will be changed to True in a future version.
307312
Deactivating can be useful for debugging reasons.
313+
:param max_workers: If parallel is True, this can be used to limit the number of parallel processes.
314+
This can be useful if you run into out-of-memory errors otherwise.
308315
"""
309316

310317
if library is not None:
@@ -317,8 +324,8 @@ def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=Fal
317324
elif name.endswith('.gds'):
318325
name = name[:-4]
319326
library = library or 'gdshelpers'
320-
elif name.endswith('.oasis'):
321-
name = name[:-6]
327+
elif name.endswith('.oas'):
328+
name = name[:-4]
322329
library = library or 'fatamorgana'
323330
elif name.endswith('.dxf'):
324331
name = name[:-4]
@@ -331,15 +338,16 @@ def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=Fal
331338
import shutil
332339

333340
with NamedTemporaryFile('wb', delete=False) as tmp:
334-
write_cell_to_gdsii_file(tmp, self, grid_steps_per_unit=grid_steps_per_micron, parallel=parallel)
341+
write_cell_to_gdsii_file(tmp, self, grid_steps_per_unit=grid_steps_per_micron, parallel=parallel,
342+
max_workers=max_workers)
335343
shutil.move(tmp.name, name + '.gds')
336344

337345
elif library == 'gdspy':
338346
import gdspy
339347

340348
if parallel:
341349
from concurrent.futures import ProcessPoolExecutor
342-
with ProcessPoolExecutor() as pool:
350+
with ProcessPoolExecutor(max_workers=max_workers) as pool:
343351
self.get_gdspy_cell(pool)
344352
else:
345353
self.get_gdspy_cell()
@@ -348,7 +356,7 @@ def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=Fal
348356
gdspy_cells = self.get_gdspy_lib().cell_dict.values()
349357
if parallel:
350358
from concurrent.futures import ProcessPoolExecutor
351-
with ProcessPoolExecutor() as pool:
359+
with ProcessPoolExecutor(max_workers=max_workers) as pool:
352360
binary_cells = pool.map(gdspy.Cell.to_gds, gdspy_cells, [grid_steps_per_micron] * len(gdspy_cells))
353361
else:
354362
binary_cells = map(gdspy.Cell.to_gds, gdspy_cells, [grid_steps_per_micron] * len(gdspy_cells))
@@ -360,31 +368,13 @@ def save(self, name=None, library=None, grid_steps_per_micron=1000, parallel=Fal
360368

361369
if parallel:
362370
from concurrent.futures import ProcessPoolExecutor
363-
with ProcessPoolExecutor() as pool:
371+
with ProcessPoolExecutor(max_workers=max_workers) as pool:
364372
cells = self.get_oasis_cells(grid_steps_per_micron, pool)
365373
else:
366374
cells = self.get_oasis_cells(grid_steps_per_micron)
367375

368376
layout.cells = [cells[0]] + list(set(cells[1:]))
369377

370-
# noinspection PyUnresolvedReferences
371-
def replace_names_by_ids(oasis_layout):
372-
name_id = {}
373-
for cell_id, cell in enumerate(oasis_layout.cells):
374-
if cell.name.string in name_id:
375-
raise RuntimeError(
376-
'Each cell name should be unique, name "' + cell.name.string + '" is used multiple times')
377-
name_id[cell.name.string] = cell_id
378-
cell.name = cell_id
379-
for cell in oasis_layout.cells:
380-
for placement in cell.placements:
381-
placement.name = name_id[placement.name.string]
382-
383-
oasis_layout.cellnames = {v: k for k, v in name_id.items()}
384-
385-
# improves performance for reading oasis file and workaround for fatamorgana-bug
386-
replace_names_by_ids(layout)
387-
388378
with open(name + '.oas', 'wb') as f:
389379
layout.write(f)
390380
elif library == 'ezdxf':
@@ -407,7 +397,7 @@ def save_desc(self, filename: str):
407397
"""
408398
if not filename.endswith('.desc'):
409399
filename += '.desc'
410-
with open(filename + '.desc', 'w') as f:
400+
with open(filename, 'w') as f:
411401
json.dump(self.get_desc(), f, indent=True)
412402

413403
def get_reduced_layer(self, layer: int):
@@ -547,7 +537,7 @@ def add_dlw_marker(self, label: str, layer: int, origin):
547537
self.add_to_layer(layer, DLWMarker(origin))
548538
self.add_to_layer(std_layers.parnamelayer1, Text(origin, 2, label, alignment='center-center'))
549539

550-
self.add_dlw_data('marker', label, {'origin': list(origin)})
540+
self.add_dlw_data('marker', label, {'origin': list(origin), 'angle': 0})
551541

552542
def add_dlw_taper_at_port(self, label: str, layer: int, port: Port, taper_length: float, tip_width=.01,
553543
with_markers=True):

gdshelpers/parts/waveguide.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def find_y(y):
280280
poly_path = np.concatenate([poly_path_1, poly_path_2[::-1, :]])
281281

282282
assert shapely.geometry.LineString(poly_path).is_simple, \
283-
'Outer lines of parameterized wg intersect. Try using lower bend radii or smaller a smaller wg'
283+
'Outer lines of parameterized wg intersect. Try using larger bend radii or smaller a smaller wg'
284284

285285
# Now add the shapely objects and do book keeping
286286
polygon = shapely.geometry.Polygon(poly_path)

gdshelpers/simulation/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)