Files
mxpic_forge/mxpic/components/geometry/rings.py
T
2026-06-06 16:43:26 +08:00

515 lines
25 KiB
Python

"""Circular, elliptical, and bend geometry primitives."""
from typing import Any, Optional
from cmath import pi
import nazca as nd
import numpy as np
from .polygons import _my_polygon
class circle :
'''
# =================================================================
# @ File : <mx_frame_lib.py>
# @ structure: circle ring or disk
# @ Args : * radius : center radius of the ring
# : * width : width of the ring
# : * theta_start : start end of the ring, range [0~360], can be negative
# : * theta_stop : stop end of the ring, range [0~360], can be negative
# : * n_points : resolution of the polygon
# : * xs : placing layer
# @ located in the center of the ring
# =================================================================
'''
def __init__(self,radius: float = 10, width: float = 0.45, theta_start: float=0, theta_stop: float=360,res: float=0.05,angle: Optional[float]=None,
# n_points = 64,
xs: str='strip',layer: Optional[str]=None,sharp_patch: bool=True,
y_cut: Optional[float] = None) -> None:
with nd.Cell(instantiate=False) as C:
if (angle!=None):
theta_start = 0
theta_stop = angle
if (res!=None):
n_points = int(np.floor(abs(theta_start-theta_stop)*radius*np.pi/180/res)+1)
if (layer==None):
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
theta = np.linspace(theta_start,theta_stop,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(radius+width*a1 + b1)
vtx_outer_y = np.sin(theta)*(radius+width*a1 + b1)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
## used for 360 degree
if (radius+width*a2+b1>0.0000001 or np.abs(theta_stop-theta_start)<360):
vtx_inner_x = np.cos(theta)*(radius+width*a2 + b2)
vtx_inner_y = np.sin(theta)*(radius+width*a2 + b2)
vtx_inner = np.c_[np.flip(vtx_inner_x),np.flip(vtx_inner_y)]
vtx = np.r_[vtx_outer,vtx_inner]
else :
vtx = vtx_outer
""" add in 2023.09.20 """
vtx_y = vtx[:,1]
vtx_x = vtx[:,0]
vtx_cut = None
if (y_cut!=None):
if (y_cut> min(vtx_y)):
x_cut_max = max(vtx_x)
x_cut_min = min(vtx_x)
y_cut_max = y_cut
y_cut_min = min(vtx_y)-1
vtx_x_cut = np.array([x_cut_max,x_cut_max,x_cut_min,x_cut_min])
vtx_y_cut = np.array([y_cut_max,y_cut_min,y_cut_min,y_cut_max])
vtx_cut = np.c_[vtx_x_cut,vtx_y_cut]
""" """
if (sharp_patch==True and b2!=0 and b1!=0):
L_patch = max([max(vtx_outer_x),max(vtx_inner_x)])-min([min(vtx_outer_x),min(vtx_inner_x)])
X_patch = 1/2*(max([max(vtx_outer_x),max(vtx_inner_x)])+min([min(vtx_outer_x),min(vtx_inner_x)]))
W_patch = (max([max(vtx_outer_y),max(vtx_inner_y)])-min([min(vtx_outer_y),min(vtx_inner_y)]))
Y_patch = 1/2*(max([max(vtx_outer_y),max(vtx_inner_y)])+min([min(vtx_outer_y),min(vtx_inner_y)]))
nd.strt(length=L_patch,width=W_patch,layer=layers).put(X_patch-L_patch/2,Y_patch,0)
else:
_my_polygon(layer_wg=layers,vtx=vtx,vtx_not=vtx_cut).put(0,0,0)
nd.Pin(name='a1',width=width,xs=xs).put(radius*np.cos(theta_start/180*np.pi),radius*np.sin(theta_start/180*np.pi),theta_start-90)
nd.Pin(name='b1',width=width,xs=xs).put(radius*np.cos(theta_stop/180*np.pi),radius*np.sin(theta_stop/180*np.pi),theta_stop+90)
else:
theta = np.linspace(theta_start,theta_stop,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(radius+width/2)
vtx_outer_y = np.sin(theta)*(radius+width/2)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
if (radius-width/2>0.0000001 or np.abs(theta_stop-theta_start)<360):
vtx_inner_x = np.cos(theta)*(radius-width/2)
vtx_inner_y = np.sin(theta)*(radius-width/2)
vtx_inner = np.c_[np.flip(vtx_inner_x),np.flip(vtx_inner_y)]
vtx = np.r_[vtx_outer,vtx_inner]
else :
vtx = vtx_outer
""" add in 2023.09.20 """
vtx_y = vtx[:, 1]
vtx_x = vtx[:, 0]
vtx_cut = None
if (y_cut!=None):
if (y_cut> min(vtx_y)):
x_cut_max = max(vtx_x)
x_cut_min = min(vtx_x)
y_cut_max = y_cut
y_cut_min = min(vtx_y)-1
vtx_x_cut = np.array([x_cut_max,x_cut_max,x_cut_min,x_cut_min])
vtx_y_cut = np.array([y_cut_max,y_cut_min,y_cut_min,y_cut_max])
vtx_cut = np.c_[vtx_x_cut,vtx_y_cut]
""" """
_my_polygon(layer_wg=layer,vtx=vtx,vtx_not=vtx_cut).put(0,0,0)
nd.Pin(name='a1',width=width,layer=layer).put(radius*np.cos(theta_start/180*np.pi),radius*np.sin(theta_start/180*np.pi),theta_start-90)
nd.Pin(name='b1',width=width,layer=layer).put(radius*np.cos(theta_stop/180*np.pi),radius*np.sin(theta_stop/180*np.pi),theta_stop+90)
self.vtx = vtx
self.sz = [radius*2,radius*2]
self.w = [width,width]
self.cell = C
class mx_bend :
def __init__(self,radius: float = 10, width: float = 0.45, theta_start: float=0, theta_stop: float=360,res: float=0.05,angle: Optional[float]=None,
# n_points = 64,
xs: str='strip',layer: Optional[str]=None,sharp_patch: bool=True) -> None:
with nd.Cell(instantiate=False) as C:
if (angle!=None):
theta_start = 0
theta_stop = angle
if (res!=None):
n_points = int(np.floor(abs(theta_start-theta_stop)*radius/180*np.pi/res)+1)
if (layer==None):
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
theta = np.linspace(theta_start,theta_stop,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(radius+width*a1 + b1)
vtx_outer_y = np.sin(theta)*(radius+width*a1 + b1)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
if (radius+width*a2+b1>0.0000001 or np.abs(theta_stop-theta_start)<360):
vtx_inner_x = np.cos(theta)*(radius+width*a2 + b2)
vtx_inner_y = np.sin(theta)*(radius+width*a2 + b2)
vtx_inner = np.c_[np.flip(vtx_inner_x),np.flip(vtx_inner_y)]
vtx = np.r_[vtx_outer,vtx_inner]
else :
vtx = vtx_outer
if (sharp_patch==True and b2!=0 and b1!=0):
L_patch = max([max(vtx_outer_x),max(vtx_inner_x)])-min([min(vtx_outer_x),min(vtx_inner_x)])
X_patch = 1/2*(max([max(vtx_outer_x),max(vtx_inner_x)])+min([min(vtx_outer_x),min(vtx_inner_x)]))
W_patch = (max([max(vtx_outer_y),max(vtx_inner_y)])-min([min(vtx_outer_y),min(vtx_inner_y)]))
Y_patch = 1/2*(max([max(vtx_outer_y),max(vtx_inner_y)])+min([min(vtx_outer_y),min(vtx_inner_y)]))
nd.strt(length=L_patch,width=W_patch,layer=layers).put(X_patch-L_patch/2,Y_patch,0)
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
nd.Pin(name='a0',width=width,xs=xs).put(radius*np.cos(theta_start/180*np.pi),radius*np.sin(theta_start/180*np.pi),theta_start-90)
nd.Pin(name='b0',width=width,xs=xs).put(radius*np.cos(theta_stop/180*np.pi),radius*np.sin(theta_stop/180*np.pi),theta_stop+90)
else:
theta = np.linspace(theta_start,theta_stop,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(radius+width/2)
vtx_outer_y = np.sin(theta)*(radius+width/2)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
if (radius-width/2>0.0000001 or np.abs(theta_stop-theta_start)<360):
vtx_inner_x = np.cos(theta)*(radius-width/2)
vtx_inner_y = np.sin(theta)*(radius-width/2)
vtx_inner = np.c_[np.flip(vtx_inner_x),np.flip(vtx_inner_y)]
vtx = np.r_[vtx_outer,vtx_inner]
else :
vtx = vtx_outer
_my_polygon(layer_wg=layer,vtx=vtx).put(0,0,0)
nd.Pin(name='a0',width=width,layer=layer).put(radius*np.cos(theta_start/180*np.pi),radius*np.sin(theta_start/180*np.pi),theta_start-90)
nd.Pin(name='b0',width=width,layer=layer).put(radius*np.cos(theta_stop/180*np.pi),radius*np.sin(theta_stop/180*np.pi),theta_stop+90)
self.sz = [radius*2,radius*2]
self.w = [width,width]
self.cell = C
class Elipse_dual :
def __init__(self,
ORx : float ,
ORy : float ,
IRx : float ,
IRy : float ,
offset_X : float = 0,
offset_Y : float = 0,
xs : Optional[str] = None,
layer : Optional[str] = None,
theta_start : float = 0,
theta_stop : float = 360,
sharp_patch : bool = True,
# n_points : int = 1024,
res : float = 0.001,
y_cut: Optional[float]=None) -> None:
"""_summary_
Args:
ORx (float): Outer semi X-axis length
ORy (float): Outer semi Y-axis length
IRx (float): Inner semi X-axis length
IRy (float): Inner semi Y-axis length
offset_X (float, optional): Outer and Inner elipse offset in X. Defaults to 0.
offset_Y (float, optional): Outer and Inner elipse offset in Y. Defaults to 0.
xs (str, optional): xsection. Defaults to None.
layer (str, optional): layer. Defaults to None.
theta_start (str, optional): X-axis positvive starts at 0, rotation anti-clockwise . Defaults to 0.
theta_stop (str, optional): X-axis positvive starts at 0, rotation anti-clockwise. Defaults to 360.
sharp_patch (bool, optional): sharp patch. Defaults to True.
n_points (int, optional): points of the ring. Defaults to 1024.
"""
self.ORx = ORx
self.ORy = ORy
self.IRx = IRx
self.IRy = IRy
self.offset_X = offset_X
self.offset_Y = offset_Y
self.xs = xs
self.layer = layer
self.res = res
# self.n_points = int(n_points) ## Force type fixing
self.theta_start = theta_start
self.theta_stop = theta_stop
self.y_cut = y_cut
self.cell = self.generate_gds(sharp_patch=sharp_patch)
self.wa = ORx-IRx
self.wb = ORy-IRy
def generate_gds(self,sharp_patch):
with nd.Cell(instantiate=False) as C:
if (self.layer==None and self.xs!=None):
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
(a1,b1), (a2,b2),c1,c2 = growx
""" Calculating points inside the ring """
Rb = min([self.ORx+self.IRx,self.ORy+self.IRy])/2
Ra = max([self.ORx+self.IRx,self.ORy+self.IRy])/2
_L_perimeter_ = 2*pi*Rb + 4*(Ra-Rb)
n_points = int(_L_perimeter_/self.res)
n_points = int(n_points/360*abs(self.theta_start-self.theta_stop)) ## modified the points by the angle of ring
theta = np.linspace(self.theta_start,self.theta_stop,n_points)
Ox = (self.ORx + b1)*np.cos(theta/180*pi)
Oy = (self.ORy + b1)*np.sin(theta/180*pi)
Ix = (self.IRx + b2)*np.cos(theta/180*pi)+self.offset_X
Iy = (self.IRy + b2)*np.sin(theta/180*pi)+self.offset_Y
dX = np.max([np.max(Ox),np.max(Ix)]) - np.min([np.min(Ox),np.min(Ix)])
dY = np.max([np.max(Oy),np.max(Iy)]) - np.min([np.min(Oy),np.min(Iy)])
X = np.max([np.max(Ox),np.max(Ix)])/2 + np.min([np.min(Ox),np.min(Ix)])/2
Y = np.max([np.max(Oy),np.max(Iy)])/2 + np.min([np.min(Oy),np.min(Iy)])/2
cx = Ox/2+Ix/2
cy = Oy/2+Iy/2
LX = np.max(cx) - np.min(cx)
LY = np.max(cy) - np.min(cy)
self.sz = [LX,LY]
vtx_out = np.c_[Ox,Oy]
vtx_In = np.c_[np.flip(Ix),np.flip(Iy)]
vtx = np.r_[vtx_out,vtx_In]
""" add in 2023.09.20 """
vtx_y = vtx[:,1]
vtx_x = vtx[:,0]
vtx_cut = None
if (self.y_cut!=None):
if (self.y_cut> min(vtx_y)):
x_cut_max = max(vtx_x)
x_cut_min = min(vtx_x)
y_cut_max = self.y_cut
y_cut_min = min(vtx_y)-1
vtx_x_cut = np.array([x_cut_max,x_cut_max,x_cut_min,x_cut_min])
vtx_y_cut = np.array([y_cut_max,y_cut_min,y_cut_min,y_cut_max])
vtx_cut = np.c_[vtx_x_cut,vtx_y_cut]
""" """
if (sharp_patch==True and b1!=0 and b2!=0):
patch = hole(r_hole=min([self.IRx + b2,self.IRy + b2]),Lx_sq=dX,Ly_sq=dY,layer=layers)
patch.cell.put(0,Y,0)
patch.cell.put(0,Y,0,flip=1)
# nd.strt(length=dX,width=dY,layer=layers).put(X-dX/2,Y,0)
else:
_my_polygon(layer_wg=layers,vtx=vtx,vtx_not=vtx_cut).put(0,0,0)
nd.Pin(name='a1').put((Ox[0]+Ix[0])/2,(Oy[0]+Iy[0])/2,theta[0]-90)
nd.Pin(name='b1').put((Ox[-1]+Ix[-1])/2,(Oy[-1]+Iy[-1])/2,theta[-1]+90)
return C
class Elipse:
def __init__(self,La: Any=None,Lb: Any=None,wa: Any=None,wb: Any=None,offset_a: float=0,offset_b: float=0,type: str="center",width_type: str='sine',layer: Optional[str]=None,xs: Optional[str]=None,theta_start: float=0,theta_stop: float=360,
# n_points=512,
res: float = 0.001,
sharp_patch: bool=False,show_pins: bool=False) -> None:
self.La = La
self.Lb = Lb
self.wa = wa
self.wb = wb
self.offset_a = offset_a
self.offset_b = offset_b
self.type = type
self.layer = layer
self.xs = xs
self.theta_start = theta_start
self.theta_stop = theta_stop
# self.n_points = n_points
self.res = res
self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins)
def generate_gds(self,sharp_patch,show_pins):
with nd.Cell(instantiate=False) as C:
if (self.layer==None and self.xs!=None):
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
(a1,b1), (a2,b2),c1,c2 = growx
""" calculated number of points """
Rb = self.La
Ra = self.Lb
_L_perimeter_ = 2*pi*Rb + 4*(Ra-Rb)
n_points = int(_L_perimeter_/self.res)
n_points = int(n_points/360*abs(self.theta_start-self.theta_stop)) ## modified the points by the angle of ring
theta = np.linspace(self.theta_start,self.theta_stop,n_points)
if (self.type=='center'):
cx = self.La*np.cos(theta/180*pi)
cy = self.Lb*np.sin(theta/180*pi)
w = (self.wa-self.wb)*np.cos(theta/180*pi)*np.cos(theta/180*pi) + self.wb
offset = (self.offset_a-self.offset_b)*np.cos(theta/180*pi)*np.cos(theta/180*pi) + self.offset_b
w = w*(a1-a2) + (b1-b2)
## norm vector
nx = 2*cx/self.La/self.La
ny = 2*cy/self.Lb/self.Lb
Ln = np.sqrt(nx*nx + ny*ny)
Ox = cx + nx*(w/2 + offset)/Ln
Oy = cy + ny*(w/2 + offset)/Ln
Ix = cx + nx*(-w/2 + offset)/Ln
Iy = cy + ny*(-w/2 + offset)/Ln
elif (self.type == 'concentric'):
Ox = (self.La+(self.wa*a1+b1))*np.cos(theta/180*pi)
Oy = (self.Lb+(self.wb*a1+b1))*np.sin(theta/180*pi)
Ix = (self.La+(self.wa*a2+b2))*np.cos(theta/180*pi)
Iy = (self.Lb+(self.wb*a2+b2))*np.sin(theta/180*pi)
cx = Ox/2+Ix/2
cy = Oy/2+Iy/2
else :
raise Exception("ERROR: In <mxpic::passive::Elipse>, <type> not recongized, please input [center | concentric]")
dX = np.max([np.max(Ox),np.max(Ix)]) - np.min([np.min(Ox),np.min(Ix)])
dY = np.max([np.max(Oy),np.max(Iy)]) - np.min([np.min(Oy),np.min(Iy)])
X = np.max([np.max(Ox),np.max(Ix)])/2 + np.min([np.min(Ox),np.min(Ix)])/2
Y = np.max([np.max(Oy),np.max(Iy)])/2 + np.min([np.min(Oy),np.min(Iy)])/2
LX = np.max(cx) - np.min(cx)
LY = np.max(cy) - np.min(cy)
self.sz = [LX,LY]
vtx_out = np.c_[Ox,Oy]
vtx_In = np.c_[np.flip(Ix),np.flip(Iy)]
vtx = np.r_[vtx_out,vtx_In]
if (sharp_patch==True and b1!=0 and b2!=0):
nd.strt(length=dX,width=dY,layer=layers).put(X-dX/2,Y,0)
else:
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
Ain = np.angle(nx[0]+1j*ny[0])/pi*180
Aout = np.angle(nx[-1]+1j*ny[-1])/pi*180
nd.Pin(name='a1').put(Ox[0]/2+Ix[0]/2,Oy[0]/2+Iy[0]/2,Ain-90)
nd.Pin(name='b1').put(Ox[-1]/2+Ix[-1]/2,Oy[-1]/2+Iy[-1]/2,Aout+90)
nd.Pin(name='a0').put(0,0,180)
nd.Pin(name='b0').put(0,0,0)
if (show_pins):
nd.put_stub()
return C
class hole :
def __init__(self,r_hole: float = 0.3,Dx_hole: float=0.3,Dy_hole: float=0.3,Lx_sq: int = 6,Ly_sq: int=6,offset: float=0,
# n_points = 1024,
res: float = 0.05,
xs: str='strip',layer: Optional[str]=None,sharp_patch: bool=True,hole_shape: str='circle') -> None:
with nd.Cell(instantiate=False) as C:
if (r_hole+offset>Lx_sq/2 or -r_hole+offset<-Lx_sq/2):
raise Exception("ERROR: In <mxpic::structure::hole>, hole outside sqaure area, <r_hole>")
if (Dx_hole/2+offset>Lx_sq/2 or -Dx_hole/2+offset<-Lx_sq/2):
raise Exception("ERROR: In <mxpic::structure::hole>, hole outside sqaure area, <Dx_hole>")
if (Dy_hole>Ly_sq):
raise Exception("ERROR: In <mxpic::structure::hole>, hole outside sqaure area, <Dy_hole>")
n_points = int(np.floor(r_hole*2*np.pi/res)+1)
if (layer==None):
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
if (b1==0 and b2==0):
if (hole_shape=='circle'):
theta = np.linspace(0,180,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(r_hole)+offset
vtx_outer_y = np.sin(theta)*(r_hole)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
vtx_sq_x = np.array([Lx_sq/2, Lx_sq/2,-Lx_sq/2,-Lx_sq/2])
vtx_sq_y = np.array([ 0, Ly_sq/2, Ly_sq/2, 0])
vtx_sq = np.c_[vtx_sq_x,vtx_sq_y]
vtx = np.r_[vtx_outer,np.flip(vtx_sq,0)]
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,180,flip=1)
elif (hole_shape=='rectangle'):
vtx_outer_x = np.array([Dx_hole/2,Dx_hole/2,-Dx_hole/2,-Dx_hole/2])+offset
vtx_outer_y = np.array([0,Dy_hole/2, Dy_hole/2,0])
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
vtx_sq_x = np.array([Lx_sq/2, Lx_sq/2,-Lx_sq/2,-Lx_sq/2])
vtx_sq_y = np.array([ 0, Ly_sq/2, Ly_sq/2, 0])
vtx_sq = np.c_[vtx_sq_x,vtx_sq_y]
vtx = np.r_[vtx_outer,np.flip(vtx_sq,0)]
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,180,flip=1)
else :
_L_ = Lx_sq*(a1-a2)+(b1-b1)
_w_ = Ly_sq*(a1-a2)+(b1-b1)
nd.strt(length=_L_,width=_w_,layer=layers).put(-_L_/2,0,0)
else:
if (hole_shape=='circle'):
theta = np.linspace(0,180,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.cos(theta)*(r_hole)+offset
vtx_outer_y = np.sin(theta)*(r_hole)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
vtx_sq_x = np.array([Lx_sq/2, Lx_sq/2,-Lx_sq/2,-Lx_sq/2])
vtx_sq_y = np.array([ 0, Ly_sq/2, Ly_sq/2, 0])
vtx_sq = np.c_[vtx_sq_x,vtx_sq_y]
vtx = np.r_[vtx_outer,np.flip(vtx_sq,0)]
_my_polygon(layer_wg=layer,vtx=vtx).put(0,0,0)
_my_polygon(layer_wg=layer,vtx=vtx).put(0,0,0,flip=1)
elif (hole_shape=='rectangle'):
vtx_outer_x = np.array([Dx_hole/2,Dx_hole/2,-Dx_hole/2,-Dx_hole/2])+offset
vtx_outer_y = np.array([0,Dy_hole/2, Dy_hole/2,0])
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
vtx_sq_x = np.array([Lx_sq/2, Lx_sq/2,-Lx_sq/2,-Lx_sq/2])
vtx_sq_y = np.array([ 0, Ly_sq/2, Ly_sq/2, 0])
vtx_sq = np.c_[vtx_sq_x,vtx_sq_y]
vtx = np.r_[vtx_outer,np.flip(vtx_sq,0)]
_my_polygon(layer_wg=layer,vtx=vtx).put(0,0,0)
_my_polygon(layer_wg=layer,vtx=vtx).put(0,0,180,flip=1)
self.cell = C