bspline.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. # Created: 2012.01.03
  2. # Copyright (c) 2012-2018 Manfred Moitzi
  3. # License: MIT License
  4. """
  5. B-Splines
  6. =========
  7. https://www.cl.cam.ac.uk/teaching/2000/AGraphHCI/SMEG/node4.html
  8. n + 1 ... number of control points P_1, P_2, ..., P_{n+1} or P_0, P_1, ..., P_n
  9. k ... order of the B-spline, 2 <= k <= n + 1
  10. degree ... k - 1
  11. B-splines are a more general type of curve than Bezier curves. In a B-spline each control point is associated with a
  12. basis function.
  13. (87) P(t) = sum {i=1}^{n+1} N_{i,k}(t) P_i, t_min <= t < t_max
  14. There are n + 1 control points, P_1, P_2, ..., P_{n+1}. The N_{i,k} basis functions are of order k(degree k-1).
  15. k must be at least 2 (linear), and can be no more than n+1 (the number of control points). The important point here is
  16. that the order of the curve (linear, quadratic, cubic,...) is therefore not dependent on the number of control points
  17. (which it is for Bezier curves, where k must always equal n+1).
  18. Equation (87) defines a piecewise continuous function. A knot vector, (t_1, t_2, ..., t_{k+(n+1)}), must be specified.
  19. This determines the values of t at which the pieces of curve join, like knots joining bits of string. It is necessary
  20. that:
  21. (88) t_i <= t_{i+1}, for all i
  22. The N_{i,k} depend only on the value of k and the values in the knot vector. N is defined recursively as:
  23. (89) N_{i,1}(t) = 1 for t_i <= t < t_{i+1}; 0 otherwise
  24. N_{i,k}(t) = (t-t_i) / ({t_{i+k-1} - t_i}) * N_{i,k-1}(t) + (t_{i+k}-t) / (t_{i+k} - t_{i+1}) * N_{i+1,k-1}(t)
  25. This is essentially a modified version of the idea of taking linear interpolations of linear interpolations of linear
  26. interpolations ... n
  27. The Knot Vector
  28. ---------------
  29. The above explanation shows that the knot vector is very important. The knot vector can, by its definition, be any
  30. sequence of numbers provided that each one is greater than or equal to the preceding one. Some types of knot vector are
  31. more useful than others. Knot vectors are generally placed into one of three categories: uniform, open uniform, and
  32. non-uniform.
  33. Uniform Knot Vector
  34. ~~~~~~~~~~~~~~~~~~~
  35. These are knot vectors for which
  36. (90) t_{i+1} - t_i = constant, for all i
  37. e.g. [1, 2, 3, 4, 5, 6, 7, 8], [0, .25, .5, .75, 1.]
  38. Open Uniform Knot Vector
  39. ~~~~~~~~~~~~~~~~~~~~~~~~
  40. These are uniform knot vectors which have k equal knot values at each end
  41. (91) t_i = t_1, i <= k
  42. t_{i+1} - t_i = constant, k <= i < n+2
  43. t_i = t_{k+(n+1)}, i >= n + 2
  44. e.g. [0, 0, 0, 0, 1, 2, 3, 4, 4, 4, 4] for k=4,
  45. [1, 1, 1, 2, 3, 4, 5, 6, 6, 6] for k=3
  46. [.1, .1, .1, .1, .1, .3, .5, .7, .7, .7, .7, .7] for k=5
  47. Non-uniform Knot Vector
  48. ~~~~~~~~~~~~~~~~~~~~~~~
  49. This is the general case, the only constraint being the standard t_i <= t_{i+1}, for all i (Equations 88).
  50. e.g. [1, 3, 7, 22, 23, 23, 49, 50, 50]
  51. [1, 1, 1, 2, 2, 3, 4, 5, 6, 6, 6, 7, 7, 7]
  52. [.2, .7, .7, .7, 1.2, 1.2, 2.9, 3.6]
  53. The shapes of the N_{i,k} basis functions are determined entirely by the relative spacing between the knots.
  54. scaling: t_i' = alpha * t_i, for all i
  55. translating t_i'= t_i + delta t, for all i
  56. The knot vector has no effect on the shapes of the N_{i,k}.
  57. The above gives a description of the various types of knot vector but it doesn't really give you any insight into how
  58. the knot vector determines the shape of the curve. The following subsections look at the different types of knot vector
  59. in more detail. However, the best way to get to feel for these is to derive and draw the basis functions yourself.
  60. Uniform Knot Vector
  61. ~~~~~~~~~~~~~~~~~~~
  62. For simplicity, let t_i = i (this is allowable given that the scaling or translating the knot vector has no effect on
  63. the shapes of the N_{i,k}). The knot vector thus becomes [1,2,3, ... ,k+(n+1)] and equation (89) simplifies to:
  64. (92) N_{i,1}(t) = 1 for t_i <= t < t_{i+1}; 0 otherwise
  65. N_{i,k}(t) = (t-i)(k-1) * N_{i,k-1}(t) + (i+k-t)/ (k-1) * N_{i+1,k-1}(t)
  66. Things you can change about a uniform B-spline
  67. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  68. With a uniform B-spline, you obviously cannot change the basis functions (they are fixed because all the knots are
  69. equi-spaced). However you can alter the shape of the curve by modifying a number of things:
  70. Moving control points:
  71. Moving the control points obviously changes the shape of the curve.
  72. Multiple control points:
  73. Sticking two adjacent control points on top of one another causes the curve to pass closer to that point. Stick enough
  74. adjacent control points on top of one another and you can make the curve pass through that point.
  75. Order:
  76. Increasing the order k increases the continuity of the curve at the knots, increases the smoothness of the curve, and
  77. tends to move the curve farther from its defining polygon.
  78. Joining the ends:
  79. You can join the ends of the curve to make a closed loop. Say you have M points, P_1, ... P_M. You want a closed
  80. B-spline defined by these points. For a given order, k, you will need M+(k-1) control points (repeating the first k-1
  81. points): P_1, ... P_M, P_1, ..., P_{k-1}. Your knot vector will thus have M+2k-1 uniformly spaced knots.
  82. Open Uniform Knot Vector
  83. ~~~~~~~~~~~~~~~~~~~~~~~~
  84. The previous section intimated that uniform B-splines can be used to describe closed curves: all you have to do is join
  85. the ends as described above. If you do not want a closed curve, and you use a uniform knot vector, you find that you
  86. need to specify control points at each end of the curve which the curve doesn't go near.
  87. If you wish your B-spline to start and end at your first and last control points then you need an open uniform knot
  88. vector. The only difference between this and the uniform knot vector being that the open uniform version has k equal
  89. knots at each end.
  90. An order k open uniform B-spline with n+1=k points is the Bezier curve of order k.
  91. Non-uniform Knot Vector
  92. ~~~~~~~~~~~~~~~~~~~~~~~
  93. Any B-spline whose knot vector is neither uniform nor open uniform is non-uniform. Non-uniform knot vectors allow any
  94. spacing of the knots, including multiple knots (adjacent knots with the same value). We need to know how this
  95. non-uniform spacing affects the basis functions in order to understand where non-uniform knot vectors could be useful.
  96. It transpires that there are only three cases of any interest:
  97. 1. multiple knots (adjacent knots equal)
  98. 2. adjacent knots more closely spaced than the next knot in the vector
  99. 3. adjacent knots less closely spaced than the next knot in the vector
  100. Obviously, case (3) is simply case (2) turned the other way round.
  101. Multiple knots:
  102. A multiple knot reduces the degree of continuity at that knot value. Across a normal knot the continuity is Ck-2. Each
  103. extra knot with the same value reduces continuity at that value by one. This is the only way to reduce the continuity of
  104. the curve at the knot values. If there are k-1 (or more) equal knots then you get a discontinuity in the curve.
  105. Close knots:
  106. As two knots' values get closer together, relative to the spacing of the other knots, the curve moves closer to the
  107. related control point.
  108. Distant knots:
  109. As two knots' values get further apart, relative to the spacing of the other knots, the curve moves further away from
  110. the related control point.
  111. Use of Non-uniform Knot Vectors
  112. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113. Standard procedure is to use uniform or open uniform B-splines unless there is a very good reason not to do so.
  114. Moving two knots closer together tends to move the curve only slightly and so there is usually little point in doing it.
  115. This leads to the conclusion that the main use of non-uniform B-splines is to allow for multiple knots, which adjust the
  116. continuity of the curve at the knot values.
  117. However, non-uniform B-splines are the general form of the B-spline because they incorporate open uniform and uniform
  118. B-splines as special cases. Thus we will talk about non-uniform B-splines when we mean the general case, incorporating
  119. both uniform and open uniform.
  120. What can you do to control the shape of a B-spline?
  121. - Move the control points.
  122. - Add or remove control points.
  123. - Use multiple control points.
  124. - Change the order, k.
  125. - Change the type of knot vector.
  126. - Change the relative spacing of the knots.
  127. - Use multiple knot values in the knot vector.
  128. What should the defaults be?
  129. If there are no pressing reasons for doing otherwise, your B-spline should be defined as follows:
  130. - k=4 (cubic)
  131. - no multiple control points
  132. - uniform (for a closed curve) or open uniform (for an open curve) knot vector.
  133. Rational B-splines
  134. ==================
  135. https://www.cl.cam.ac.uk/teaching/2000/AGraphHCI/SMEG/node5.html:
  136. Rational B-splines have all of the properties of non-rational B-splines plus the following two useful features:
  137. They produce the correct results under projective transformations (while non-rational B-splines only produce the correct
  138. results under affine transformations).
  139. They can be used to represent lines, conics, non-rational B-splines; and, when generalised to patches, can represents
  140. planes, quadrics, and tori.
  141. The antonym of rational is non-rational. Non-rational B-splines are a special case of rational B-splines, just as
  142. uniform B-splines are a special case of non-uniform B-splines. Thus, non-uniform rational B-splines encompass almost
  143. every other possible 3D shape definition. Non-uniform rational B-spline is a bit of a mouthful and so it is generally
  144. abbreviated to NURBS.
  145. We have already learnt all about the the B-spline bit of NURBS and about the non-uniform bit. So now all we need to
  146. know is the meaning of the rational bit and we will fully(?) understand NURBS.
  147. Rational B-splines are defined simply by applying the B-spline equation (Equation 87) to homogeneous coordinates,
  148. rather than normal 3D coordinates.
  149. """
  150. from typing import List, Iterable, Sequence, TYPE_CHECKING, Dict, Tuple, Optional
  151. from .vector import Vector, distance
  152. from .matrix import Matrix
  153. from math import pow, isclose
  154. from ezdxf.lldxf.const import DXFValueError
  155. if TYPE_CHECKING:
  156. from ezdxf.eztypes import Vertex
  157. def knot_open_uniform(n: int, order: int) -> List[float]:
  158. """
  159. Returns a open uniform knot vector.
  160. Args:
  161. n: count of control points
  162. order: spline order
  163. Returns: list of floats (knot vector)
  164. """
  165. nplusc = n + order
  166. nplus2 = n + 2
  167. knots = [0.]
  168. for i in range(2, nplusc + 1):
  169. if (i > order) and (i < nplus2):
  170. knots.append(knots[-1] + 1.)
  171. else:
  172. knots.append(knots[-1])
  173. return knots
  174. def is_uniform_knots(knots: Sequence[float], places: int = 4) -> bool:
  175. deltas = set(round(k2 - k1, ndigits=places) for k1, k2 in zip(knots, knots[1:]))
  176. return len(deltas) == 1
  177. def knot_uniform(n: int, order: int) -> List[float]:
  178. """
  179. Returns a uniform knot vector.
  180. Args:
  181. n: count of control points
  182. order: spline order
  183. Returns: list of floats (knot vector)
  184. """
  185. return [float(knot_value) for knot_value in range(0, n + order)]
  186. def required_knot_values(count: int, order: int) -> int:
  187. # just to show the connections
  188. # count = count of control points = n + 1
  189. # k = order of spline = degree + 1
  190. # 2 <= k <= n + 1
  191. # p = degree
  192. # order = p + 1
  193. k = order
  194. n = count - 1
  195. p = k - 1
  196. if not (2 <= k <= (n + 1)):
  197. raise DXFValueError('Invalid count/order combination')
  198. # n + p + 2 = count + order
  199. return n + p + 2
  200. def uniform_t_vector(fit_points: Sequence) -> Iterable[float]:
  201. n = float(len(fit_points) - 1)
  202. for t in range(len(fit_points)):
  203. yield float(t) / n
  204. def distance_t_vector(fit_points: Iterable['Vertex']):
  205. return centripetal_t_vector(fit_points, power=1)
  206. def centripetal_t_vector(fit_points: Iterable['Vertex'], power: float = .5) -> Iterable[float]:
  207. distances = [pow(distance(p1, p2), power) for p1, p2 in zip(fit_points, fit_points[1:])]
  208. total_length = sum(distances)
  209. s = 0.
  210. yield s
  211. for d in distances:
  212. s += d
  213. yield s / total_length
  214. def bspline_basis(u: float, index: int, degree: int, knots: Sequence[float]) -> float:
  215. """
  216. B-spline basis function.
  217. Simple recursive implementation for testing and comparison.
  218. Args:
  219. u: curve parameter in range [0 .. max(knots)]
  220. index: index of control point
  221. degree: degree of B-spline
  222. knots: knots vector
  223. Returns: basis value N_i,p(u) as float
  224. """
  225. cache = {} # type: Dict[Tuple[int, int], float]
  226. u = float(u)
  227. def N(i: int, p: int) -> float:
  228. try:
  229. return cache[(i, p)]
  230. except KeyError:
  231. if p == 0:
  232. retval = 1 if knots[i] <= u < knots[i + 1] else 0.
  233. else:
  234. dominator = (knots[i + p] - knots[i])
  235. f1 = (u - knots[i]) / dominator * N(i, p - 1) if dominator else 0.
  236. dominator = (knots[i + p + 1] - knots[i + 1])
  237. f2 = (knots[i + p + 1] - u) / dominator * N(i + 1, p - 1) if dominator else 0.
  238. retval = f1 + f2
  239. cache[(i, p)] = retval
  240. return retval
  241. return N(int(index), int(degree))
  242. def bspline_basis_vector(u: float, count: int, degree: int, knots: Sequence[float]) -> List[float]:
  243. """
  244. Create basis vector at parameter u.
  245. Used with the bspline_basis() for testing and comparison.
  246. Args:
  247. u: curve parameter in range [0 .. max(knots)]
  248. count: control point count (n + 1)
  249. degree: degree of B-spline (order = degree + 1)
  250. knots: knot vector
  251. Returns: basis vector as list fo floats, len(basis) == count
  252. """
  253. assert len(knots) == (count + degree + 1)
  254. basis = [bspline_basis(u, index, degree, knots) for index in range(count)] # type: List[float]
  255. if isclose(u, knots[-1]): # pick up last point ??? why is this necessary ???
  256. basis[-1] = 1.
  257. return basis
  258. def bspline_vertex(u: float, degree: int, control_points: Sequence['Vertex'], knots: Sequence[float]) -> Vector:
  259. """
  260. Calculate B-spline vertex at parameter u.
  261. Used with the bspline_basis_vector() for testing and comparison.
  262. Args:
  263. u: curve parameter in range [0 .. max(knots)]
  264. degree: degree of B-spline (order = degree + 1)
  265. control_points: control points as list of (x, y[,z]) tuples
  266. knots: knot vector as list of floats, len(knots) == (count + order)
  267. Returns: Vector() object
  268. """
  269. basis_vector = bspline_basis_vector(u, count=len(control_points), degree=degree, knots=knots)
  270. vertex = Vector()
  271. for basis, point in zip(basis_vector, control_points):
  272. vertex += Vector(point) * basis
  273. return vertex
  274. def bspline_control_frame(fit_points: Iterable['Vertex'], degree: int = 3, method: str = 'distance', power: float = .5):
  275. """
  276. Calculate B-spline control frame, given are the fit points and the degree of the B-spline.
  277. 1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
  278. 2. method = 'distance', creates a t vector with values proportional to the fit point distances
  279. 3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power
  280. Args:
  281. fit_points: fit points of B-spline
  282. degree: degree of B-spline
  283. method: calculation method for parameter vector t
  284. power: power for centripetal method
  285. """
  286. def create_t_vector():
  287. if method == 'uniform':
  288. return uniform_t_vector(fit_points) # equally spaced 0 .. 1
  289. elif method == 'distance':
  290. return distance_t_vector(fit_points)
  291. elif method == 'centripetal':
  292. return centripetal_t_vector(fit_points, power=power)
  293. else:
  294. raise DXFValueError('Unknown method: {}'.format(method))
  295. fit_points = list(fit_points)
  296. count = len(fit_points)
  297. order = degree + 1
  298. if order > count:
  299. raise DXFValueError('Need more fit points for degree {}'.format(degree))
  300. t_vector = list(create_t_vector())
  301. knots = list(control_frame_knots(count - 1, degree, t_vector))
  302. control_points = global_curve_interpolation(fit_points, degree, t_vector, knots)
  303. bspline = BSpline(control_points, order=order, knots=knots)
  304. bspline.t_array = t_vector
  305. return bspline
  306. def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance',
  307. power: float = .5):
  308. """
  309. Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline.
  310. 1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
  311. 2. method = 'distance', creates a t vector with values proportional to the fit point distances
  312. 3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power
  313. Args:
  314. fit_points: all fit points of B-spline
  315. count: count of designated control points
  316. degree: degree of B-spline
  317. method: calculation method for parameter vector t
  318. power: power for centripetal method
  319. """
  320. def create_t_vector():
  321. if method == 'uniform':
  322. return uniform_t_vector(fit_points) # equally spaced 0 .. 1
  323. elif method == 'distance':
  324. return distance_t_vector(fit_points)
  325. elif method == 'centripetal':
  326. return centripetal_t_vector(fit_points, power=power)
  327. else:
  328. raise DXFValueError('Unknown method: {}'.format(method))
  329. fit_points = list(fit_points)
  330. order = degree + 1
  331. if order > count:
  332. raise DXFValueError('More control points for degree {} required.'.format(degree))
  333. t_vector = list(create_t_vector())
  334. knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector))
  335. control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots)
  336. bspline = BSpline(control_points, order=order)
  337. return bspline
  338. def control_frame_knots(n: int, p: int, t_vector: Iterable[float]) -> Iterable[float]:
  339. """
  340. Generates a 'clamped' knot vector for control frame creation. All knot values in the range [0 .. 1].
  341. Args:
  342. n: count fit points - 1
  343. p: degree of spline
  344. t_vector: parameter vector, length(t_vector) == n+1
  345. Yields: n+p+2 knot values as floats
  346. """
  347. order = int(p + 1)
  348. if order > (n + 1):
  349. raise DXFValueError('Invalid n/p combination')
  350. t_vector = [float(t) for t in t_vector]
  351. for _ in range(order): # clamped spline has 'order' leading 0s
  352. yield t_vector[0]
  353. for j in range(1, n - p + 1):
  354. yield sum(t_vector[j: j + p]) / p
  355. for _ in range(order): # clamped spline has 'order' appended 1s
  356. yield t_vector[-1]
  357. def global_curve_interpolation(fit_points: Sequence['Vertex'],
  358. degree: int,
  359. t_vector: Iterable[float],
  360. knots: Iterable[float]) -> List[Vector]:
  361. def create_matrix_N():
  362. spline = Basis(knots=knots, order=degree + 1, count=len(fit_points))
  363. return Matrix([spline.basis(t) for t in t_vector])
  364. matrix_N = create_matrix_N()
  365. control_points = matrix_N.gauss_matrix(fit_points)
  366. return Vector.list(control_points.rows())
  367. def global_curve_approximation(fit_points: Iterable['Vertex'],
  368. count: int,
  369. degree: int,
  370. t_vector: Iterable[float],
  371. knots: Iterable[float]) -> List[Vector]:
  372. """
  373. Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline.
  374. Args:
  375. fit_points: all B-spline fit point
  376. count: count of designated control points
  377. degree: degree of B-spline
  378. t_vector: parameter vector
  379. knots: knot vector
  380. Returns: BSpline() object
  381. """
  382. # source: http://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
  383. fit_points = Vector.list(fit_points) # data points D
  384. n = len(fit_points) - 1
  385. h = count - 1
  386. d0 = fit_points[0]
  387. dn = fit_points[n]
  388. spline = Basis(knots, order=degree + 1, count=len(fit_points))
  389. # matrix_N[0] == row 0
  390. matrix_N = [spline.basis(t) for t in t_vector] # 0 .. n
  391. def Q(k):
  392. ntk = matrix_N[k]
  393. return fit_points[k] - d0 * ntk[0] - dn * ntk[h]
  394. # matrix_Q[0] == row 1
  395. matrix_Q = [sum(Q(k) * matrix_N[k][i] for k in range(1, n)) for i in range(1, h)]
  396. matrix_N = Matrix([row[1:h] for row in matrix_N[1:-1]])
  397. matrix_M = matrix_N.transpose() * matrix_N
  398. P = matrix_M.gauss_matrix(matrix_Q)
  399. control_points = [d0]
  400. control_points.extend(Vector.generate(P.rows()))
  401. control_points.append(dn)
  402. return control_points
  403. class Basis:
  404. def __init__(self, knots: Iterable[float], order: int, count: int, weights: Sequence[float] = None):
  405. self.knots = list(knots) # type: List[float]
  406. self.order = order # type: int
  407. self.count = count # type: int
  408. self.weights = weights # type: Optional[Sequence[float]]
  409. @property
  410. def max_t(self) -> float:
  411. return self.knots[-1]
  412. @property
  413. def nplusc(self) -> int:
  414. return self.count + self.order
  415. def create_nbasis(self, t: float) -> List[float]:
  416. """
  417. Calculate the first order basis functions N[i][1]
  418. Returns: list of basis values
  419. """
  420. return [1. if k1 <= t < k2 else 0. for k1, k2 in zip(self.knots, self.knots[1:])]
  421. def basis(self, t: float) -> List[float]:
  422. knots = self.knots
  423. nbasis = self.create_nbasis(t)
  424. # calculate the higher order basis functions
  425. for k in range(2, self.order + 1):
  426. for i in range(self.nplusc - k):
  427. d = ((t - knots[i]) * nbasis[i]) / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
  428. e = ((knots[i + k] - t) * nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
  429. nbasis[i] = d + e
  430. if isclose(t, self.max_t): # pick up last point
  431. nbasis[self.count - 1] = 1.
  432. if self.weights is None:
  433. return nbasis[:self.count]
  434. else:
  435. return self.weighting(nbasis[:self.count])
  436. def weighting(self, nbasis: Iterable[float]) -> List[float]:
  437. products = [nb * w for nb, w in zip(nbasis, self.weights)]
  438. s = sum(products)
  439. return [0.0] * self.count if s == 0.0 else [p / s for p in products]
  440. class DBasis(Basis):
  441. def basis(self, t: float) -> Tuple[List[float], List[float], List[float]]:
  442. knots = self.knots
  443. nbasis = self.create_nbasis2(t)
  444. d1nbasis = [0.] * self.nplusc
  445. d2nbasis = d1nbasis[:]
  446. for k in range(2, self.order + 1):
  447. for i in range(self.nplusc - k):
  448. # calculate basis functions
  449. b1 = ((t - knots[i]) * nbasis[i]) / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
  450. b2 = ((knots[i + k] - t) * nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
  451. # calculate first derivative
  452. f1 = nbasis[i] / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
  453. f2 = -nbasis[i + 1] / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
  454. f3 = ((t - knots[i]) * d1nbasis[i]) / (knots[i + k - 1] - knots[i]) if d1nbasis[i] != 0. else 0.
  455. f4 = ((knots[i + k] - t) * d1nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d1nbasis[
  456. i + 1] != 0. else 0.
  457. # calculate second derivative
  458. s1 = (2 * d1nbasis[i]) / (knots[i + k - 1] - knots[i]) if d1nbasis[i] != 0. else 0.
  459. s2 = (-2 * d1nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d1nbasis[i + 1] != 0. else 0.
  460. s3 = ((t - knots[i]) * d2nbasis[i]) / (knots[i + k - 1] - knots[i]) if d2nbasis[i] != 0. else 0.
  461. s4 = ((knots[i + k] - t) * d2nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d2nbasis[
  462. i + 1] != 0. else 0.
  463. nbasis[i] = b1 + b2
  464. d1nbasis[i] = f1 + f2 + f3 + f4
  465. d2nbasis[i] = s1 + s2 + s3 + s4
  466. count = self.count
  467. if self.weights is None:
  468. return nbasis[:count], d1nbasis[:count], d2nbasis[:count]
  469. else:
  470. self.weighting(nbasis[:count]), self.weighting(d1nbasis[:count]), self.weighting(d2nbasis[:count])
  471. def create_nbasis2(self, t: float) -> List[float]:
  472. nbasis = self.create_nbasis(t)
  473. if isclose(t, self.max_t):
  474. nbasis[self.count - 1] = 1.
  475. return nbasis
  476. class DBasisU(DBasis):
  477. def create_nbasis2(self, t: float) -> List[float]:
  478. nbasis = self.create_nbasis(t)
  479. if isclose(t, self.knots[self.count]):
  480. nbasis[self.count - 1] = 1.
  481. nbasis[self.count] = 0.
  482. return nbasis
  483. class BSpline:
  484. """
  485. Calculate the points of a B-spline curve, using an uniform open knot vector ("clamped").
  486. Accepts 2d points as definition points, but output ist always 3d (z-axis is 0).
  487. """
  488. def __init__(self, control_points: Iterable['Vertex'],
  489. order: int = 4,
  490. knots: Iterable[float] = None,
  491. weights: Iterable[float] = None):
  492. self.control_points = Vector.list(control_points)
  493. self.order = order
  494. if order > self.count:
  495. raise DXFValueError('Invalid need more control points for order {}'.format(order))
  496. if knots is None:
  497. knots = knot_open_uniform(self.count, self.order)
  498. else:
  499. knots = list(knots)
  500. if len(knots) != self.nplusc:
  501. raise ValueError("{} knot values required, got {}.".format(self.nplusc, len(knots)))
  502. self.basis = Basis(knots, self.order, self.count, weights=weights)
  503. @property
  504. def nplusc(self) -> int:
  505. return self.count + self.order
  506. @property
  507. def count(self) -> int:
  508. return len(self.control_points)
  509. @property
  510. def max_t(self) -> float:
  511. return self.basis.max_t
  512. @property
  513. def degree(self) -> int:
  514. return self.order - 1
  515. def knot_values(self) -> List[float]:
  516. return self.basis.knots
  517. def basis_values(self, t: float) -> List[float]:
  518. return self.basis.basis(t)
  519. def step_size(self, segments: int) -> float:
  520. return self.max_t / float(segments)
  521. def approximate(self, segments: int = 20) -> Iterable[Vector]:
  522. step = self.step_size(segments)
  523. for point_index in range(segments + 1):
  524. yield self.point(point_index * step)
  525. def point(self, t: float) -> Vector:
  526. """
  527. Get point at SplineCurve(t) as tuple (x, y, z).
  528. Args:
  529. t: parameter in range [0, max_t]
  530. Returns: Vector(x, y, z)
  531. """
  532. if isclose(t, self.max_t):
  533. t = self.max_t
  534. p = Vector()
  535. for control_point, basis in zip(self.control_points, self.basis_values(t)):
  536. p += control_point * basis
  537. return p
  538. def insert_knot(self, t: float) -> None:
  539. """
  540. Insert additional knot, without altering the curve shape.
  541. Args:
  542. t: position of new knot 0 < t < max_t
  543. """
  544. if self.basis.weights is not None:
  545. raise DXFValueError('Rational splines not supported.')
  546. knots = self.basis.knots
  547. cpoints = self.control_points
  548. p = self.degree
  549. def find_knot_index() -> int:
  550. for knot_index in range(1, len(knots)):
  551. if knots[knot_index - 1] <= t < knots[knot_index]:
  552. return knot_index - 1
  553. def new_point(index: int) -> Vector:
  554. a = (t - knots[index]) / (knots[index + p] - knots[index])
  555. return cpoints[index - 1] * (1 - a) + cpoints[index] * a
  556. if t <= 0. or t >= self.max_t:
  557. raise DXFValueError('Invalid position t')
  558. k = find_knot_index()
  559. if k < p:
  560. raise DXFValueError('Invalid position t')
  561. cpoints[k - p + 1:k] = [new_point(i) for i in range(k - p + 1, k + 1)]
  562. knots.insert(k + 1, t) # knot[k] <= t < knot[k+1]
  563. self.basis.count = len(cpoints)
  564. class BSplineU(BSpline):
  565. """
  566. Calculate the points of a B-spline curve, uniform (periodic) knot vector (not "clamped").
  567. """
  568. def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
  569. control_points = list(control_points)
  570. knots = knot_uniform(len(control_points), order)
  571. super().__init__(control_points, order=order, knots=knots, weights=weights)
  572. def step_size(self, segments: int) -> float:
  573. return float(self.count - self.order + 1) / segments
  574. def approximate(self, segments=20) -> Iterable[Vector]:
  575. step = self.step_size(segments)
  576. base = float(self.order - 1)
  577. for point_index in range(segments + 1):
  578. yield self.point(base + point_index * step)
  579. def t_array(self) -> List[float]:
  580. raise NotImplemented
  581. class BSplineClosed(BSplineU):
  582. """
  583. Calculate the points of a closed uniform B-spline curve.
  584. """
  585. def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
  586. # control points wrap around
  587. points = list(control_points)
  588. points.extend(points[:order - 1])
  589. if weights is not None:
  590. weights = list(weights)
  591. weights.extend(weights[:order - 1])
  592. super().__init__(points, order=order, weights=weights)
  593. class DerivativePoint: # Mixin
  594. def point(self, t: float) -> Tuple[Vector, Vector, Vector]:
  595. """
  596. Get point, 1st and 2nd derivative at B-spline(t) as tuple (p, d1, d3),
  597. where p, d1 nad d2 is a tuple (x, y, z).
  598. Args:
  599. t: parameter in range [0, max_t]
  600. """
  601. if isclose(self.max_t, t):
  602. t = self.max_t
  603. nbasis, d1nbasis, d2nbasis = self.basis_values(t)
  604. point = Vector()
  605. d1 = Vector()
  606. d2 = Vector()
  607. for i, control_point in enumerate(self.control_points):
  608. point += control_point * nbasis[i]
  609. d1 += control_point * d1nbasis[i]
  610. d2 += control_point * d2nbasis[i]
  611. return point, d1, d2
  612. class DBSpline(DerivativePoint, BSpline):
  613. """
  614. Calculate the Points and Derivative of an open uniform B-spline curve ("clamped").
  615. """
  616. def __init__(self, control_points: Iterable['Vertex'],
  617. order: int = 4,
  618. knots: Iterable[float] = None,
  619. weights: Iterable[float] = None):
  620. super().__init__(control_points, order=order, knots=knots, weights=weights)
  621. self.basis = DBasis(self.knot_values(), self.order, self.count)
  622. class DBSplineU(DerivativePoint, BSplineU):
  623. """
  624. Calculate the Points and Derivative of an uniform B-spline curve (not "clamped").
  625. """
  626. def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
  627. super().__init__(control_points, order=order, weights=weights)
  628. self.basis = DBasisU(self.knot_values(), self.order, self.count)
  629. class DBSplineClosed(DerivativePoint, BSplineClosed):
  630. """
  631. Calculate the Points and Derivative of a closed B-spline curve.
  632. UNTESTED!
  633. """
  634. def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
  635. super().__init__(control_points, order=order, weights=weights)
  636. self.basis = DBasisU(self.knot_values(), self.order, self.count)