Skip to content

Commit

Permalink
Bézier curves, PCell
Browse files Browse the repository at this point in the history
* bezier bend, sbend, taper
* Update __init__.py
* bezier update
* fix menu Layout > Show selected components
  • Loading branch information
lukasc-ubc committed Jul 29, 2024
2 parents 85cd0d9 + b9db833 commit 43b3d59
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 16 deletions.
2 changes: 1 addition & 1 deletion klayout_dot_config/grain.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<salt-grain>
<name>siepic_tools</name>
<version>0.5.11</version>
<version>0.5.12</version>
<api-version>0.27</api-version>
<title>SiEPIC Tools</title>
<doc>Tools for designing Silicon Photonic Integrated Circuits, including waveguides, component simulations, functional verification, DRC verification, Functional verification, netlist extraction, circuit simulations. Layout can be implemented graphically or by programming in Python using the SiEPIC functions and KLayout Python API. Framework and examples for creating layouts using scripts. Includes a generic PDK (GSiP). Other PDKs are installed separately, and depend on SiEPIC-Tools.</doc>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def show_component_info():
text += ('&lt;br&gt;')
text += " selected cell name: %s" % obj.inst().cell.name
text += ('&lt;br&gt;')
c = cell.find_components(cell_selected=[obj.inst().cell],verbose=True)
c = cell.find_components(cell_selected=tuple([obj.inst().cell]),verbose=True, raiseException = False)
if c and c[0].cell:
text += c[0].display().replace(';','&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;')
if c[0].cell.is_pcell_variant():
Expand Down
2 changes: 1 addition & 1 deletion klayout_dot_config/python/SiEPIC/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
SiEPIC-Tools package for KLayout
'''

__version__ = '0.5.11'
__version__ = '0.5.12'

print("KLayout SiEPIC-Tools version %s" %__version__)

Expand Down
17 changes: 13 additions & 4 deletions klayout_dot_config/python/SiEPIC/extend.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,11 @@ def find_pins(self, verbose=False, polygon_devrec=None, GUI=False):
Electrical Pins have:
1) box on layer PinRec
2) text on layer PinRec, inside the box
Returns:
pins: SiEPIC.core.Pin
pin_errors: text
'''

if verbose:
Expand Down Expand Up @@ -946,7 +951,7 @@ def find_pins_component(self, component):
'''
from functools import lru_cache
@lru_cache(maxsize=None)
def find_components(self, cell_selected=None, inst=None, verbose=False):
def find_components(self, cell_selected=None, inst=None, verbose=False, raiseException = True):
'''
Function to traverse the cell's hierarchy and find all the components
returns list of components (class Component)
Expand All @@ -963,13 +968,15 @@ def find_components(self, cell_selected=None, inst=None, verbose=False):
inst: return only the component that matches the instance inst
raiseException: False turns of exception handling, and returns None instead
limitation:
- flat components only. doesn't find the component if it is buried in a hierarchy
- no function for instance.find_components. Instead we find based on cell, then try to match it to the requested instance.
'''

if cell_selected != None and type(cell_selected) != type([]):
if cell_selected != None and type(cell_selected) != type([]) and type(cell_selected) != tuple:
cell_selected=[cell_selected]

if verbose:
Expand Down Expand Up @@ -1143,8 +1150,10 @@ def find_components(self, cell_selected=None, inst=None, verbose=False):
return component_matched

if components == []:
raise Exception ('SiEPIC.extend.find_components: No component found for cell_selected=%s' % (cell_selected[0].name if cell_selected else None))

if raiseException:
raise Exception ('SiEPIC.extend.find_components: No component found for cell_selected=%s' % (cell_selected[0].name if cell_selected else None))
else:
return None
return components
# end def find_components

Expand Down
11 changes: 7 additions & 4 deletions klayout_dot_config/python/SiEPIC/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def connect_pins_with_waveguide(instanceA, pinA, instanceB, pinB, waveguide = No
else:
waveguide = waveguides[0]
print('error: waveguide type not found in PDK waveguides')
raise Exception('error: waveguide type (%s) not found in PDK waveguides' % waveguide_type)
raise Exception('error: waveguide type (%s) not found in PDK. Waveguides available: %s' % (waveguide_type, [w['name'] for w in waveguides]))
# check if the waveguide type is compound waveguide
if 'compound_waveguide' in waveguide:
waveguide = [w for w in waveguides if w['name']==waveguide['compound_waveguide']['singlemode']]
Expand Down Expand Up @@ -1780,7 +1780,8 @@ def connect_cell(instanceA, pinA, cellB, pinB, mirror = False, verbose=False, tr

if type(componentA) == type([]):
componentA = componentA[0]
componentB = componentB[0]
if type(componentB) == type([]):
componentB = componentB[0]
if verbose:
componentA.display()
componentB.display()
Expand All @@ -1794,9 +1795,11 @@ def connect_cell(instanceA, pinA, cellB, pinB, mirror = False, verbose=False, tr
import re
try:
if cpinA==[]:
cpinA = [p for p in componentA.pins if re.findall(r'\d+', pinA)[0] in p.pin_name]
if re.findall(r'\d+', pinA):
cpinA = [p for p in componentA.pins if re.findall(r'\d+', pinA)[0] in p.pin_name]
if cpinB==[]:
cpinB = [p for p in componentB.pins if re.findall(r'\d+', pinB)[0] in p.pin_name]
if re.findall(r'\d+', pinB):
cpinB = [p for p in componentB.pins if re.findall(r'\d+', pinB)[0] in p.pin_name]
except:
print('error in siepic.scripts.connect_cell')

Expand Down
26 changes: 22 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,25 @@ 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]
in_type = 'Point'
elif type(pts[0]) == pya.DPoint:
in_type = 'DPoint'
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 +1162,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 in_type == 'Point':
return [pt.to_itype(1) for pt in tpts]
else:
return tpts


def pt_intersects_segment(a, b, c):
Expand Down
76 changes: 76 additions & 0 deletions klayout_dot_config/python/SiEPIC/utils/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ def __str__(self):
return str(Point({self.x}, {self.y}))

def norm(self):
'''Euclidean length'''
return sqrt(self.x**2 + self.y**2)

def long_edge_length(self):
'''return the longest segment of a Manhattan distance'''
return max(self.x, self.y)

class Line(Point):
""" Defines a line """
Expand Down Expand Up @@ -150,6 +154,13 @@ def max_curvature(P0, P1, P2, P3):
max_curv = np.max(np.abs(curv.flatten()))
return max_curv

def min_curvature(P0, P1, P2, P3):
"""Gets the minimum curvature of Bezier curve"""
t = np.linspace(0, 1, 300)
curv = curvature_bezier(P0, P1, P2, P3)(t)
min_curv = np.min(np.abs(curv.flatten()))
return min_curv


def _curvature_penalty(P0, P1, P2, P3):
"""Penalty on the curvyness of Bezier curve"""
Expand Down Expand Up @@ -377,6 +388,71 @@ def bezier_optimal(P0, P3, *args, **kwargs):
except ImportError:
pass

def bezier_cubic(P0, P3, angle0, angle3, a, b, accuracy = 0.001, verbose=False, plot=False, *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 longest segment in a Manhattan route between P0 and P3.
Args:
P0, P3: pya.DPoint (in microns)
angle0, angle3: radians
a, b: <float>. 0 corresponds to P1=P0, and 1 corresponds to P1 at the corner of a 90º bend
accuracy: 0.001 = 1 nm
Returns:
list of pya.DPoint
Example:
Bezier curve can approximate a 1/4 circle (arc) for a=b=0.553
# https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
'''

P0 = Point(P0.x, P0.y)
P3 = Point(P3.x, P3.y)
scale = (P3 - P0).long_edge_length() # longest 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

if verbose:
# print the minimum/maximum curvature
print ('SiEPIC.utils.geometry.bezier_cubic: minimum radius of curvature = %0.3g' % (1/max_curvature(P0, P1, P2, P3)))
print ('SiEPIC.utils.geometry.bezier_cubic: maximum radius of curvature = %0.3g' % (1/min_curvature(P0, P1, P2, P3)))
if plot:
t = np.linspace(0, 1, 300)
curv = curvature_bezier(P0, P1, P2, P3)(t)
rc = 1./curv.flatten()
import matplotlib.pyplot as plt
plt.plot(t, rc, '--pb', label='a=%3g, b=%3g' % (a,b), linewidth=1.5)
SizeFont = 19
plt.xlabel('Position along path (t)', fontsize=SizeFont)
plt.ylabel('Radius of curvature (microns)', fontsize=SizeFont)
plt.legend(fontsize=SizeFont)
plt.xticks(fontsize=SizeFont)
plt.ylim(bottom=0)
plt.yticks(fontsize=SizeFont)
plt.show()

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



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


Expand Down
2 changes: 1 addition & 1 deletion klayout_dot_config/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "SiEPIC"
version = "0.5.11"
version = "0.5.12"
authors = [
{ name="Lukas Chrostowski", email="lukasc@ece.ubc.ca" },
]
Expand Down
Loading

0 comments on commit 43b3d59

Please sign in to comment.