def fang(wg_width, length, orientation): F = Device() w1 = wg_width X1 = CrossSection() X1.add(width=w1, offset=0, layer=30, ports=('in', 'out')) P = Path() P.append(pp.euler(radius=50, angle=45)) # Euler bend (aka "racetrack" curve) fang = P.extrude(X1) fang = F.add_ref(fang) D = pg.taper(length=length, width1=w1, width2=0.000001, port=None, layer=30) taper = F.add_ref(D) taper.connect(port=1, destination=fang.ports['out']) #Defualt is RU, right up if orientation == 'RD': F.mirror(p1=[0, 0], p2=[1, 0]) elif orientation == 'LU': F.mirror(p1=[0, 0], p2=[0, 1]) elif orientation == 'LD': F.rotate(180, center=[0, 0]) return F
def arc(radius=10, angle=90, num_pts=720): """ Create a circular arc Path Parameters ---------- radius : int or float Radius of arc angle : int or float Total angle of arc num_pts : int Number of points used per 360 degrees Returns ------- Path A Path object with the specified arc """ num_pts = abs(int(num_pts * angle / 360)) t = np.linspace(-90 * np.pi / 180, (angle - 90) * np.pi / 180, num_pts) x = radius * np.cos(t) y = radius * (np.sin(t) + 1) points = np.array((x, y)).T * np.sign(angle) P = Path() # Manually add points & adjust start and end angles P.points = points P.start_angle = 0 P.end_angle = angle return P
def dcpm(L, elec_w, e_e_gap, via, wg_width): P = Path() P.append(pp.straight(length=L)) X = CrossSection() X.add(width=wg_width, offset=0, layer=30) DCPM = Device() DCPM << P.extrude(X) R1 = pg.rectangle(size=(L, elec_w), layer=40) R2 = pg.rectangle(size=(L, elec_w), layer=40) DCPM << R1.move([0, e_e_gap / 2]) DCPM << R2.move([0, -elec_w - e_e_gap / 2]) return DCPM
def rfpm(wg_width, length, middle_e_width, e_e_gap): side_electrode_width = middle_e_width * 2 P = Path() P.append(pp.straight(length=length)) X = CrossSection() X.add(width=wg_width, offset=0, layer=30) RFPM = Device() RFPM << P.extrude(X) Rt = pg.rectangle(size=(length, side_electrode_width), layer=40) Rm = pg.rectangle(size=(length, middle_e_width), layer=40) Rb = pg.rectangle(size=(length, side_electrode_width), layer=40) RFPM << Rt.move([0, e_e_gap / 2]) RFPM << Rm.move([0, -middle_e_width - e_e_gap / 2]) RFPM << Rb.move( [0, -middle_e_width - side_electrode_width - e_e_gap - e_e_gap / 2]) square = middle_e_width * 0.9 side_height = side_electrode_width * 0.9 square_rec_offset = (side_electrode_width - side_height) / 2 square_rec_offset_m = (middle_e_width - square) / 2 e_left = 0 e_right = e_left + length - square #side_e_width R = pg.rectangle(size=(length, middle_e_width), layer=40) R2 = pg.rectangle(size=(length, side_electrode_width), layer=40) S = pg.rectangle(size=(square, square), layer=50) S2 = pg.rectangle(size=(square, side_height), layer=50) #top electrode h_top = e_e_gap / 2 RFPM.add_ref(S2).move([e_left, h_top + square_rec_offset]) RFPM.add_ref(S2).move([e_right, h_top + square_rec_offset]) #middle electrode h_mid = -middle_e_width - e_e_gap / 2 RFPM.add_ref(S).move([e_left, h_mid + square_rec_offset_m]) RFPM.add_ref(S).move([e_right, h_mid + square_rec_offset_m]) #bottom electrode h_bot = -middle_e_width - side_electrode_width - e_e_gap - e_e_gap / 2 RFPM.add_ref(S2).move([e_left, h_bot + square_rec_offset]) RFPM.add_ref(S2).move([e_right, h_bot + square_rec_offset]) return RFPM
def straight(length=5, num_pts=100): """ Creates a straight Path Parameters ---------- length : int or float Total length of straight path num_pts : int Number of points along Path Returns ------- Path A Path object with the specified straight section """ x = np.linspace(0, length, num_pts) y = x * 0 points = np.array((x, y)).T P = Path() P.append(points) return P
def euler(radius=3, angle=90, p=1.0, use_eff=False, num_pts=720): """ Create an Euler bend (also known as "racetrack" or "clothoid" curves) that adiabatically transitions from straight to curved. By default, `radius` corresponds to the minimum radius of curvature of the bend. However, if `use_eff` is set to True, `radius` corresponds to the effective radius of curvature (making the curve a drop-in replacement for an arc). If p < 1.0, will create a "partial euler" curve as described in Vogelbacher et. al. https://dx.doi.org/10.1364/oe.27.031394 Parameters ---------- radius : int or float Minimum radius of curvature angle : int or float Total angle of curve p : float Proportion of curve that is an Euler curve use_eff : bool If False: `radius` corresponds to minimum radius of curvature of the bend If True: The curve will be scaled such that the endpoints match an arc with parameters `radius` and `angle` num_pts : int Number of points used per 360 degrees Returns ------- Path A Path object with the specified Euler curve """ if (p < 0) or (p > 1): raise ValueError( '[PHIDL] euler() requires argument `p` be between 0 and 1') if p == 0: P = arc(radius=radius, angle=angle, num_pts=num_pts) P.info['Reff'] = radius P.info['Rmin'] = radius return P if angle < 0: mirror = True angle = np.abs(angle) else: mirror = False R0 = 1 alpha = np.radians(angle) Rp = R0 / (np.sqrt(p * alpha)) sp = R0 * np.sqrt(p * alpha) s0 = 2 * sp + Rp * alpha * (1 - p) num_pts = abs(int(num_pts * angle / 360)) num_pts_euler = int(np.round(sp / (s0 / 2) * num_pts)) num_pts_arc = num_pts - num_pts_euler xbend1, ybend1 = _fresnel(R0, sp, num_pts_euler) xp, yp = xbend1[-1], ybend1[-1] dx = xp - Rp * np.sin(p * alpha / 2) dy = yp - Rp * (1 - np.cos(p * alpha / 2)) s = np.linspace(sp, s0 / 2, num_pts_arc) xbend2 = Rp * np.sin((s - sp) / Rp + p * alpha / 2) + dx ybend2 = Rp * (1 - np.cos((s - sp) / Rp + p * alpha / 2)) + dy x = np.concatenate([xbend1, xbend2[1:]]) y = np.concatenate([ybend1, ybend2[1:]]) points1 = np.array([x, y]).T points2 = np.flipud(np.array([x, -y]).T) points2 = _rotate_points(points2, angle - 180) points2 += -points2[0, :] + points1[-1, :] points = np.concatenate([points1[:-1], points2]) # Find y-axis intersection point to compute Reff start_angle = 180 * (angle < 0) end_angle = start_angle + angle dy = np.tan(np.radians(end_angle - 90)) * points[-1][0] Reff = points[-1][1] - dy Rmin = Rp # Fix degenerate condition at angle == 180 if np.abs(180 - angle) < 1e-3: Reff = points[-1][1] / 2 # Scale curve to either match Reff or Rmin if use_eff == True: scale = radius / Reff else: scale = radius / Rmin points *= scale P = Path() # Manually add points & adjust start and end angles P.points = points P.start_angle = start_angle P.end_angle = end_angle P.info['Reff'] = Reff * scale P.info['Rmin'] = Rmin * scale if mirror == True: P.mirror((1, 0)) return P
def smooth(points=[ (20, 0), (40, 0), (80, 40), (80, 10), (100, 10), ], radius=4, corner_fun=euler, **kwargs): """ Create a smooth path from a series of waypoints. Corners will be rounded using `corner_fun` and any additional key word arguments (for example, `use_eff = True` when `corner_fun = pp.euler`) Parameters ---------- points : array-like[N][2] List of waypoints for the path to follow radius : int or float Radius of curvature, this argument will be passed to `corner_fun` corner_fun : function The function that controls how the corners are rounded. Typically either `arc()` or `euler()` **kwargs : dict Extra keyword arguments that will be passed to `corner_fun` Returns ------- Path A Path object with the specified smoothed path. """ points = np.asfarray(points) normals = np.diff(points, axis=0) normals = (normals.T / np.linalg.norm(normals, axis=1)).T # normals_rev = normals*np.array([1,-1]) dx = np.diff(points[:, 0]) dy = np.diff(points[:, 1]) ds = np.sqrt(dx**2 + dy**2) theta = np.degrees(np.arctan2(dy, dx)) dtheta = np.diff(theta) # FIXME add caching # Create arcs paths = [] radii = [] for dt in dtheta: P = corner_fun(radius=radius, angle=dt, **kwargs) chord = np.linalg.norm(P.points[-1, :] - P.points[0, :]) r = (chord / 2) / np.sin(np.radians(dt / 2)) r = np.abs(r) radii.append(r) paths.append(P) d = np.abs(np.array(radii) / np.tan(np.radians(180 - dtheta) / 2)) encroachment = np.concatenate([[0], d]) + np.concatenate([d, [0]]) if np.any(encroachment > ds): raise ValueError( '[PHIDL] smooth(): Not enough distance between points to to fit curves. Try reducing the radius or spacing the points out farther' ) p1 = points[1:-1, :] - normals[:-1, :] * d[:, np.newaxis] # Move arcs into position new_points = [] new_points.append([points[0, :]]) for n, dt in enumerate(dtheta): P = paths[n] P.rotate(theta[n] - 0) P.move(p1[n]) new_points.append(P.points) new_points.append([points[-1, :]]) new_points = np.concatenate(new_points) P = Path(new_points) P.move(points[0, :]) return P
def spiral(num_turns=5, gap=1, inner_gap=2, num_pts=10000): """ Creates a spiral geometry consisting of two oddly-symmetric semi-circular arcs in the centre and two Archimedean (involute) spiral arms extending outward from the ends of both arcs. Parameters ---------- num_turns : int or float The number of turns in the spiral. Must be greater than 1. A full spiral rotation counts as 1 turn, and the center arcs will together always be 0.5 turn. gap : int or float The distance between any point on one arm of the spiral and a point with the same angular coordinate on an adjacent arm. inner_gap : int or float The inner size of the spiral, equal to twice the chord length of the centre arcs. num_pts: int The number of points in the entire spiral. The actual number of points will be slightly different than the specified value, as they are dynamically allocated using the path lengths of the spiral. Returns ------- Path A Path object forming a spiral Notes ----- ``num_turns`` usage (x is any whole number): - ``num_turns = x.0``: Output arm will be extended 0.5 turn to be on the same side as the input. - ``num_turns < x.5``: Input arm will be extended by the fractional amount. - ``num_turns = x.5``: Both arms will be the same length and the input and output will be on opposite sides. - ``num_turns > x.5``: Output arm will be extended by the fractional amount. """ # Establishing number of turns in each arm if num_turns <= 1: raise ValueError('num_turns must be greater than 1') diff = num_turns - np.floor(num_turns) if diff < 0.5: num_turns1 = np.floor(num_turns) - 1 + 2 * diff else: num_turns1 = np.floor(num_turns) if diff > 0.5: num_turns2 = np.floor(num_turns) - 1 + 2 * diff else: num_turns2 = np.floor(num_turns) # Establishing relevant angles and spiral/centre arc parameters a1 = np.pi / 2 a2 = np.array([np.pi * num_turns1 + a1, np.pi * num_turns2 + a1]) a = inner_gap / 2 - gap / 2 b = gap / np.pi Rc = inner_gap * np.sqrt(1 + (b / (a + b * a1))**2) / 4 theta = np.degrees(2 * np.arcsin(inner_gap / 4 / Rc)) # Establishing number of points in each arm s_centre = Rc * np.radians(theta) s_spiral = ((a + a2 * b)**2 + b**2)**(3 / 2) / (3 * (a * b + (a2 * b**2))) z = num_pts / (s_spiral[0] + s_spiral[1] + 2 * s_centre) num_pts0 = int(z * s_centre) num_pts1 = int(z * s_spiral[0]) num_pts2 = int(z * s_spiral[1]) - num_pts1 # Forming both spiral arms arm1 = np.linspace(a1, a2[0], num_pts1) arm2 = np.linspace(a2[0], a2[1], num_pts2)[1:] a_spiral = np.array([arm1, np.concatenate([arm1, arm2])]) r_spiral = a + b * a_spiral x_spiral = np.array([np.zeros(num_pts1), np.zeros(len(a_spiral[1]))]) y_spiral = np.array([np.zeros(num_pts1), np.zeros(len(a_spiral[1]))]) for i in range(2): x_spiral[i] = r_spiral[i] * np.cos(a_spiral[i]) y_spiral[i] = r_spiral[i] * np.sin(a_spiral[i]) # Forming centre arcs pts = _rotate_points( arc(Rc, theta, 360 * num_pts0 / theta).points, -theta / 2 + 90) x_centre = pts[:, 0] + x_spiral[0][0] - pts[:, 0][-1] y_centre = pts[:, 1] + y_spiral[0][0] - pts[:, 1][-1] x_centre = np.concatenate([-np.flip(x_centre), x_centre]) y_centre = np.concatenate([-np.flip(y_centre), y_centre]) # Combining into final spiral x = np.concatenate([-np.flip(x_spiral[1]), x_centre, x_spiral[0]]) y = np.concatenate([-np.flip(y_spiral[1]), y_centre, y_spiral[0]]) points = np.array((x, y)).T P = Path() # Manually add points & adjust start and end angles P.points = points nx1, ny1 = points[1] - points[0] P.start_angle = np.arctan2(ny1, nx1) / np.pi * 180 nx2, ny2 = points[-1] - points[-2] P.end_angle = np.arctan2(ny2, nx2) / np.pi * 180 # print(P.start_angle) # print(P.end_angle) return P
def mzi(length, radius, angle, wg_width, Y_mmi): P1 = Path() P1.append(pp.euler(radius=radius, angle=-angle)) P1.append(pp.euler(radius=radius, angle=angle)) P1.append(pp.straight(length=length)) P1.append(pp.euler(radius=radius, angle=angle)) P1.append(pp.euler(radius=radius, angle=-angle)) X = CrossSection() X.add(width=wg_width, offset=0, layer=30) waveguide_device1 = P1.extrude(X) E = Device('EOM_GHz') b1 = E.add_ref(waveguide_device1).move([0, -Y_mmi / 2]) b2 = E.add_ref(waveguide_device1).move([0, -Y_mmi / 2]) b2.mirror((0, 0), (1, 0)) return E
def connect(x2, y2, middle_e_width, chip_width, chip_height, pos, radius, length, e_e_gap, setpos): mm = 10**3 um = 1 M = Path() # Dimensions margin = 0.5 * mm to_pad_term = 0.1 * mm pad = 50 * um pitch = 100 * um side_e_width = middle_e_width * 2 rad_gap = e_e_gap + middle_e_width / 2 + side_e_width / 2 if pos == 't': Rt = radius + rad_gap * 2 elif pos == 'm': Rt = radius + rad_gap else: Rt = radius if setpos == 't': set_bias = 0.6 * mm elif setpos == 'm': set_bias = 0.3 * mm else: set_bias = 0 x1 = (chip_width - length) / 2 - Rt - set_bias y1 = margin + to_pad_term points = np.array([(x1, y1), (x1, y2), (x2, y2)]) points = rotate90(points) M = pp.smooth( points=points, radius=Rt, corner_fun=pp.arc, ) M.rotate(90) X = CrossSection() if pos == 'm': X.add(width=middle_e_width, offset=0, layer=3) else: X.add(width=side_e_width, offset=0, layer=3) L = M.extrude(X) #Left Trace if pos == 'm': # adding pads S = pg.rectangle(size=(pad, pad), layer=3) L.add_ref(S).move([x1 - pad / 2, margin]) L.add_ref(S).move([x1 - pad / 2 - pitch, margin]) L.add_ref(S).move([x1 - pad / 2 + pitch, margin]) xm1 = x1 - middle_e_width / 2 xm2 = x1 + middle_e_width / 2 xm3 = x1 + pad / 2 xm4 = x1 - pad / 2 xpts = (xm1, xm2, xm3, xm4) ypts = (y1, y1, margin + pad, margin + pad) L.add_polygon([xpts, ypts], layer=3) xt1 = xm1 + middle_e_width + e_e_gap xt2 = xt1 + side_e_width xt3 = xm3 + pitch xt4 = xm4 + pitch xpts = (xt1, xt2, xt3, xt4) ypts = (y1, y1, margin + pad, margin + pad) L.add_polygon([xpts, ypts], layer=3) xb1 = xm1 - side_e_width - e_e_gap xb2 = xm1 - e_e_gap xb3 = xm3 - pitch xb4 = xm4 - pitch xpts = (xb1, xb2, xb3, xb4) ypts = (y1, y1, margin + pad, margin + pad) L.add_polygon([xpts, ypts], layer=3) R = pg.copy(L) # Right Trace R.mirror((chip_width / 2, chip_height), (chip_width / 2, 0)) D = Device('trace') D << L D << R return D
def eom_sym(wg_width, length, middle_e_width, e_e_gap, chip_width, offset, radius): euler_y = mod_euler(radius=radius, angle=-45)[1][1] euler_x = mod_euler(radius=radius, angle=-45)[1][0] wg_wg_sep = (middle_e_width + e_e_gap) / 2 - 2 * euler_y straight = wg_wg_sep * np.sqrt(2) if wg_wg_sep < 0: raise Exception( "middle_e_width is set too small with respect to Euler radius") left = chip_width / 2 - length / 2 - 2 * euler_x - wg_wg_sep + offset right = left P1 = Path() P1.append(pp.straight(length=left)) P1.append(mod_euler(radius=radius, angle=-45)[0]) P1.append(pp.straight(length=straight)) P1.append(mod_euler(radius=radius, angle=45)[0]) P1.append(pp.straight(length=length)) P1.append(mod_euler(radius=radius, angle=45)[0]) P1.append(pp.straight(length=straight)) P1.append(mod_euler(radius=radius, angle=-45)[0]) P1.append(pp.straight(length=right)) X = CrossSection() X.add(width=wg_width, offset=0, layer=1) waveguide_device1 = P1.extrude(X) E = Device('EOM_GHz') b1 = E.add_ref(waveguide_device1) b2 = E.add_ref(waveguide_device1) b2.mirror((0, 0), (1, 0)) square = middle_e_width * 0.6 square_rec_offset = (middle_e_width - square) / 2 e_left = left + 2 * euler_x + wg_wg_sep e_right = left + 2 * euler_x + wg_wg_sep + length - square #side_e_width R = pg.rectangle(size=(length, middle_e_width), layer=10) S = pg.rectangle(size=(square, square), layer=2) #top electrode h_top = middle_e_width / 2 + e_e_gap E.add_ref(R).move([e_left, h_top]) E.add_ref(S).move([e_left, h_top + square_rec_offset]) E.add_ref(S).move([e_right, h_top + square_rec_offset]) #middle electrode E.add_ref(R).move([e_left, -middle_e_width / 2]) E.add_ref(S).move([e_left, -square / 2]) E.add_ref(S).move([e_right, -square / 2]) #bottom electrode h_bot = -3 * middle_e_width / 2 - e_e_gap E.add_ref(R).move([e_left, h_bot]) E.add_ref(S).move([e_left, h_bot + square_rec_offset]) E.add_ref(S).move([e_right, h_bot + square_rec_offset]) #E << R return E
def dcim(im_gap, im_length, coupler_l, im_r, im_angle, elec_w, e_e_gap, via, wg_width, V_Groove_Spacing): P = Path() euler_y = mod_euler(radius=im_r, angle=im_angle)[1][1] euler_x = mod_euler(radius=im_r, angle=im_angle)[1][0] l_bend = ((V_Groove_Spacing - im_gap - 4 * euler_y - wg_width) / 2) / np.sin(np.pi * im_angle / 180) P.append(pp.euler(radius=im_r, angle=-im_angle)) P.append(pp.straight(length=l_bend)) P.append(pp.euler(radius=im_r, angle=im_angle)) P.append(pp.straight(length=coupler_l)) P.append(pp.euler(radius=im_r, angle=im_angle)) P.append(pp.euler(radius=im_r, angle=-im_angle)) P.append(pp.straight(length=im_length)) P.append(pp.euler(radius=im_r, angle=-im_angle)) P.append(pp.euler(radius=im_r, angle=im_angle)) P.append(pp.straight(length=coupler_l)) P.append(pp.euler(radius=im_r, angle=im_angle)) P.append(pp.straight(length=l_bend)) P.append(pp.euler(radius=im_r, angle=-im_angle)) P.movey(V_Groove_Spacing) X = CrossSection() X.add(width=wg_width, offset=0, layer=30) IM = Device('IM') IM << P.extrude(X) IM << P.extrude(X).mirror(p1=[1, V_Groove_Spacing / 2], p2=[2, V_Groove_Spacing / 2]) R1 = pg.rectangle(size=(im_length, elec_w), layer=40) R2 = pg.rectangle(size=(im_length, elec_w), layer=40) R3 = pg.rectangle(size=(im_length, elec_w), layer=40) R4 = pg.rectangle(size=(im_length, elec_w), layer=40) movex = euler_x * 4 + coupler_l + l_bend * np.cos(np.pi * im_angle / 180) movey = euler_y * 2 + im_gap / 2 + wg_width IM << R1.move( [movex, V_Groove_Spacing / 2 + movey + e_e_gap / 2 - wg_width / 2]) IM << R2.move([ movex, V_Groove_Spacing / 2 + movey - e_e_gap / 2 - elec_w - wg_width / 2 ]) IM << R3.move( [movex, V_Groove_Spacing / 2 - movey + e_e_gap / 2 + wg_width / 2]) IM << R4.move([ movex, V_Groove_Spacing / 2 - movey - e_e_gap / 2 - elec_w + wg_width / 2 ]) return IM, movex
def smooth(points=[ (20, 0), (40, 0), (80, 40), (80, 10), (100, 10), ], radius=4, corner_fun=euler, **kwargs): """ Create a smooth path from a series of waypoints. Corners will be rounded using `corner_fun` and any additional key word arguments (for example, `use_eff = True` when `corner_fun = pp.euler`) Parameters ---------- points : array-like[N][2] List of waypoints for the path to follow radius : int or float Radius of curvature, this argument will be passed to `corner_fun` corner_fun : function The function that controls how the corners are rounded. Typically either `arc()` or `euler()` **kwargs : dict Extra keyword arguments that will be passed to `corner_fun` Returns ------- Path A Path object with the specified smoothed path. """ points, normals, ds, theta, dtheta = _compute_segments(points) colinear_elements = np.concatenate([[False], np.abs(dtheta) < 1e-6, [False]]) if np.any(colinear_elements): new_points = points[~colinear_elements, :] points, normals, ds, theta, dtheta = _compute_segments(new_points) if np.any(np.abs(np.abs(dtheta) - 180) < 1e-6): raise ValueError( '[PHIDL] smooth() received points which double-back on themselves' + '--turns cannot be computed when going forwards then exactly backwards.' ) # FIXME add caching # Create arcs paths = [] radii = [] for dt in dtheta: P = corner_fun(radius=radius, angle=dt, **kwargs) chord = np.linalg.norm(P.points[-1, :] - P.points[0, :]) r = (chord / 2) / np.sin(np.radians(dt / 2)) r = np.abs(r) radii.append(r) paths.append(P) d = np.abs(np.array(radii) / np.tan(np.radians(180 - dtheta) / 2)) encroachment = np.concatenate([[0], d]) + np.concatenate([d, [0]]) if np.any(encroachment > ds): raise ValueError( '[PHIDL] smooth(): Not enough distance between points to to fit curves. Try reducing the radius or spacing the points out farther' ) p1 = points[1:-1, :] - normals[:-1, :] * d[:, np.newaxis] # Move arcs into position new_points = [] new_points.append([points[0, :]]) for n, dt in enumerate(dtheta): P = paths[n] P.rotate(theta[n] - 0) P.move(p1[n]) new_points.append(P.points) new_points.append([points[-1, :]]) new_points = np.concatenate(new_points) P = Path() P.rotate(theta[0]) P.append(new_points) P.move(points[0, :]) return P
def extrude( p: Path, cross_section: Optional[CrossSectionOrFactory] = None, layer: Optional[Layer] = None, width: Optional[float] = None, widths: Optional[Float2] = None, simplify: Optional[float] = None, ) -> Component: """Returns Component extruding a Path with a cross_section. A path can be extruded using any CrossSection returning a Component The CrossSection defines the layer numbers, widths and offsetts adapted from phidl.path Args: p: a path is a list of points (arc, straight, euler) cross_section: to extrude layer: width: widths: tuple of starting and end width simplify: Tolerance value for the simplification algorithm. All points that can be removed without changing the resulting polygon by more than the value listed here will be removed. """ if cross_section is None and layer is None: raise ValueError("CrossSection or layer needed") if cross_section is not None and layer is not None: raise ValueError("Define only CrossSection or layer") if layer is not None and width is None and widths is None: raise ValueError("Need to define layer width or widths") elif width: cross_section = CrossSection() cross_section.add(width=width, layer=layer) elif widths: cross_section = CrossSection() cross_section.add(width=_linear_transition(widths[0], widths[1]), layer=layer) xsection_points = [] c = Component() cross_section = cross_section() if callable(cross_section) else cross_section snap_to_grid = cross_section.info.get("snap_to_grid", None) for section in cross_section.sections: width = section["width"] offset = section["offset"] layer = section["layer"] ports = section["ports"] port_types = section["port_types"] hidden = section["hidden"] if isinstance(width, (int, float)) and isinstance(offset, (int, float)): xsection_points.append([width, offset]) if isinstance(layer, int): layer = (layer, 0) if ( isinstance(layer, Iterable) and len(layer) == 2 and isinstance(layer[0], int) and isinstance(layer[1], int) ): xsection_points.append([layer[0], layer[1]]) if callable(offset): P_offset = p.copy() P_offset.offset(offset) points = P_offset.points start_angle = P_offset.start_angle end_angle = P_offset.end_angle offset = 0 else: points = p.points start_angle = p.start_angle end_angle = p.end_angle if callable(width): # Compute lengths dx = np.diff(p.points[:, 0]) dy = np.diff(p.points[:, 1]) lengths = np.cumsum(np.sqrt((dx) ** 2 + (dy) ** 2)) lengths = np.concatenate([[0], lengths]) width = width(lengths / lengths[-1]) else: pass points1 = p._centerpoint_offset_curve( points, offset_distance=offset + width / 2, start_angle=start_angle, end_angle=end_angle, ) points2 = p._centerpoint_offset_curve( points, offset_distance=offset - width / 2, start_angle=start_angle, end_angle=end_angle, ) # Simplify lines using the Ramer–Douglas–Peucker algorithm if isinstance(simplify, bool): raise ValueError( "[PHIDL] the simplify argument must be a number (e.g. 1e-3) or None" ) if simplify is not None: points1 = _simplify(points1, tolerance=simplify) points2 = _simplify(points2, tolerance=simplify) if snap_to_grid: snap_to_grid_nm = snap_to_grid * 1e3 points1 = ( snap_to_grid_nm * np.round(np.array(points1) * 1e3 / snap_to_grid_nm) / 1e3 ) points2 = ( snap_to_grid_nm * np.round(np.array(points2) * 1e3 / snap_to_grid_nm) / 1e3 ) # Join points together points = np.concatenate([points1, points2[::-1, :]]) layers = layer if hidden else [layer, layer] if not hidden and p.length() > 1e-3: c.add_polygon(points, layer=layer) # Add ports if they were specified if ports[0] is not None: orientation = (p.start_angle + 180) % 360 _width = width if np.isscalar(width) else width[0] new_port = c.add_port( name=ports[0], layer=layers[0], port_type=port_types[0], width=_width, orientation=orientation, cross_section=cross_section.cross_sections[0] if hasattr(cross_section, "cross_sections") else cross_section, ) new_port.endpoints = (points1[0], points2[0]) if ports[1] is not None: orientation = (p.end_angle + 180) % 360 _width = width if np.isscalar(width) else width[-1] new_port = c.add_port( name=ports[1], layer=layers[1], port_type=port_types[1], width=_width, orientation=orientation, cross_section=cross_section.cross_sections[1] if hasattr(cross_section, "cross_sections") else cross_section, ) new_port.endpoints = (points2[-1], points1[-1]) points = np.concatenate((p.points, np.array(xsection_points))) c.name = f"path_{hash_points(points)[:26]}" # c.path = path # c.cross_section = cross_section return c