Coverage for trimesh/path/curve.py: 87%
47 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
1import numpy as np
3from ..constants import res_path as res
4from ..constants import tol_path as tol
5from ..typed import Integer
8def discretize_bezier(points, count=None, scale=1.0):
9 """
10 Parameters
11 ----------
12 points : (order, dimension) float
13 Control points of the bezier curve
14 For a 2D cubic bezier, order=3, dimension=2
15 count : int, or None
16 Number of segments
17 scale : float
18 Scale of curve
19 Returns
20 ----------
21 discrete: (n, dimension) float
22 Points forming a a polyline representation
23 """
24 # make sure we have a numpy array
25 points = np.asanyarray(points, dtype=np.float64)
27 if count is None:
28 # how much distance does a small percentage of the curve take
29 # this is so we can figure out how finely we have to sample t
30 norm = np.linalg.norm(np.diff(points, axis=0), axis=1).sum()
31 count = np.ceil(norm / (res.seg_frac * scale))
32 count = int(
33 np.clip(count, res.min_sections * len(points), res.max_sections * len(points))
34 )
35 count = int(count)
37 # parameterize incrementing 0.0 - 1.0
38 t = np.linspace(0.0, 1.0, count)
39 # decrementing 1.0-0.0
40 t_d = 1.0 - t
41 n = len(points) - 1
42 # binomial coefficients, i, and each point
43 iterable = zip(binomial(n), np.arange(len(points)), points)
44 # run the actual interpolation
45 stacked = [
46 ((t**i) * (t_d ** (n - i))).reshape((-1, 1)) * p * c for c, i, p in iterable
47 ]
48 result = np.sum(stacked, axis=0)
50 # a bezier curve always starts and ends on control points
51 if tol.strict:
52 # test to make sure end points are correct
53 test = np.sum((result[[0, -1]] - points[[0, -1]]) ** 2, axis=1)
54 assert (test < tol.merge).all()
55 assert len(result) >= 2
57 # snap the first and last points to the exact control point
58 result[[0, -1]] = points[[0, -1]]
60 return result
63def discretize_bspline(control, knots, count=None, scale=1.0):
64 """
65 Given a B-Splines control points and knot vector, return
66 a sampled version of the curve.
68 Parameters
69 ----------
70 control : (o, d) float
71 Control points of the b- spline
72 knots : (j,) float
73 B-spline knots
74 count : int
75 Number of line segments to discretize the spline
76 If not specified will be calculated as something reasonable
78 Returns
79 ----------
80 discrete : (count, dimension) float
81 Points on a polyline version of the B-spline
82 """
84 # evaluate the b-spline using scipy/fitpack
85 from scipy.interpolate import splev
87 # (n, d) control points where d is the dimension of vertices
88 control = np.asanyarray(control, dtype=np.float64)
89 degree = len(knots) - len(control) - 1
90 if count is None:
91 norm = np.linalg.norm(np.diff(control, axis=0), axis=1).sum()
92 count = int(
93 np.clip(
94 norm / (res.seg_frac * scale),
95 res.min_sections * len(control),
96 res.max_sections * len(control),
97 )
98 )
100 ipl = np.linspace(knots[0], knots[-1], count)
101 discrete = splev(ipl, [knots, control.T, degree])
102 discrete = np.column_stack(discrete)
104 return discrete
107def binomial(n: Integer) -> list:
108 """
109 Return all binomial coefficients for a given order.
111 For n > 5, scipy.special.binom is used, below we hardcode.
113 Parameters
114 --------------
115 n : int
116 Order of binomial
118 Returns
119 ---------------
120 binom : (n + 1,) int
121 Binomial coefficients of a given order
122 """
123 if n == 1:
124 return [1, 1]
125 elif n == 2:
126 return [1, 2, 1]
127 elif n == 3:
128 return [1, 3, 3, 1]
129 elif n == 4:
130 return [1, 4, 6, 4, 1]
131 elif n == 5:
132 return [1, 5, 10, 10, 5, 1]
133 else:
134 from scipy.special import binom
136 return binom(n, np.arange(n + 1))