Skip to content

Commit

Permalink
bezier bend, sbend, taper
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasc-ubc committed Jul 25, 2024
1 parent 85cd0d9 commit c8aaea5
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 4 deletions.
25 changes: 21 additions & 4 deletions klayout_dot_config/python/SiEPIC/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1119,9 +1119,24 @@ def arc_to_waveguide(pts, width):


def translate_from_normal(pts, trans):
'''Translate each point by its normal a distance 'trans' '''
'''Translate each point by its normal a distance 'trans'
Args:
pts: list of pya.Point (nanometers)
or pya.DPoint (microns)
trans: <float> (matching pts, either nm or microns)
Returns:
list of pya.Point or pya.DPoint, matching Arg pts type.
'''
# pts = [pya.DPoint(pt) for pt in pts]
pts = [pt.to_dtype(1) for pt in pts]
if type(pts[0]) == pya.Point:
# convert to float pya.DPoint
pts = [pt.to_dtype(1) for pt in pts]
elif type(pts[0]) == pya.DPoint:
pass
else:
raise Exception('SiEPIC.utils.translate_from_normal expects pts=[pya.Point,...] or [pya.DPoint,...]')
if len(pts) < 2:
return pts
from math import cos, sin, pi
Expand All @@ -1146,8 +1161,10 @@ def translate_from_normal(pts, trans):
else:
tpts[-1].x = pts[-1].x
# return [pya.Point(pt) for pt in tpts]
return [pt.to_itype(1) for pt in tpts]

if type(pts[0]) == pya.Point:
return [pt.to_itype(1) for pt in tpts]
else:
return tpts


def pt_intersects_segment(a, b, c):
Expand Down
42 changes: 42 additions & 0 deletions klayout_dot_config/python/SiEPIC/utils/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,48 @@ def bezier_optimal(P0, P3, *args, **kwargs):
except ImportError:
pass

def bezier_cubic(P0, P3, angle0, angle3, a, b, accuracy = 0.001, *args, **kwargs):
'''
Calculate a cubic Bezier curve between Points P0 and P3,
where the control point positions P1 and P2 are determined by
the angles at P0 (angle0) and P3 (angle3),
at a distance of a * scale from P0, and b * scale from P3,
where scale is the distance between P0 and P3.
Args:
P0, P3: pya.DPoint (in microns)
angle0, angle3: radians
a, b: <float> greater than 0
accuracy: 0.001 = 1 nm
Returns:
list of pya.DPoint
'''

P0 = Point(P0.x, P0.y)
P3 = Point(P3.x, P3.y)
scale = (P3 - P0).norm() # distance between the two end points
P1 = a * scale * Point(np.cos(angle0), np.sin(angle0)) + P0
P2 = P3 - b * scale * Point(np.cos(angle3), np.sin(angle3))
new_bezier_line = bezier_line(P0, P1, P2, P3)
# new_bezier_line = _bezier_optimal_pure(P0, P3, *args, **kwargs)
bezier_point_coordinates = lambda t: np.array([new_bezier_line(t).x, new_bezier_line(t).y])

_, bezier_point_coordinates_sampled = \
sample_function(bezier_point_coordinates, [0, 1], tol=accuracy / scale)

# # This yields a better polygon
bezier_point_coordinates_sampled = \
np.insert(bezier_point_coordinates_sampled, 1, bezier_point_coordinates(accuracy / scale),
axis=1) # add a point right after the first one
bezier_point_coordinates_sampled = \
np.insert(bezier_point_coordinates_sampled, -1, bezier_point_coordinates(1 - accuracy / scale),
axis=1) # add a point right before the last one

return [pya.DPoint(x, y) for (x, y) in zip(*(bezier_point_coordinates_sampled))]



# ####################### SIEPIC EXTENSION ##########################


Expand Down
209 changes: 209 additions & 0 deletions klayout_dot_config/tech/GSiP/pymacros/tests/test_bezier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
'''
Bezier curve test structures
by Lukas Chrostowski, 2024
Example simple script to
- create a new layout with a top cell
- create Bezier S-Bend, 90º Bend, taper
- to do: U-turn
usage:
- run this script in KLayout Application, or in standalone Python
'''

from pya import *

def test_bezier_bends():
designer_name = 'Test_Bezier_bend'
top_cell_name = 'GSiP_%s' % designer_name

import pya

import SiEPIC
from SiEPIC._globals import Python_Env
from SiEPIC.scripts import connect_cell, connect_pins_with_waveguide, zoom_out, export_layout
from SiEPIC.utils.layout import new_layout, floorplan
from SiEPIC.extend import to_itype
from SiEPIC.verification import layout_check

import os
path = os.path.dirname(os.path.realpath(__file__))

tech_name = 'GSiP'

from packaging import version
if version.parse(SiEPIC.__version__) < version.parse('0.5.4'):
raise Exception("Errors", "This example requires SiEPIC-Tools version 0.5.4 or greater.")

if Python_Env == 'Script':
# Load the PDK from a folder, e.g, GitHub, when running externally from the KLayout Application
import sys
sys.path.insert(0,os.path.abspath(os.path.join(path, '../../..')))
import GSiP

'''
Create a new layout
'''
cell, ly = new_layout(tech_name, top_cell_name, GUI=True, overwrite = True)
dbu = ly.dbu
import numpy as np

from SiEPIC.utils.geometry import bezier_parallel, bezier_cubic
from SiEPIC.utils import translate_from_normal
from SiEPIC.utils import arc_xy, arc_bezier

accuracy = 0.005

w = 10
h = 1
width = 0.5
layer = ly.layer(ly.TECHNOLOGY['Waveguide'])

# Two S-bends
wg_Dpts = bezier_parallel(pya.DPoint(0, 0), pya.DPoint(w, h), 0)
wg_polygon = pya.DPolygon(translate_from_normal(wg_Dpts, width/2) +
translate_from_normal(wg_Dpts, -width/2)[::-1]
)
cell.shapes(layer).insert(wg_polygon)
a = 0.5
wg_Dpts = bezier_cubic(pya.DPoint(0, 0), pya.DPoint(w, h), 0, 0, a, a, accuracy = accuracy)
wg_polygon = pya.DPolygon(translate_from_normal(wg_Dpts, width/2) +
translate_from_normal(wg_Dpts, -width/2)[::-1]
)
cell.shapes(layer).insert(wg_polygon)

# 90º bend
r = 5
bezier = 0.3
a = (1-bezier)/np.sqrt(2)
wg_Dpts = bezier_cubic(pya.DPoint(0, 0), pya.DPoint(r, r), 0, 90/180*np.pi, a, a, accuracy = accuracy)
wg_polygon = pya.DPolygon(translate_from_normal(wg_Dpts, width/2) +
translate_from_normal(wg_Dpts, -width/2)[::-1]
)
cell.shapes(layer).insert(wg_polygon)
wg_pts = arc_bezier(r/dbu, 0, 90, float(bezier))
wg_Dpts = [p.to_dtype(dbu) for p in wg_pts]
# wg_pts += Path(arc_xy(-pt_radius, pt_radius, pt_radius, 270, 270 + inner_angle_b_vectors(pts[i-1]-pts[i], pts[i+1]-pts[i]),
# DevRec='DevRec' in layers[lr], dbu=dbu), 0).transformed(Trans(angle, turn < 0, pts[i])).get_points()
wg_polygon = pya.DPolygon(translate_from_normal(wg_Dpts, width/2) +
translate_from_normal(wg_Dpts, -width/2)[::-1]
).transformed(pya.Trans(r,0))
cell.shapes(layer).insert(wg_polygon)


# Save
filename = os.path.splitext(os.path.basename(__file__))[0]
file_out = export_layout(cell, path, filename+top_cell_name, format='oas', screenshot=True)

# Display in KLayout
from SiEPIC._globals import Python_Env
if Python_Env == 'Script':
from SiEPIC.utils import klive
klive.show(file_out, technology=tech_name, keep_position=True)

# Plot
# cell.plot() # in the browser


def test_bezier_tapers():
designer_name = 'Test_Bezier_tapers'
top_cell_name = 'GSiP_%s' % designer_name

import pya

import SiEPIC
from SiEPIC._globals import Python_Env
from SiEPIC.scripts import connect_cell, connect_pins_with_waveguide, zoom_out, export_layout
from SiEPIC.utils.layout import new_layout, floorplan
from SiEPIC.extend import to_itype
from SiEPIC.verification import layout_check

import os
path = os.path.dirname(os.path.realpath(__file__))

tech_name = 'GSiP'

from packaging import version
if version.parse(SiEPIC.__version__) < version.parse('0.5.4'):
raise Exception("Errors", "This example requires SiEPIC-Tools version 0.5.4 or greater.")

if Python_Env == 'Script':
# Load the PDK from a folder, e.g, GitHub, when running externally from the KLayout Application
import sys
sys.path.insert(0,os.path.abspath(os.path.join(path, '../../..')))
import GSiP

'''
Create a new layout
'''
cell, ly = new_layout(tech_name, top_cell_name, GUI=True, overwrite = True)
dbu = ly.dbu
import numpy as np

from SiEPIC.utils.geometry import bezier_parallel, bezier_cubic
from SiEPIC.utils import translate_from_normal
from SiEPIC.utils import arc_xy, arc_bezier

accuracy = 0.005
layer = ly.layer(ly.TECHNOLOGY['Waveguide'])

# Bezier Taper
taper_length = 20
width0 = 0.5
width1 = 3.0
# a = 0.4 # bezier parameter
# b = a
# wg_Dpts = bezier_cubic(pya.DPoint(0, width0/2), pya.DPoint(taper_length, width1/2), 0, 0, a, b, accuracy = accuracy) + \
# bezier_cubic(pya.DPoint(0, -width0/2), pya.DPoint(taper_length, -width1/2), 0, 0, a, b, accuracy = accuracy)[::-1]
# wg_polygon = pya.DPolygon( wg_Dpts )
# cell.shapes(layer).insert(wg_polygon)
# a = 0.5 # bezier parameter
# b = a
# wg_Dpts = bezier_cubic(pya.DPoint(0, width0/2), pya.DPoint(taper_length, width1/2), 0, 0, a, b, accuracy = accuracy) + \
# bezier_cubic(pya.DPoint(0, -width0/2), pya.DPoint(taper_length, -width1/2), 0, 0, a, b, accuracy = accuracy)[::-1]
# wg_polygon = pya.DPolygon( wg_Dpts )
# cell.shapes(layer).insert(wg_polygon)
# a = 0.6 # bezier parameter
# b = a
# wg_Dpts = bezier_cubic(pya.DPoint(0, width0/2), pya.DPoint(taper_length, width1/2), 0, 0, a, b, accuracy = accuracy) + \
# bezier_cubic(pya.DPoint(0, -width0/2), pya.DPoint(taper_length, -width1/2), 0, 0, a, b, accuracy = accuracy)[::-1]
# wg_polygon = pya.DPolygon( wg_Dpts )
# cell.shapes(layer).insert(wg_polygon)

# matching a Sinusoidal taper, per Sean Lam in EBeam_Beta
a = 0.37 # bezier parameter
b = 0.37 # 0.385
taper_length = 20
wg_Dpts = bezier_cubic(pya.DPoint(0, width0/2), pya.DPoint(taper_length, width1/2), 0, 0, a, b, accuracy = accuracy) + \
bezier_cubic(pya.DPoint(0, -width0/2), pya.DPoint(taper_length, -width1/2), 0, 0, a, b, accuracy = accuracy)[::-1]
wg_polygon = pya.DPolygon( wg_Dpts )
cell.shapes(layer).insert(wg_polygon)

# a = 0.95 # bezier parameter
# b = 0.05 # bezier parameter
# wg_Dpts = bezier_cubic(pya.DPoint(0, width0/2), pya.DPoint(taper_length, width1/2), 0, 0, a, b, accuracy = accuracy) + \
# bezier_cubic(pya.DPoint(0, -width0/2), pya.DPoint(taper_length, -width1/2), 0, 0, a, b, accuracy = accuracy)[::-1]
# wg_polygon = pya.DPolygon( wg_Dpts )
# cell.shapes(layer).insert(wg_polygon)


# Save
filename = os.path.splitext(os.path.basename(__file__))[0]
file_out = export_layout(cell, path, filename+top_cell_name, format='oas', screenshot=True)

# Display in KLayout
from SiEPIC._globals import Python_Env
if Python_Env == 'Script':
from SiEPIC.utils import klive
klive.show(file_out, technology=tech_name, keep_position=True)

# Plot
# cell.plot() # in the browser


if __name__ == "__main__":
test_bezier_bends()
test_bezier_tapers()

0 comments on commit c8aaea5

Please sign in to comment.