@@ -424,6 +424,7 @@ def to_gdstk(
424424 frequency : pydantic .PositiveFloat = 0 ,
425425 gds_layer : pydantic .NonNegativeInt = 0 ,
426426 gds_dtype : pydantic .NonNegativeInt = 0 ,
427+ pixel_exact : bool = False ,
427428 ) -> None :
428429 """Convert a structure's planar slice to a .gds type polygon.
429430
@@ -435,15 +436,16 @@ def to_gdstk(
435436 Position of plane in y direction, only one of x,y,z can be specified to define plane.
436437 z : float = None
437438 Position of plane in z direction, only one of x,y,z can be specified to define plane.
438- permittivity_threshold : float = 1.1
439- Permitivitty value used to define the shape boundaries for structures with custom
440- medim
439+ permittivity_threshold : float = 1
440+ Permitivitty value used to define the shape boundaries for structures with custom medium
441441 frequency : float = 0
442442 Frequency for permittivity evaluaiton in case of custom medium (Hz).
443443 gds_layer : int = 0
444444 Layer index to use for the shapes stored in the .gds file.
445445 gds_dtype : int = 0
446446 Data-type index to use for the shapes stored in the .gds file.
447+ pixel_exact : bool = False
448+ If true export gds as pixel exact rectangles instead of gdstk contour if a custom medium is provided.
447449
448450 Return
449451 ------
@@ -457,27 +459,72 @@ def to_gdstk(
457459 axis , _ = self .geometry .parse_xyz_kwargs (x = x , y = y , z = z )
458460 bb_min , bb_max = self .geometry .bounds
459461
460- # Set the contour scale to be the minimal cooridante step size w.r.t. the 3 main axes,
461- # skipping those with a single coordniate. In case all axes have only a single coordinate,
462- # use the largest bounding box dimension.
463462 eps , _ , _ = self .medium .eps_dataarray_freq (frequency = frequency )
464- scale = max (b - a for a , b in zip (bb_min , bb_max ))
465- for coord in (eps .x , eps .y , eps .z ):
466- if len (coord ) > 1 :
467- scale = min (scale , np .diff (coord ).min ())
468-
469- coords = Coords (
470- x = np .arange (bb_min [0 ], bb_max [0 ] + scale * 0.9 , scale ) if x is None else x ,
471- y = np .arange (bb_min [1 ], bb_max [1 ] + scale * 0.9 , scale ) if y is None else y ,
472- z = np .arange (bb_min [2 ], bb_max [2 ] + scale * 0.9 , scale ) if z is None else z ,
473- )
463+ if pixel_exact :
464+ coords = Coords (
465+ x = eps .x if x is None else x ,
466+ y = eps .y if y is None else y ,
467+ z = eps .z if z is None else z ,
468+ )
469+ else :
470+ # Set the contour scale to be the minimal cooridante step size w.r.t. the 3 main axes,
471+ # skipping those with a single coordniate. In case all axes have only a single coordinate,
472+ # use the largest bounding box dimension.
473+ scale = max (b - a for a , b in zip (bb_min , bb_max ))
474+ for coord in (eps .x , eps .y , eps .z ):
475+ if len (coord ) > 1 :
476+ scale = min (scale , np .diff (coord ).min ())
477+ coords = Coords (
478+ x = np .arange (bb_min [0 ], bb_max [0 ] + scale * 0.9 , scale ) if x is None else x ,
479+ y = np .arange (bb_min [1 ], bb_max [1 ] + scale * 0.9 , scale ) if y is None else y ,
480+ z = np .arange (bb_min [2 ], bb_max [2 ] + scale * 0.9 , scale ) if z is None else z ,
481+ )
482+
474483 eps = self .medium .eps_diagonal_on_grid (frequency = frequency , coords = coords )
475- eps = np .stack ((eps [0 ].real , eps [1 ].real , eps [2 ].real ), axis = 3 ).max (axis = 3 ).squeeze ()
476- contours = gdstk .contour (eps .T , permittivity_threshold , scale , precision = scale * 1e-3 )
484+ eps = (
485+ np .stack ((eps [0 ].real , eps [1 ].real , eps [2 ].real ), axis = 3 )
486+ .max (axis = 3 )
487+ .squeeze (axis = axis )
488+ )
489+
490+ if pixel_exact :
491+ # Convert coordinates to numpy arrays for efficient processing
492+ _ , (w , h ) = self .geometry .pop_axis ((coords .x , coords .y , coords .z ), axis )
493+ w , h = np .asarray (w ), np .asarray (h )
494+ _ , (wmin , hmin ) = self .geometry .pop_axis (bb_min , axis )
495+ _ , (wmax , hmax ) = self .geometry .pop_axis (bb_max , axis )
496+
497+ # Determine boundaries by taking the midpoint between adjacent coordinates
498+ if w .size > 1 :
499+ dw = np .diff (w ) * 0.5
500+ wb = np .concatenate (([wmin ], w [:- 1 ] + dw , [wmax ]))
501+ else :
502+ wb = np .array ([wmin , wmax ])
503+
504+ if h .size > 1 :
505+ dh = np .diff (h ) * 0.5
506+ hb = np .concatenate (([hmin ], h [:- 1 ] + dh , [hmax ]))
507+ else :
508+ hb = np .array ([hmin , hmax ])
509+
510+ # Create boolean mask where permittivity exceeds threshold
511+ mask = eps > permittivity_threshold
512+ w_idxs , h_idxs = np .where (mask )
513+
514+ # Generate list of gdstk.Polygon (rectangles)
515+ contours = [
516+ gdstk .rectangle ((wb [wi ], hb [hi ]), (wb [wi + 1 ], hb [hi + 1 ]))
517+ for wi , hi in zip (w_idxs , h_idxs )
518+ ]
519+
520+ else :
521+ contours = gdstk .contour (
522+ eps .T , permittivity_threshold , scale , precision = scale * 1e-3
523+ )
477524
478- _ , (dx , dy ) = self .geometry .pop_axis (bb_min , axis )
479- for polygon in contours :
480- polygon .translate (dx , dy )
525+ _ , (dx , dy ) = self .geometry .pop_axis (bb_min , axis )
526+ for polygon in contours :
527+ polygon .translate (dx , dy )
481528
482529 polygons = gdstk .boolean (polygons , contours , "and" , layer = gds_layer , datatype = gds_dtype )
483530
@@ -493,6 +540,7 @@ def to_gds(
493540 frequency : pydantic .PositiveFloat = 0 ,
494541 gds_layer : pydantic .NonNegativeInt = 0 ,
495542 gds_dtype : pydantic .NonNegativeInt = 0 ,
543+ pixel_exact : bool = False ,
496544 ) -> None :
497545 """Append a structure's planar slice to a .gds cell.
498546
@@ -515,6 +563,8 @@ def to_gds(
515563 Layer index to use for the shapes stored in the .gds file.
516564 gds_dtype : int = 0
517565 Data-type index to use for the shapes stored in the .gds file.
566+ pixel_exact : bool = False
567+ If true export gds as pixel exact rectangles instead of gdstk contour if a custom medium is provided.
518568 """
519569 if not isinstance (cell , gdstk .Cell ):
520570 if "gdstk" in cell .__class__ .__name__ .lower () and not gdstk_available :
@@ -531,6 +581,7 @@ def to_gds(
531581 frequency = frequency ,
532582 gds_layer = gds_layer ,
533583 gds_dtype = gds_dtype ,
584+ pixel_exact = pixel_exact ,
534585 )
535586 if polygons :
536587 cell .add (* polygons )
@@ -546,6 +597,7 @@ def to_gds_file(
546597 gds_layer : pydantic .NonNegativeInt = 0 ,
547598 gds_dtype : pydantic .NonNegativeInt = 0 ,
548599 gds_cell_name : str = "MAIN" ,
600+ pixel_exact : bool = False ,
549601 ) -> None :
550602 """Export a structure's planar slice to a .gds file.
551603
@@ -570,6 +622,8 @@ def to_gds_file(
570622 Data-type index to use for the shapes stored in the .gds file.
571623 gds_cell_name : str = 'MAIN'
572624 Name of the cell created in the .gds file to store the geometry.
625+ pixel_exact : bool = False
626+ If true export gds as pixel exact rectangles instead of gdstk contour if a custom medium is provided.
573627 """
574628 try :
575629 import gdstk
@@ -590,6 +644,7 @@ def to_gds_file(
590644 frequency = frequency ,
591645 gds_layer = gds_layer ,
592646 gds_dtype = gds_dtype ,
647+ pixel_exact = pixel_exact ,
593648 )
594649 fname = pathlib .Path (fname )
595650 fname .parent .mkdir (parents = True , exist_ok = True )
0 commit comments