Source code for aerocaps.geom.nurbs_purepython

"""
Pure-python implementation of NURBS evaluation (no :obj:`numpy`).

.. warning::

    The functions in this module are purely for comparison purposes and calling of these functions from higher-level
    functions or methods is discouraged since the much faster :obj:`rust_nurbs` library is available.
"""
from decimal import Decimal
from math import factorial
from typing import List

__all__ = [
    "bernstein_poly",
    "bezier_curve_eval",
    "bezier_surf_eval",
    "bezier_surf_eval_grid",
    "rational_bezier_curve_eval",
    "rational_bezier_surf_eval",
    "rational_bezier_surf_eval_grid",
    "bspline_curve_eval",
    "bspline_surf_eval",
    "bspline_surf_eval_grid",
    "nurbs_curve_eval",
    "nurbs_surf_eval",
    "nurbs_surf_eval_grid"
]


[docs] def nchoosek(n: int, k: int): r""" Computes the mathematical combination .. math:: n \choose k Parameters ---------- n: int Number of elements in the set k: int Number of items to select from the set Returns ------- :math:`n \choose k` """ return float(Decimal(factorial(n)) / Decimal(factorial(k)) / Decimal(factorial(n - k)))
[docs] def bernstein_poly(n: int, i: int, t: float) -> float: r""" Evaluates the Bernstein polynomial at a single :math:`t`-value. The Bernstein polynomial is given by .. math:: B_{i,n}(t)={n \choose i} t^i (1-t)^{n-i} Parameters ---------- n: int Degree of the polynomial i: int Index t: float Parameter value :math:`t` at which to evaluate Returns ------- float Value of the Bernstein polynomial at :math:`t` """ if not 0 <= i <= n: return 0.0 return nchoosek(n, i) * t ** i * (1.0 - t) ** (n - i)
[docs] def bezier_curve_eval(p: List[List[float]], t: float) -> List[float]: r""" Evaluates a Bézier curve with :math:`n+1` control points at a single :math:`t`-value according to .. math:: \mathbf{C}(t) = \sum\limits_{i=0}^n B_{i,n}(t) \mathbf{P}_i where :math:`B_{i,n}(t)` is the Bernstein polynomial. Parameters ---------- p: List[List[float]] 2-D list or array of control points where the inner dimension can have any size, but typical sizes include ``2`` (:math:`x`-:math:`y` space), ``3`` (:math:`x`-:math:`y`-:math:`z` space) and ``4`` (:math:`x`-:math:`y`-:math:`z`-:math:`w` space) t: float Parameter value :math:`t` at which to evaluate Returns ------- List[float] Value of the Bézier curve at :math:`t`. Has the same size as the inner dimension of ``p`` """ n = len(p) - 1 dim = len(p[0]) evaluated_point = [0.0] * dim for i in range(n + 1): b_poly = bernstein_poly(n, i, t) for j in range(dim): evaluated_point[j] += p[i][j] * b_poly return evaluated_point
[docs] def bezier_surf_eval(p: List[List[List[float]]], u: float, v: float) -> List[float]: r""" Evaluates a Bézier surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at a :math:`(u,v)` parameter pair according to .. math:: \mathbf{S}(u,v) = \sum\limits_{i=0}^n \sum\limits_{j=0}^m B_{i,n}(u) B_{j,m}(v) \mathbf{P}_{i,j} Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but typical sizes include ``2`` (:math:`x`-:math:`y` space), ``3`` (:math:`x`-:math:`y`-:math:`z` space) and ``4`` (:math:`x`-:math:`y`-:math:`z`-:math:`w` space) u: float Parameter value in the :math:`u`-direction at which to evaluate the surface v: float Parameter value in the :math:`v`-direction at which to evaluate the surface Returns ------- List[float] Value of the Bézier surface at :math:`(u,v)`. Has the same size as the innermost dimension of ``p`` """ n = len(p) - 1 m = len(p[0]) - 1 dim = len(p[0][0]) evaluated_point = [0.0] * dim for i in range(n + 1): b_poly_u = bernstein_poly(n, i, u) for j in range(m + 1): b_poly_v = bernstein_poly(m, j, v) b_poly_prod = b_poly_u * b_poly_v for k in range(dim): evaluated_point[k] += p[i][j][k] * b_poly_prod return evaluated_point
[docs] def bezier_surf_eval_grid(p: List[List[List[float]]], nu: int, nv: int) -> List[List[List[float]]]: r""" Evaluates a Bézier surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at :math:`N_u \times N_v` points along a linearly-spaced rectangular grid in :math:`(u,v)`-space according to .. math:: \mathbf{S}(u,v) = \sum\limits_{i=0}^n \sum\limits_{j=0}^m B_{i,n}(u) B_{j,m}(v) \mathbf{P}_{i,j} Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but typical sizes include ``2`` (:math:`x`-:math:`y` space), ``3`` (:math:`x`-:math:`y`-:math:`z` space) and ``4`` (:math:`x`-:math:`y`-:math:`z`-:math:`w` space) nu: int Number of linearly-spaced points in the :math:`u`-direction. E.g., ``nu=3`` outputs the evaluation of the surface at :math:`u=0.0`, :math:`u=0.5`, and :math:`u=1.0`. nv: int Number of linearly-spaced points in the :math:`v`-direction. E.g., ``nv=3`` outputs the evaluation of the surface at :math:`v=0.0`, :math:`v=0.5`, and :math:`v=1.0`. Returns ------- List[List[List[float]]] Values of :math:`N_u \times N_v` points on the Bézier surface at :math:`(u,v)`. Output array has size :math:`N_u \times N_v \times d`, where :math:`d` is the spatial dimension (usually either ``2``, ``3``, or ``4``) """ n = len(p) - 1 m = len(p[0]) - 1 dim = len(p[0][0]) evaluated_points = [[[0.0] * dim] * nv] * nu for u_idx in range(nu): u = float(u_idx) * 1.0 / (float(nu) - 1.0) for v_idx in range(nv): v = float(v_idx) * 1.0 / (float(nv) - 1.0) for i in range(n + 1): b_poly_u = bernstein_poly(n, i, u) for j in range(m + 1): b_poly_v = bernstein_poly(m, j, v) b_poly_prod = b_poly_u * b_poly_v for k in range(dim): evaluated_points[u_idx][v_idx][k] += p[i][j][k] * b_poly_prod return evaluated_points
[docs] def rational_bezier_curve_eval(p: List[List[float]], w: List[float], t: float) -> List[float]: r""" Evaluates a rational Bézier curve with :math:`n+1` control points at a single :math:`t`-value according to .. math:: \mathbf{C}(t) = \frac{\sum_{i=0}^n B_{i,n}(t) w_i \mathbf{P}_i}{\sum_{i=0}^n B_{i,n}(t) w_i} where :math:`B_{i,n}(t)` is the Bernstein polynomial. Parameters ---------- p: List[List[float]] 2-D list or array of control points where the inner dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[float] 1-D list or array of weights corresponding to each of control points. Must have length equal to the outer dimension of ``p``. t: float Parameter value :math:`t` at which to evaluate Returns ------- List[float] Value of the rational Bézier curve at :math:`t`. Has the same size as the inner dimension of ``p`` """ n = len(p) - 1 dim = len(p[0]) evaluated_point = [0.0] * dim w_sum = 0.0 for i in range(n + 1): b_poly = bernstein_poly(n, i, t) w_sum += w[i] * b_poly for j in range(dim): evaluated_point[j] += p[i][j] * w[i] * b_poly for j in range(dim): evaluated_point[j] /= w_sum return evaluated_point
[docs] def rational_bezier_surf_eval(p: List[List[List[float]]], w: List[List[float]], u: float, v: float) -> List[float]: r""" Evaluates a rational Bézier surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at a :math:`(u,v)` parameter pair according to .. math:: \mathbf{S}(u,v) = \frac{\sum_{i=0}^n \sum_{j=0}^m B_{i,n}(u) B_{j,m}(v) w_{i,j} \mathbf{P}_{i,j}}{\sum_{i=0}^n \sum_{j=0}^m B_{i,n}(u) B_{j,m}(v) w_{i,j}} Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[List[float]] 2-D list or array of weights corresponding to each of control points. The size of the array must be equal to the size of the first two dimensions of ``p`` (:math:`n+1 \times m+1`) u: float Parameter value in the :math:`u`-direction at which to evaluate the surface v: float Parameter value in the :math:`v`-direction at which to evaluate the surface Returns ------- List[float] Value of the rational Bézier surface at :math:`(u,v)`. Has the same size as the innermost dimension of ``p`` """ n = len(p) - 1 m = len(p[0]) - 1 dim = len(p[0][0]) evaluated_point = [0.0] * dim w_sum = 0.0 for i in range(n + 1): b_poly_u = bernstein_poly(n, i, u) for j in range(m + 1): b_poly_v = bernstein_poly(m, j, v) b_poly_prod = b_poly_u * b_poly_v w_sum += w[i][j] * b_poly_prod for k in range(dim): evaluated_point[k] += p[i][j][k] * w[i][j] * b_poly_prod for k in range(dim): evaluated_point[k] /= w_sum return evaluated_point
[docs] def rational_bezier_surf_eval_grid(p: List[List[List[float]]], w: List[List[float]], nu: int, nv: int) -> List[List[List[float]]]: r""" Evaluates a rational Bézier surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at :math:`N_u \times N_v` points along a linearly-spaced rectangular grid in :math:`(u,v)`-space according to .. math:: \mathbf{S}(u,v) = \frac{\sum_{i=0}^n \sum_{j=0}^m B_{i,n}(u) B_{j,m}(v) w_{i,j} \mathbf{P}_{i,j}}{\sum_{i=0}^n \sum_{j=0}^m B_{i,n}(u) B_{j,m}(v) w_{i,j}} Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[List[float]] 2-D list or array of weights corresponding to each of control points. The size of the array must be equal to the size of the first two dimensions of ``p`` (:math:`n+1 \times m+1`) nu: int Number of linearly-spaced points in the :math:`u`-direction. E.g., ``nu=3`` outputs the evaluation of the surface at :math:`u=0.0`, :math:`u=0.5`, and :math:`u=1.0`. nv: int Number of linearly-spaced points in the :math:`v`-direction. E.g., ``nv=3`` outputs the evaluation of the surface at :math:`v=0.0`, :math:`v=0.5`, and :math:`v=1.0`. Returns ------- List[List[List[float]]] Values of :math:`N_u \times N_v` points on the rational Bézier surface at :math:`(u,v)`. Output array has size :math:`N_u \times N_v \times d`, where :math:`d` is the spatial dimension (usually ``3``) """ n = len(p) - 1 m = len(p[0]) - 1 dim = len(p[0][0]) evaluated_points = [[[0.0] * dim] * nv] * nu for u_idx in range(nu): u = float(u_idx) * 1.0 / (float(nu) - 1.0) for v_idx in range(nv): v = float(v_idx) * 1.0 / (float(nv) - 1.0) w_sum = 0.0 for i in range(n + 1): b_poly_u = bernstein_poly(n, i, u) for j in range(m + 1): b_poly_v = bernstein_poly(m, j, v) b_poly_prod = b_poly_u * b_poly_v w_sum += w[i][j] * b_poly_prod for k in range(dim): evaluated_points[u_idx][v_idx][k] += p[i][j][k] * w[i][j] * b_poly_prod for k in range(dim): evaluated_points[u_idx][v_idx][k] /= w_sum return evaluated_points
def _get_possible_span_indices(k: List[float]) -> List[int]: """ Gets the list of possible knot span indices (those that have a non-zero width) Parameters ---------- k: List[float] Knot vector Returns ------- List[int] Possible knot span indices (each value represents the index at the start of the knot span) """ possible_span_indices = [] num_knots = len(k) for i in range(num_knots - 1): if k[i] == k[i + 1]: continue possible_span_indices.append(i) return possible_span_indices def _find_span(k: List[float], possible_span_indices: List[int], t: float) -> int: """ Finds the knot span on which the parameter value :math:`t` lies Parameters ---------- k: List[float] Knot vector possible_span_indices: List[int] List of possible span indices along which :math:`t` can lie (usually called from ``_get_possible_span_indices``) t: float Parameter value :math:`t` Returns ------- int Index corresponding to the start of the knot span on which the parameter value :math:`t` lies """ for knot_span_idx in possible_span_indices: if k[knot_span_idx] <= t < k[knot_span_idx + 1]: return knot_span_idx if t == k[-1]: return possible_span_indices[-1] raise ValueError(f"Parameter value {t = } out of bounds for knot vector with " f"first knot {k[0]} and last knot {k[-1]}") def _cox_de_boor(k: List[float], possible_span_indices: List[int], degree: int, i: int, t: float) -> float: """ Implements the Cox de Boor algorithm for computing the B-spline basis function Parameters ---------- k: List[float] Knot vector possible_span_indices: List[int] List of possible span indices along which :math:`t` can lie (usually called from ``_get_possible_span_indices``) degree: int B-spline basis function degree i: int B-spline basis index t: float Parameter value :math:`t` Returns ------- float Value of the B-spline basis function at a particular value of :math:`t` """ if degree == 0: if i in possible_span_indices and _find_span(k, possible_span_indices, t) == i: return 1.0 return 0.0 f = 0.0 g = 0.0 if k[i + degree] - k[i] != 0.0: f = (t - k[i]) / (k[i + degree + 1]) if k[i + degree + 1] - k[i + 1] != 0.0: g = (k[i + degree + 1] - t) / (k[i + degree + 1] - k[i + 1]) if f == 0.0 and g == 0.0: return 0.0 if g == 0.0: return f * _cox_de_boor(k, possible_span_indices, degree - 1, i, t) if f == 0.0: return g * _cox_de_boor(k, possible_span_indices, degree - 1, i + 1, t) return f * _cox_de_boor(k, possible_span_indices, degree - 1, i, t) + g * _cox_de_boor( k, possible_span_indices, degree - 1, i + 1, t)
[docs] def bspline_curve_eval(p: List[List[float]], k: List[float], t: float) -> List[float]: r""" Evaluates a B-spline curve with :math:`n+1` control points at a single :math:`t`-value according to .. math:: \mathbf{C}(t) = \sum\limits_{i=0}^n N_{i,q}(t) \mathbf{P}_i where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`, defined recursively as .. math:: N_{i,q} = \frac{t - t_i}{t_{i+q} - t_i} N_{i,q-1}(t) + \frac{t_{i+q+1} - t}{t_{i+q+1} - t_{i+1}} N_{i+1, q-1}(t) with base case .. math:: N_{i,0} = \begin{cases} 1, & \text{if } t_i \leq t < t_{i+1} \text{ and } t_i < t_{i+1} \\ 0, & \text{otherwise} \end{cases} The degree of the B-spline is computed as ``q = k.len() - len(p) - 1``. Parameters ---------- p: List[List[float]] 2-D list or array of control points where the inner dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) k: List[float] 1-D list or array of knots t: float Parameter value :math:`t` at which to evaluate Returns ------- List[float] Value of the B-spline curve at :math:`t`. Has the same size as the inner dimension of ``p`` """ n = len(p) - 1 num_knots = len(k) q = num_knots - n - 2 possible_span_indices = _get_possible_span_indices(k) dim = len(p[0]) evaluated_point = [0.0] * dim for i in range(n + 1): bspline_basis = _cox_de_boor(k, possible_span_indices, q, i, t) for j in range(dim): evaluated_point[j] += p[i][j] * bspline_basis return evaluated_point
[docs] def bspline_surf_eval(p: List[List[List[float]]], ku: List[float], kv: List[float], u: float, v: float) -> List[float]: r""" Evaluates a B-spline surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at a :math:`(u,v)` parameter pair according to .. math:: \mathbf{S}(u,v) = \sum\limits_{i=0}^n \sum\limits_{j=0}^m N_{i,q}(u) N_{j,r}(v) \mathbf{P}_{i,j} where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`. The degree of the B-spline in the :math:`u`-direction is computed as ``q = len(ku) - len(p) - 1``, and the degree of the B-spline surface in the :math:`v`-direction is computed as ``r = len(kv) - len(p[0]) - 1``. Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) ku: List[float] 1-D list or array of knots in the :math:`u`-parametric direction kv: List[float] 1-D list or array of knots in the :math:`v`-parametric direction u: float Parameter value in the :math:`u`-direction at which to evaluate the surface v: float Parameter value in the :math:`v`-direction at which to evaluate the surface Returns ------- List[float] Value of the B-spline surface at :math:`(u,v)`. Has the same size as the innermost dimension of ``p`` """ n = len(p) - 1 # Number of control points in the u-direction minus 1 m = len(p[0]) - 1 # Number of control points in the v-direction minus 1 num_knots_u = len(ku) # Number of knots in the u-direction num_knots_v = len(kv) # Number of knots in the v-direction q = num_knots_u - n - 2 # Degree in the u-direction r = num_knots_v - m - 2 # Degree in the v-direction possible_span_indices_u = _get_possible_span_indices(ku) possible_span_indices_v = _get_possible_span_indices(kv) dim = len(p[0][0]) # Number of spatial dimensions evaluated_point = [0.0] * dim for i in range(n + 1): bspline_basis_u = _cox_de_boor(ku, possible_span_indices_u, q, i, u) for j in range(m + 1): bspline_basis_v = _cox_de_boor(kv, possible_span_indices_v, r, j, v) bspline_basis_prod = bspline_basis_u * bspline_basis_v for k in range(dim): evaluated_point[k] += p[i][j][k] * bspline_basis_prod return evaluated_point
[docs] def bspline_surf_eval_grid(p: List[List[List[float]]], ku: List[float], kv: List[float], nu: int, nv: int) -> List[List[List[float]]]: r""" Evaluates a B-spline surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at :math:`N_u \times N_v` points along a linearly-spaced rectangular grid in :math:`(u,v)`-space according to .. math:: \mathbf{S}(u,v) = \sum\limits_{i=0}^n \sum\limits_{j=0}^m N_{i,q}(u) N_{j,r}(v) \mathbf{P}_{i,j} where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`. The degree of the B-spline in the :math:`u`-direction is computed as ``q = len(ku) - len(p) - 1``, and the degree of the B-spline surface in the :math:`v`-direction is computed as ``r = len(kv) - len(p[0]) - 1``. Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) ku: List[float] 1-D list or array of knots in the :math:`u`-parametric direction kv: List[float] 1-D list or array of knots in the :math:`v`-parametric direction nu: int Number of linearly-spaced points in the :math:`u`-direction. E.g., ``nu=3`` outputs the evaluation of the surface at :math:`u=0.0`, :math:`u=0.5`, and :math:`u=1.0`. nv: int Number of linearly-spaced points in the :math:`v`-direction. E.g., ``nv=3`` outputs the evaluation of the surface at :math:`v=0.0`, :math:`v=0.5`, and :math:`v=1.0`. Returns ------- List[List[List[float]]] Values of :math:`N_u \times N_v` points on the B-spline surface at :math:`(u,v)`. Output array has size :math:`N_u \times N_v \times d`, where :math:`d` is the spatial dimension (usually ``3``) """ n = len(p) - 1 # Number of control points in the u-direction minus 1 m = len(p[0]) - 1 # Number of control points in the v-direction minus 1 num_knots_u = len(ku) # Number of knots in the u-direction num_knots_v = len(kv) # Number of knots in the v-direction q = num_knots_u - n - 2 # Degree in the u-direction r = num_knots_v - m - 2 # Degree in the v-direction possible_span_indices_u = _get_possible_span_indices(ku) possible_span_indices_v = _get_possible_span_indices(kv) dim = len(p[0][0]) # Number of spatial dimensions evaluated_points = [[[0.0] * dim] * nv] * nu for u_idx in range(nu): u = float(u_idx) * 1.0 / (float(nu) - 1.0) for v_idx in range(nv): v = float(v_idx) * 1.0 / (float(nv) - 1.0) for i in range(n + 1): bspline_basis_u = _cox_de_boor(ku, possible_span_indices_u, q, i, u) for j in range(m + 1): bspline_basis_v = _cox_de_boor(kv, possible_span_indices_v, r, j, v) bspline_basis_prod = bspline_basis_u * bspline_basis_v for k in range(dim): evaluated_points[u_idx][v_idx][k] += p[i][j][k] * bspline_basis_prod return evaluated_points
[docs] def nurbs_curve_eval(p: List[List[float]], w: List[float], k: List[float], t: float) -> List[float]: r""" Evaluates a Non-Uniform Rational B-Spline (NURBS) curve with :math:`n+1` control points at a single :math:`t`-value according to .. math:: \mathbf{C}(t) = \frac{\sum_{i=0}^n N_{i,q}(t) w_i \mathbf{P}_i}{\sum_{i=0}^n N_{i,q}(t) w_i} where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`. The degree of the B-spline is computed as ``q = len(k) - len(p) - 1``. Parameters ---------- p: List[List[float]] 2-D list or array of control points where the inner dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[float] 1-D list or array of weights corresponding to each of control points. Must have length equal to the outer dimension of ``p``. k: List[float] 1-D list or array of knots t: float Parameter value :math:`t` at which to evaluate Returns ------- List[float] Value of the NURBS curve at :math:`t`. Has the same size as the inner dimension of ``p`` """ n = len(p) - 1 # Number of control points minus 1 num_knots = len(k) q = num_knots - n - 2 possible_span_indices = _get_possible_span_indices(k) dim = len(p[0]) evaluated_point = [0.0] * dim w_sum = 0.0 for i in range(n + 1): bspline_basis = _cox_de_boor(k, possible_span_indices, q, i, t) w_sum += w[i] * bspline_basis for j in range(dim): evaluated_point[j] += p[i][j] * w[i] * bspline_basis for j in range(dim): evaluated_point[j] /= w_sum return evaluated_point
[docs] def nurbs_surf_eval(p: List[List[List[float]]], w: List[List[float]], ku: List[float], kv: List[float], u: float, v: float) -> List[float]: r""" Evaluates a Non-Uniform Rational B-Spline (NURBS) surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at a :math:`(u,v)` parameter pair according to .. math:: \mathbf{S}(u,v) = \frac{\sum_{i=0}^n \sum_{j=0}^m N_{i,q}(u) N_{j,r}(v) w_{i,j} \mathbf{P}_{i,j}}{\sum_{i=0}^n \sum_{j=0}^m N_{i,q}(u) N_{j,r}(v) w_{i,j}} where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`. The degree of the B-spline in the :math:`u`-direction is computed as ``q = len(ku) - len(p) - 1``, and the degree of the B-spline surface in the :math:`v`-direction is computed as ``r = len(kv) - len(p[0]) - 1``. Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[List[float]] 2-D list or array of weights corresponding to each of control points. The size of the array must be equal to the size of the first two dimensions of ``p`` (:math:`n+1 \times m+1`) ku: List[float] 1-D list or array of knots in the :math:`u`-parametric direction kv: List[float] 1-D list or array of knots in the :math:`v`-parametric direction u: float Parameter value in the :math:`u`-direction at which to evaluate the surface v: float Parameter value in the :math:`v`-direction at which to evaluate the surface Returns ------- List[float] Value of the NURBS surface at :math:`(u,v)`. Has the same size as the innermost dimension of ``p`` """ n = len(p) - 1 # Number of control points in the u-direction minus 1 m = len(p[0]) - 1 # Number of control points in the v-direction minus 1 num_knots_u = len(ku) # Number of knots in the u-direction num_knots_v = len(kv) # Number of knots in the v-direction q = num_knots_u - n - 2 # Degree in the u-direction r = num_knots_v - m - 2 # Degree in the v-direction possible_span_indices_u = _get_possible_span_indices(ku) possible_span_indices_v = _get_possible_span_indices(kv) dim = len(p[0][0]) # Number of spatial dimensions evaluated_point = [0.0] * dim w_sum = 0.0 for i in range(n + 1): bspline_basis_u = _cox_de_boor(ku, possible_span_indices_u, q, i, u) for j in range(m + 1): bspline_basis_v = _cox_de_boor(kv, possible_span_indices_v, r, j, v) bspline_basis_prod = bspline_basis_u * bspline_basis_v w_sum += w[i][j] * bspline_basis_prod for k in range(dim): evaluated_point[k] += p[i][j][k] * w[i][j] * bspline_basis_prod for k in range(dim): evaluated_point[k] /= w_sum return evaluated_point
[docs] def nurbs_surf_eval_grid(p: List[List[List[float]]], w: List[List[float]], ku: List[float], kv: List[float], nu: int, nv: int) -> List[List[List[float]]]: r""" Evaluates a Non-Uniform Rational B-Spline (NURBS) surface with :math:`n+1` control points in the :math:`u`-direction and :math:`m+1` control points in the :math:`v`-direction at :math:`N_u \times N_v` points along a linearly-spaced rectangular grid in :math:`(u,v)`-space according to .. math:: \mathbf{S}(u,v) = \frac{\sum_{i=0}^n \sum_{j=0}^m N_{i,q}(u) N_{j,r}(v) w_{i,j} \mathbf{P}_{i,j}}{\sum_{i=0}^n \sum_{j=0}^m N_{i,q}(u) N_{j,r}(v) w_{i,j}} where :math:`N_{i,q}(t)` is the B-spline basis function of degree :math:`q`. The degree of the B-spline in the :math:`u`-direction is computed as ``q = len(ku) - len(p) - 1``, and the degree of the B-spline surface in the :math:`v`-direction is computed as ``r = len(kv) - len(p[0]) - 1``. Parameters ---------- p: List[List[List[float]]] 3-D list or array of control points where the innermost dimension can have any size, but the typical size is ``3`` (:math:`x`-:math:`y`-:math:`z` space) w: List[List[float]] 2-D list or array of weights corresponding to each of control points. The size of the array must be equal to the size of the first two dimensions of ``p`` (:math:`n+1 \times m+1`) ku: List[float] 1-D list or array of knots in the :math:`u`-parametric direction kv: List[float] 1-D list or array of knots in the :math:`v`-parametric direction nu: int Number of linearly-spaced points in the :math:`u`-direction. E.g., ``nu=3`` outputs the evaluation of the surface at :math:`u=0.0`, :math:`u=0.5`, and :math:`u=1.0`. nv: int Number of linearly-spaced points in the :math:`v`-direction. E.g., ``nv=3`` outputs the evaluation of the surface at :math:`v=0.0`, :math:`v=0.5`, and :math:`v=1.0`. Returns ------- List[List[List[float]]] Values of :math:`N_u \times N_v` points on the NURBS surface at :math:`(u,v)`. Output array has size :math:`N_u \times N_v \times d`, where :math:`d` is the spatial dimension (usually ``3``) """ n = len(p) - 1 # Number of control points in the u-direction minus 1 m = len(p[0]) - 1 # Number of control points in the v-direction minus 1 num_knots_u = len(ku) # Number of knots in the u-direction num_knots_v = len(kv) # Number of knots in the v-direction q = num_knots_u - n - 2 # Degree in the u-direction r = num_knots_v - m - 2 # Degree in the v-direction possible_span_indices_u = _get_possible_span_indices(ku) possible_span_indices_v = _get_possible_span_indices(kv) dim = len(p[0][0]) # Number of spatial dimensions evaluated_points = [[[0.0] * dim] * nv] * nu for u_idx in range(nu): u = float(u_idx) * 1.0 / (float(nu) - 1.0) for v_idx in range(nv): v = float(v_idx) * 1.0 / (float(nv) - 1.0) w_sum = 0.0 for i in range(n + 1): bspline_basis_u = _cox_de_boor(ku, possible_span_indices_u, q, i, u) for j in range(m + 1): bspline_basis_v = _cox_de_boor(kv, possible_span_indices_v, r, j, v) bspline_basis_prod = bspline_basis_u * bspline_basis_v w_sum += w[i][j] * bspline_basis_prod for k in range(dim): evaluated_points[u_idx][v_idx][k] += p[i][j][k] * w[i][j] * bspline_basis_prod for k in range(dim): evaluated_points[u_idx][v_idx][k] /= w_sum return evaluated_points