Source code for gdsfactory.components.bezier

from __future__ import annotations

import numpy as np
from numpy import ndarray

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.config import ErrorType
from gdsfactory.geometry.functions import angles_deg, curvature, path_length, snap_angle
from gdsfactory.typings import (
    Coordinate,
    Coordinates,
    CrossSectionSpec,
)


def bezier_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.
    """
    from scipy.special import binom

    xs = 0.0
    ys = 0.0
    n = len(control_points) - 1
    for k in range(n + 1):
        ank = binom(n, k) * (1 - t) ** (n - k) * t**k
        xs += ank * control_points[k][0]
        ys += ank * control_points[k][1]

    return np.column_stack([xs, ys])


[docs] @gf.cell def bezier( 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) if with_manhattan_facing_angles: path.start_angle = start_angle or snap_angle(path.start_angle) path.end_angle = end_angle or snap_angle(path.end_angle) c = Component() bend = path.extrude(xs) bend_ref = c << bend c.add_ports(bend_ref.ports) c.absorb(bend_ref) curv = curvature(path_points, t) length = gf.snap.snap_to_grid(path_length(path_points)) if max(np.abs(curv)) == 0: min_bend_radius = np.inf else: 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_radius c.info["start_angle"] = path.start_angle c.info["end_angle"] = path.end_angle xs.validate_radius(min_bend_radius, bend_radius_error_type) return c
def find_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. """ from scipy.optimize import minimize t = np.linspace(0, 1, npoints) def array_1d_to_cpts(a): xs = a[::2] ys = a[1::2] return list(zip(xs, ys)) def objective_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_angle return angle_mismatch * alpha + max_curv x0, y0 = start_point[0], start_point[1] xn, yn = end_point[0], end_point[1] initial_guess = [] for i in range(nb_pts): x = (i + 1) * (x0 + xn) / nb_pts y = (i + 1) * (y0 + yn) / nb_pts initial_guess += [x, y] # initial_guess = [(x0 + xn) / 2, y0, (x0 + xn) / 2, yn] res = minimize(objective_func, initial_guess, method="Nelder-Mead") p = res.x return [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)