from__future__importannotationsimportnumpyasnpfromnumpyimportndarrayimportgdsfactoryasgffromgdsfactory.componentimportComponentfromgdsfactory.configimportErrorTypefromgdsfactory.geometry.functionsimportangles_deg,curvature,path_length,snap_anglefromgdsfactory.typingsimport(Coordinate,Coordinates,CrossSectionSpec,)defbezier_curve(t:ndarray,control_points:Coordinates)->ndarray:"""Returns bezier coordinates. Args: t: 1D array of points varying between 0 and 1. control_points: for the bezier curve. """fromscipy.specialimportbinomxs=0.0ys=0.0n=len(control_points)-1forkinrange(n+1):ank=binom(n,k)*(1-t)**(n-k)*t**kxs+=ank*control_points[k][0]ys+=ank*control_points[k][1]returnnp.column_stack([xs,ys])
[docs]@gf.celldefbezier(control_points:Coordinates=((0.0,0.0),(5.0,0.0),(5.0,1.8),(10.0,1.8)),npoints:int=201,with_manhattan_facing_angles:bool=True,start_angle:int|None=None,end_angle:int|None=None,cross_section:CrossSectionSpec="xs_sc",bend_radius_error_type:ErrorType|None=None,)->Component:"""Returns Bezier bend. Args: control_points: list of points. npoints: number of points varying between 0 and 1. with_manhattan_facing_angles: bool. start_angle: optional start angle in deg. end_angle: optional end angle in deg. cross_section: spec. """xs=gf.get_cross_section(cross_section)t=np.linspace(0,1,npoints)path_points=bezier_curve(t,control_points)path=gf.Path(path_points)ifwith_manhattan_facing_angles:path.start_angle=start_angleorsnap_angle(path.start_angle)path.end_angle=end_angleorsnap_angle(path.end_angle)c=Component()bend=path.extrude(xs)bend_ref=c<<bendc.add_ports(bend_ref.ports)c.absorb(bend_ref)curv=curvature(path_points,t)length=gf.snap.snap_to_grid(path_length(path_points))ifmax(np.abs(curv))==0:min_bend_radius=np.infelse:min_bend_radius=gf.snap.snap_to_grid(1/max(np.abs(curv)))c.info["length"]=float(length)c.info["min_bend_radius"]=min_bend_radiusc.info["start_angle"]=path.start_anglec.info["end_angle"]=path.end_anglexs.validate_radius(min_bend_radius,bend_radius_error_type)returnc
deffind_min_curv_bezier_control_points(start_point:ndarray,end_point:Coordinate,start_angle:float,end_angle:float,npoints:int=201,alpha:float=0.05,nb_pts:int=2,)->Coordinates:"""Returns bezier control points that minimize curvature. Args: start_point: start point. end_point: end point. start_angle: start angle in deg. end_angle: end angle in deg. npoints: number of points varying between 0 and 1. alpha: weight for angle mismatch. nb_pts: number of control points. """fromscipy.optimizeimportminimizet=np.linspace(0,1,npoints)defarray_1d_to_cpts(a):xs=a[::2]ys=a[1::2]returnlist(zip(xs,ys))defobjective_func(p):"""minimize max curvaturea and negligible start angle and end angle mismatch"""ps=array_1d_to_cpts(p)control_points=[start_point]+ps+[end_point]path_points=bezier_curve(t,control_points)max_curv=max(np.abs(curvature(path_points,t)))angles=angles_deg(path_points)dstart_angle=abs(angles[0]-start_angle)dend_angle=abs(angles[-2]-end_angle)angle_mismatch=dstart_angle+dend_anglereturnangle_mismatch*alpha+max_curvx0,y0=start_point[0],start_point[1]xn,yn=end_point[0],end_point[1]initial_guess=[]foriinrange(nb_pts):x=(i+1)*(x0+xn)/nb_ptsy=(i+1)*(y0+yn)/nb_ptsinitial_guess+=[x,y]# initial_guess = [(x0 + xn) / 2, y0, (x0 + xn) / 2, yn]res=minimize(objective_func,initial_guess,method="Nelder-Mead")p=res.xreturn[tuple(start_point)]+array_1d_to_cpts(p)+[tuple(end_point)]if__name__=="__main__":control_points=((0.0,0.0),(5.0,0.0),(5.0,5.0),(10.0,5.0))c=bezier(control_points=control_points)c.show(show_ports=True)