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
Example #2
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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
Example #8
0
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
Example #13
0
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
Example #14
0
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