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

1import numpy as np 

2 

3from ..constants import res_path as res 

4from ..constants import tol_path as tol 

5from ..typed import Integer 

6 

7 

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) 

26 

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) 

36 

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) 

49 

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 

56 

57 # snap the first and last points to the exact control point 

58 result[[0, -1]] = points[[0, -1]] 

59 

60 return result 

61 

62 

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. 

67 

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 

77 

78 Returns 

79 ---------- 

80 discrete : (count, dimension) float 

81 Points on a polyline version of the B-spline 

82 """ 

83 

84 # evaluate the b-spline using scipy/fitpack 

85 from scipy.interpolate import splev 

86 

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 ) 

99 

100 ipl = np.linspace(knots[0], knots[-1], count) 

101 discrete = splev(ipl, [knots, control.T, degree]) 

102 discrete = np.column_stack(discrete) 

103 

104 return discrete 

105 

106 

107def binomial(n: Integer) -> list: 

108 """ 

109 Return all binomial coefficients for a given order. 

110 

111 For n > 5, scipy.special.binom is used, below we hardcode. 

112 

113 Parameters 

114 -------------- 

115 n : int 

116 Order of binomial 

117 

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 

135 

136 return binom(n, np.arange(n + 1))