@@ -223,16 +223,19 @@ def _(
223223 splitter = import_gds (splitter_path , cellname = splitter_cell , rename_duplicated_cells = True )
224224 add_ports_from_markers_center (splitter , pin_layer = (1 , 10 ), port_layer = (1 , 0 ))
225225 auto_rename_ports_orientation (splitter )
226+ splitter .remove_layers (layers = [(1 , 10 ), (68 , 0 )], recursive = True )
226227 splitter .name = f"{ splitter_cell } _splitter"
227228
228229 combiner = import_gds (combiner_path , cellname = combiner_cell , rename_duplicated_cells = True )
229230 add_ports_from_markers_center (combiner , pin_layer = (1 , 10 ), port_layer = (1 , 0 ))
230231 auto_rename_ports_orientation (combiner )
232+ combiner .remove_layers (layers = [(1 , 10 ), (68 , 0 )], recursive = True )
231233 combiner .name = f"{ combiner_cell } _combiner"
232234
233235 gc = import_gds (gc_path , cellname = gc_cell , rename_duplicated_cells = True )
234236 add_ports_from_markers_center (gc , pin_layer = (1 , 10 ), port_layer = (1 , 0 ))
235237 auto_rename_ports_orientation (gc )
238+ gc .remove_layers (layers = [(68 , 0 )], recursive = True )
236239 gc .name = f"{ gc_cell } _gc"
237240
238241 mzi = gf .components .mzi (
@@ -257,166 +260,66 @@ def _(
257260 # 0 -------- 2
258261
259262 c = gf .Component ()
263+
260264 def _ports_list (component ):
261265 ports = component .ports
262266 return list (ports .values ()) if hasattr (ports , "values" ) else list (ports )
263267
264268 def _pick_gc_port (component ):
265269 ports = _ports_list (component )
266- optical = [p for p in ports if getattr (p , "port_type" , None ) in (None , "optical" )]
270+ optical = [
271+ p for p in ports if getattr (p , "port_type" , None ) in (None , "optical" )
272+ ]
267273 ports = optical or ports
268274 for port in ports :
269275 if port .orientation is not None and abs ((port .orientation - 180 ) % 360 ) < 1e-3 :
270276 return port
271277 return ports [0 ]
272278
273- def _port_with_orientation (port , orientation ):
274- if port .orientation is not None :
275- return port
276- return gf .Port (
277- name = port .name ,
278- center = port .center ,
279- width = port .width ,
280- orientation = orientation ,
281- layer = port .layer ,
282- port_type = port .port_type ,
283- )
284-
285- mzi_ref = c << mzi
286- mzi_ports = _ports_list (mzi_ref )
287- if hasattr (mzi_ref .ports , "get" ) and all (
288- name in mzi_ref .ports for name in ("o1" , "o2" , "o3" )
289- ):
290- mzi_input = mzi_ref .ports ["o1" ]
291- mzi_out_through = mzi_ref .ports ["o2" ]
292- mzi_out_cross = mzi_ref .ports ["o3" ]
279+ mzi_ports = _ports_list (mzi )
280+ if hasattr (mzi .ports , "get" ) and all (name in mzi .ports for name in ("o1" , "o2" , "o3" )):
281+ mzi_input = mzi .ports ["o1" ]
282+ mzi_out_through = mzi .ports ["o2" ]
283+ mzi_out_cross = mzi .ports ["o3" ]
293284 else :
294285 optical_ports = [
295- p
296- for p in mzi_ports
297- if getattr (p , "port_type" , None ) in (None , "optical" )
286+ p for p in mzi_ports if getattr (p , "port_type" , None ) in (None , "optical" )
298287 ]
299288 mzi_ports = optical_ports or mzi_ports
300289 mzi_input = min (mzi_ports , key = lambda p : p .center [0 ])
301290 mzi_outputs = [p for p in mzi_ports if p is not mzi_input ]
302- mzi_outputs = [p for p in mzi_outputs if p .orientation in (0 , None )]
303- if len (mzi_outputs ) < 2 :
304- max_x = max (p .center [0 ] for p in mzi_ports )
305- x_tol = 1.0
306- mzi_outputs = [
307- p for p in mzi_ports if abs (p .center [0 ] - max_x ) <= x_tol
308- ]
309- mzi_outputs = [p for p in mzi_outputs if p is not mzi_input ]
310291 mzi_outputs = sorted (mzi_outputs , key = lambda p : p .center [1 ])
311292 if len (mzi_outputs ) >= 2 :
312293 mzi_out_cross , mzi_out_through = mzi_outputs [0 ], mzi_outputs [- 1 ]
313294 else :
314295 mzi_out_through = mzi_outputs [0 ]
315296 mzi_out_cross = mzi_outputs [0 ]
316297
317- mzi_input = _port_with_orientation (mzi_input , 180 )
318- mzi_out_through = _port_with_orientation (mzi_out_through , 0 )
319- mzi_out_cross = _port_with_orientation (mzi_out_cross , 0 )
320-
321298 gc_port = _pick_gc_port (gc )
322- gc_pitch = 127.0
323- gc_offset_x = 120.0
324-
325- def place_gc_at (x : float , y : float ):
326- gc_ref = c << gc
327- port_name = gc_port .name
328- gc_ref_port = gc_ref .ports [port_name ]
329- if gc_ref_port .orientation is not None :
330- rotation = (180 - gc_ref_port .orientation ) % 360
331- if rotation :
332- gc_ref .rotate (rotation , center = gc_ref_port .center )
333- gc_ref_port = gc_ref .ports [port_name ]
334- gc_ref .move ((x - gc_ref_port .center [0 ], y - gc_ref_port .center [1 ]))
335- gc_ref_port = gc_ref .ports [port_name ]
336- gc_ref_port = _port_with_orientation (gc_ref_port , 180 )
337- return gc_ref , gc_ref_port
338-
339- mzi_bbox = (
340- mzi_ref .bbox ()
341- if callable (getattr (mzi_ref , "bbox" , None ))
342- else mzi_ref .bbox
343- )
344- if hasattr (mzi_bbox , "left" ):
345- mzi_xmin , mzi_ymin , mzi_xmax , mzi_ymax = (
346- mzi_bbox .left ,
347- mzi_bbox .bottom ,
348- mzi_bbox .right ,
349- mzi_bbox .top ,
350- )
351- else :
352- (mzi_xmin , mzi_ymin ), (mzi_xmax , mzi_ymax ) = mzi_bbox [0 ], mzi_bbox [1 ]
353- gc_column_x = mzi_xmax + gc_offset_x
354- gc_center_y = mzi_input .center [1 ]
355-
356- # Place input GC on top, outputs on middle/bottom to reduce crossings.
357- gc_in , gc_in_port = place_gc_at (
358- gc_column_x ,
359- gc_center_y + gc_pitch ,
360- )
361- gc_out_through , gc_out_through_port = place_gc_at (
362- gc_column_x ,
363- gc_center_y ,
364- )
365- gc_out_cross , gc_out_cross_port = place_gc_at (
366- gc_column_x ,
367- gc_center_y - gc_pitch ,
368- )
369-
370- def route_on_track (start_port , end_port , track_y ):
371- start_x , start_y = start_port .center
372- end_x , end_y = end_port .center
373- x1 = start_x + 20.0
374- x2 = end_x - 20.0
375- if x2 <= x1 :
376- x_mid = (start_x + end_x ) / 2
377- waypoints = [(x_mid , start_y ), (x_mid , track_y ), (x_mid , end_y )]
378- else :
379- waypoints = [(x1 , start_y ), (x1 , track_y ), (x2 , track_y ), (x2 , end_y )]
380- return gf .routing .route_single (
381- c ,
382- start_port ,
383- end_port ,
384- cross_section = xs ,
385- waypoints = waypoints ,
386- start_straight_length = 0.0 ,
387- end_straight_length = 0.0 ,
388- )
389-
390- # Route on explicit tracks to avoid overlaps and mirror the example's intent.
391- route_top_y = mzi_ymax + 40.0
392- route_mid_y = gc_center_y
393- route_bot_y = mzi_ymin - 40.0
394-
395- input_stub_x = mzi_xmin - 40.0
396- input_waypoints = [
397- (gc_in_port .center [0 ] - 20.0 , gc_in_port .center [1 ]),
398- (gc_in_port .center [0 ] - 20.0 , route_top_y ),
399- (input_stub_x , route_top_y ),
400- (input_stub_x , mzi_input .center [1 ]),
401- (mzi_input .center [0 ] - 5.0 , mzi_input .center [1 ]),
402- ]
403- gf .routing .route_single (
404- c ,
405- gc_in_port ,
406- mzi_input ,
407- cross_section = xs ,
408- waypoints = input_waypoints ,
299+ gc_port_name = gc_port .name
300+ if gc .ports [gc_port_name ].orientation is not None :
301+ rotation = (180 - gc .ports [gc_port_name ].orientation ) % 360
302+ if rotation :
303+ gc = gf .functions .rotate (gc , rotation )
304+
305+ port_names = [mzi_input .name , mzi_out_through .name , mzi_out_cross .name ]
306+ if any (name is None for name in port_names ):
307+ port_names = None
308+
309+ c = gf .routing .add_fiber_array (
310+ component = mzi ,
311+ grating_coupler = gc ,
312+ gc_port_name = gc_port_name ,
313+ gc_port_name_fiber = gc_port_name ,
314+ port_names = port_names ,
315+ pitch = 127.0 ,
316+ radius = 20.0 ,
317+ with_loopback = False ,
409318 start_straight_length = 0.0 ,
410319 end_straight_length = 0.0 ,
320+ force_manhattan = True ,
411321 )
412322
413- route_on_track (mzi_out_through , gc_out_through_port , route_mid_y )
414- route_on_track (mzi_out_cross , gc_out_cross_port , route_bot_y )
415-
416- c .add_port ("o_in" , port = gc_in_port )
417- c .add_port ("o_through" , port = gc_out_through_port )
418- c .add_port ("o_cross" , port = gc_out_cross_port )
419-
420323 import matplotlib .pyplot as plt_layout
421324 fig_layout = c .plot ()
422325 plt_layout .show ()
@@ -463,22 +366,7 @@ def _(c, mo):
463366 mo .md ("No layout available yet." )
464367 )
465368
466- pin_layer = (1 , 10 )
467- pin_w = 2.0
468- pin_h = 1.0
469- ports = c .ports
470- port_list = list (ports .values ()) if hasattr (ports , "values" ) else list (ports )
471- for port in port_list :
472- cx , cy = port .center
473- c .add_polygon (
474- [
475- (cx - pin_w / 2 , cy - pin_h / 2 ),
476- (cx + pin_w / 2 , cy - pin_h / 2 ),
477- (cx + pin_w / 2 , cy + pin_h / 2 ),
478- (cx - pin_w / 2 , cy + pin_h / 2 ),
479- ],
480- layer = pin_layer ,
481- )
369+ # PinRec markers are provided by the grating coupler cells.
482370
483371 layout_bbox = c .bbox () if callable (getattr (c , "bbox" , None )) else c .bbox
484372 if hasattr (layout_bbox , "left" ):
@@ -623,11 +511,24 @@ def _(c, export_gds, mo):
623511 mo .md ("No layout available yet." )
624512 )
625513 import pathlib
514+ import subprocess
626515
627516 out = pathlib .Path (str (export_gds )).expanduser ()
628517 out .parent .mkdir (parents = True , exist_ok = True )
629518 written = c .write_gds (out )
630- mo .md (f"Wrote: `{ written } `" )
519+ script = pathlib .Path (__file__ ).resolve ().parents [2 ] / "scripts" / "run_klayout_drc.sh"
520+ report = out .with_suffix (".lyrdb" )
521+ if script .exists ():
522+ try :
523+ subprocess .run (
524+ [str (script ), str (out ), str (report )],
525+ check = True ,
526+ )
527+ mo .md (f"Wrote: `{ written } `\n \n DRC report: `{ report } `" )
528+ except subprocess .CalledProcessError as exc :
529+ mo .md (f"Wrote: `{ written } `\n \n DRC failed: `{ exc } `" )
530+ else :
531+ mo .md (f"Wrote: `{ written } `\n \n DRC script not found: `{ script } `" )
631532 return
632533
633534
0 commit comments