123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862 |
- # Created: 2012.01.03
- # Copyright (c) 2012-2018 Manfred Moitzi
- # License: MIT License
- """
- B-Splines
- =========
- https://www.cl.cam.ac.uk/teaching/2000/AGraphHCI/SMEG/node4.html
- n + 1 ... number of control points P_1, P_2, ..., P_{n+1} or P_0, P_1, ..., P_n
- k ... order of the B-spline, 2 <= k <= n + 1
- degree ... k - 1
- B-splines are a more general type of curve than Bezier curves. In a B-spline each control point is associated with a
- basis function.
- (87) P(t) = sum {i=1}^{n+1} N_{i,k}(t) P_i, t_min <= t < t_max
- 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).
- 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
- that the order of the curve (linear, quadratic, cubic,...) is therefore not dependent on the number of control points
- (which it is for Bezier curves, where k must always equal n+1).
- Equation (87) defines a piecewise continuous function. A knot vector, (t_1, t_2, ..., t_{k+(n+1)}), must be specified.
- This determines the values of t at which the pieces of curve join, like knots joining bits of string. It is necessary
- that:
- (88) t_i <= t_{i+1}, for all i
- The N_{i,k} depend only on the value of k and the values in the knot vector. N is defined recursively as:
- (89) N_{i,1}(t) = 1 for t_i <= t < t_{i+1}; 0 otherwise
- 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)
- This is essentially a modified version of the idea of taking linear interpolations of linear interpolations of linear
- interpolations ... n
- The Knot Vector
- ---------------
- The above explanation shows that the knot vector is very important. The knot vector can, by its definition, be any
- sequence of numbers provided that each one is greater than or equal to the preceding one. Some types of knot vector are
- more useful than others. Knot vectors are generally placed into one of three categories: uniform, open uniform, and
- non-uniform.
- Uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~
- These are knot vectors for which
- (90) t_{i+1} - t_i = constant, for all i
- e.g. [1, 2, 3, 4, 5, 6, 7, 8], [0, .25, .5, .75, 1.]
- Open Uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~~~~~~
- These are uniform knot vectors which have k equal knot values at each end
-
- (91) t_i = t_1, i <= k
- t_{i+1} - t_i = constant, k <= i < n+2
- t_i = t_{k+(n+1)}, i >= n + 2
- e.g. [0, 0, 0, 0, 1, 2, 3, 4, 4, 4, 4] for k=4,
- [1, 1, 1, 2, 3, 4, 5, 6, 6, 6] for k=3
- [.1, .1, .1, .1, .1, .3, .5, .7, .7, .7, .7, .7] for k=5
- Non-uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~~~~~
- This is the general case, the only constraint being the standard t_i <= t_{i+1}, for all i (Equations 88).
- e.g. [1, 3, 7, 22, 23, 23, 49, 50, 50]
- [1, 1, 1, 2, 2, 3, 4, 5, 6, 6, 6, 7, 7, 7]
- [.2, .7, .7, .7, 1.2, 1.2, 2.9, 3.6]
- The shapes of the N_{i,k} basis functions are determined entirely by the relative spacing between the knots.
-
- scaling: t_i' = alpha * t_i, for all i
- translating t_i'= t_i + delta t, for all i
-
- The knot vector has no effect on the shapes of the N_{i,k}.
- The above gives a description of the various types of knot vector but it doesn't really give you any insight into how
- the knot vector determines the shape of the curve. The following subsections look at the different types of knot vector
- in more detail. However, the best way to get to feel for these is to derive and draw the basis functions yourself.
- Uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~
- For simplicity, let t_i = i (this is allowable given that the scaling or translating the knot vector has no effect on
- the shapes of the N_{i,k}). The knot vector thus becomes [1,2,3, ... ,k+(n+1)] and equation (89) simplifies to:
- (92) N_{i,1}(t) = 1 for t_i <= t < t_{i+1}; 0 otherwise
- N_{i,k}(t) = (t-i)(k-1) * N_{i,k-1}(t) + (i+k-t)/ (k-1) * N_{i+1,k-1}(t)
- Things you can change about a uniform B-spline
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- With a uniform B-spline, you obviously cannot change the basis functions (they are fixed because all the knots are
- equi-spaced). However you can alter the shape of the curve by modifying a number of things:
- Moving control points:
- Moving the control points obviously changes the shape of the curve.
- Multiple control points:
- Sticking two adjacent control points on top of one another causes the curve to pass closer to that point. Stick enough
- adjacent control points on top of one another and you can make the curve pass through that point.
- Order:
- Increasing the order k increases the continuity of the curve at the knots, increases the smoothness of the curve, and
- tends to move the curve farther from its defining polygon.
- Joining the ends:
- 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
- B-spline defined by these points. For a given order, k, you will need M+(k-1) control points (repeating the first k-1
- points): P_1, ... P_M, P_1, ..., P_{k-1}. Your knot vector will thus have M+2k-1 uniformly spaced knots.
- Open Uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~~~~~~
- The previous section intimated that uniform B-splines can be used to describe closed curves: all you have to do is join
- the ends as described above. If you do not want a closed curve, and you use a uniform knot vector, you find that you
- need to specify control points at each end of the curve which the curve doesn't go near.
- If you wish your B-spline to start and end at your first and last control points then you need an open uniform knot
- vector. The only difference between this and the uniform knot vector being that the open uniform version has k equal
- knots at each end.
- An order k open uniform B-spline with n+1=k points is the Bezier curve of order k.
- Non-uniform Knot Vector
- ~~~~~~~~~~~~~~~~~~~~~~~
- Any B-spline whose knot vector is neither uniform nor open uniform is non-uniform. Non-uniform knot vectors allow any
- spacing of the knots, including multiple knots (adjacent knots with the same value). We need to know how this
- non-uniform spacing affects the basis functions in order to understand where non-uniform knot vectors could be useful.
- It transpires that there are only three cases of any interest:
- 1. multiple knots (adjacent knots equal)
- 2. adjacent knots more closely spaced than the next knot in the vector
- 3. adjacent knots less closely spaced than the next knot in the vector
-
- Obviously, case (3) is simply case (2) turned the other way round.
- Multiple knots:
- A multiple knot reduces the degree of continuity at that knot value. Across a normal knot the continuity is Ck-2. Each
- extra knot with the same value reduces continuity at that value by one. This is the only way to reduce the continuity of
- the curve at the knot values. If there are k-1 (or more) equal knots then you get a discontinuity in the curve.
- Close knots:
- As two knots' values get closer together, relative to the spacing of the other knots, the curve moves closer to the
- related control point.
- Distant knots:
- As two knots' values get further apart, relative to the spacing of the other knots, the curve moves further away from
- the related control point.
- Use of Non-uniform Knot Vectors
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Standard procedure is to use uniform or open uniform B-splines unless there is a very good reason not to do so.
- Moving two knots closer together tends to move the curve only slightly and so there is usually little point in doing it.
- This leads to the conclusion that the main use of non-uniform B-splines is to allow for multiple knots, which adjust the
- continuity of the curve at the knot values.
- However, non-uniform B-splines are the general form of the B-spline because they incorporate open uniform and uniform
- B-splines as special cases. Thus we will talk about non-uniform B-splines when we mean the general case, incorporating
- both uniform and open uniform.
- What can you do to control the shape of a B-spline?
- - Move the control points.
- - Add or remove control points.
- - Use multiple control points.
- - Change the order, k.
- - Change the type of knot vector.
- - Change the relative spacing of the knots.
- - Use multiple knot values in the knot vector.
- What should the defaults be?
- If there are no pressing reasons for doing otherwise, your B-spline should be defined as follows:
- - k=4 (cubic)
- - no multiple control points
- - uniform (for a closed curve) or open uniform (for an open curve) knot vector.
- Rational B-splines
- ==================
- https://www.cl.cam.ac.uk/teaching/2000/AGraphHCI/SMEG/node5.html:
- Rational B-splines have all of the properties of non-rational B-splines plus the following two useful features:
- They produce the correct results under projective transformations (while non-rational B-splines only produce the correct
- results under affine transformations).
- They can be used to represent lines, conics, non-rational B-splines; and, when generalised to patches, can represents
- planes, quadrics, and tori.
- The antonym of rational is non-rational. Non-rational B-splines are a special case of rational B-splines, just as
- uniform B-splines are a special case of non-uniform B-splines. Thus, non-uniform rational B-splines encompass almost
- every other possible 3D shape definition. Non-uniform rational B-spline is a bit of a mouthful and so it is generally
- abbreviated to NURBS.
- 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
- know is the meaning of the rational bit and we will fully(?) understand NURBS.
- Rational B-splines are defined simply by applying the B-spline equation (Equation 87) to homogeneous coordinates,
- rather than normal 3D coordinates.
- """
- from typing import List, Iterable, Sequence, TYPE_CHECKING, Dict, Tuple, Optional
- from .vector import Vector, distance
- from .matrix import Matrix
- from math import pow, isclose
- from ezdxf.lldxf.const import DXFValueError
- if TYPE_CHECKING:
- from ezdxf.eztypes import Vertex
- def knot_open_uniform(n: int, order: int) -> List[float]:
- """
- Returns a open uniform knot vector.
- Args:
- n: count of control points
- order: spline order
- Returns: list of floats (knot vector)
- """
- nplusc = n + order
- nplus2 = n + 2
- knots = [0.]
- for i in range(2, nplusc + 1):
- if (i > order) and (i < nplus2):
- knots.append(knots[-1] + 1.)
- else:
- knots.append(knots[-1])
- return knots
- def is_uniform_knots(knots: Sequence[float], places: int = 4) -> bool:
- deltas = set(round(k2 - k1, ndigits=places) for k1, k2 in zip(knots, knots[1:]))
- return len(deltas) == 1
- def knot_uniform(n: int, order: int) -> List[float]:
- """
- Returns a uniform knot vector.
- Args:
- n: count of control points
- order: spline order
- Returns: list of floats (knot vector)
- """
- return [float(knot_value) for knot_value in range(0, n + order)]
- def required_knot_values(count: int, order: int) -> int:
- # just to show the connections
- # count = count of control points = n + 1
- # k = order of spline = degree + 1
- # 2 <= k <= n + 1
- # p = degree
- # order = p + 1
- k = order
- n = count - 1
- p = k - 1
- if not (2 <= k <= (n + 1)):
- raise DXFValueError('Invalid count/order combination')
- # n + p + 2 = count + order
- return n + p + 2
- def uniform_t_vector(fit_points: Sequence) -> Iterable[float]:
- n = float(len(fit_points) - 1)
- for t in range(len(fit_points)):
- yield float(t) / n
- def distance_t_vector(fit_points: Iterable['Vertex']):
- return centripetal_t_vector(fit_points, power=1)
- def centripetal_t_vector(fit_points: Iterable['Vertex'], power: float = .5) -> Iterable[float]:
- distances = [pow(distance(p1, p2), power) for p1, p2 in zip(fit_points, fit_points[1:])]
- total_length = sum(distances)
- s = 0.
- yield s
- for d in distances:
- s += d
- yield s / total_length
- def bspline_basis(u: float, index: int, degree: int, knots: Sequence[float]) -> float:
- """
- B-spline basis function.
- Simple recursive implementation for testing and comparison.
- Args:
- u: curve parameter in range [0 .. max(knots)]
- index: index of control point
- degree: degree of B-spline
- knots: knots vector
- Returns: basis value N_i,p(u) as float
- """
- cache = {} # type: Dict[Tuple[int, int], float]
- u = float(u)
- def N(i: int, p: int) -> float:
- try:
- return cache[(i, p)]
- except KeyError:
- if p == 0:
- retval = 1 if knots[i] <= u < knots[i + 1] else 0.
- else:
- dominator = (knots[i + p] - knots[i])
- f1 = (u - knots[i]) / dominator * N(i, p - 1) if dominator else 0.
- dominator = (knots[i + p + 1] - knots[i + 1])
- f2 = (knots[i + p + 1] - u) / dominator * N(i + 1, p - 1) if dominator else 0.
- retval = f1 + f2
- cache[(i, p)] = retval
- return retval
- return N(int(index), int(degree))
- def bspline_basis_vector(u: float, count: int, degree: int, knots: Sequence[float]) -> List[float]:
- """
- Create basis vector at parameter u.
- Used with the bspline_basis() for testing and comparison.
- Args:
- u: curve parameter in range [0 .. max(knots)]
- count: control point count (n + 1)
- degree: degree of B-spline (order = degree + 1)
- knots: knot vector
- Returns: basis vector as list fo floats, len(basis) == count
- """
- assert len(knots) == (count + degree + 1)
- basis = [bspline_basis(u, index, degree, knots) for index in range(count)] # type: List[float]
- if isclose(u, knots[-1]): # pick up last point ??? why is this necessary ???
- basis[-1] = 1.
- return basis
- def bspline_vertex(u: float, degree: int, control_points: Sequence['Vertex'], knots: Sequence[float]) -> Vector:
- """
- Calculate B-spline vertex at parameter u.
- Used with the bspline_basis_vector() for testing and comparison.
- Args:
- u: curve parameter in range [0 .. max(knots)]
- degree: degree of B-spline (order = degree + 1)
- control_points: control points as list of (x, y[,z]) tuples
- knots: knot vector as list of floats, len(knots) == (count + order)
- Returns: Vector() object
- """
- basis_vector = bspline_basis_vector(u, count=len(control_points), degree=degree, knots=knots)
- vertex = Vector()
- for basis, point in zip(basis_vector, control_points):
- vertex += Vector(point) * basis
- return vertex
- def bspline_control_frame(fit_points: Iterable['Vertex'], degree: int = 3, method: str = 'distance', power: float = .5):
- """
- Calculate B-spline control frame, given are the fit points and the degree of the B-spline.
- 1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
- 2. method = 'distance', creates a t vector with values proportional to the fit point distances
- 3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power
- Args:
- fit_points: fit points of B-spline
- degree: degree of B-spline
- method: calculation method for parameter vector t
- power: power for centripetal method
- """
- def create_t_vector():
- if method == 'uniform':
- return uniform_t_vector(fit_points) # equally spaced 0 .. 1
- elif method == 'distance':
- return distance_t_vector(fit_points)
- elif method == 'centripetal':
- return centripetal_t_vector(fit_points, power=power)
- else:
- raise DXFValueError('Unknown method: {}'.format(method))
- fit_points = list(fit_points)
- count = len(fit_points)
- order = degree + 1
- if order > count:
- raise DXFValueError('Need more fit points for degree {}'.format(degree))
- t_vector = list(create_t_vector())
- knots = list(control_frame_knots(count - 1, degree, t_vector))
- control_points = global_curve_interpolation(fit_points, degree, t_vector, knots)
- bspline = BSpline(control_points, order=order, knots=knots)
- bspline.t_array = t_vector
- return bspline
- def bspline_control_frame_approx(fit_points: Iterable['Vertex'], count: int, degree: int = 3, method: str = 'distance',
- power: float = .5):
- """
- Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline.
- 1. method = 'uniform', creates a uniform t vector, [0 .. 1] equally spaced
- 2. method = 'distance', creates a t vector with values proportional to the fit point distances
- 3. method = 'centripetal', creates a t vector with values proportional to the fit point distances^power
- Args:
- fit_points: all fit points of B-spline
- count: count of designated control points
- degree: degree of B-spline
- method: calculation method for parameter vector t
- power: power for centripetal method
- """
- def create_t_vector():
- if method == 'uniform':
- return uniform_t_vector(fit_points) # equally spaced 0 .. 1
- elif method == 'distance':
- return distance_t_vector(fit_points)
- elif method == 'centripetal':
- return centripetal_t_vector(fit_points, power=power)
- else:
- raise DXFValueError('Unknown method: {}'.format(method))
- fit_points = list(fit_points)
- order = degree + 1
- if order > count:
- raise DXFValueError('More control points for degree {} required.'.format(degree))
- t_vector = list(create_t_vector())
- knots = list(control_frame_knots(len(fit_points) - 1, degree, t_vector))
- control_points = global_curve_approximation(fit_points, count, degree, t_vector, knots)
- bspline = BSpline(control_points, order=order)
- return bspline
- def control_frame_knots(n: int, p: int, t_vector: Iterable[float]) -> Iterable[float]:
- """
- Generates a 'clamped' knot vector for control frame creation. All knot values in the range [0 .. 1].
- Args:
- n: count fit points - 1
- p: degree of spline
- t_vector: parameter vector, length(t_vector) == n+1
- Yields: n+p+2 knot values as floats
- """
- order = int(p + 1)
- if order > (n + 1):
- raise DXFValueError('Invalid n/p combination')
- t_vector = [float(t) for t in t_vector]
- for _ in range(order): # clamped spline has 'order' leading 0s
- yield t_vector[0]
- for j in range(1, n - p + 1):
- yield sum(t_vector[j: j + p]) / p
- for _ in range(order): # clamped spline has 'order' appended 1s
- yield t_vector[-1]
- def global_curve_interpolation(fit_points: Sequence['Vertex'],
- degree: int,
- t_vector: Iterable[float],
- knots: Iterable[float]) -> List[Vector]:
- def create_matrix_N():
- spline = Basis(knots=knots, order=degree + 1, count=len(fit_points))
- return Matrix([spline.basis(t) for t in t_vector])
- matrix_N = create_matrix_N()
- control_points = matrix_N.gauss_matrix(fit_points)
- return Vector.list(control_points.rows())
- def global_curve_approximation(fit_points: Iterable['Vertex'],
- count: int,
- degree: int,
- t_vector: Iterable[float],
- knots: Iterable[float]) -> List[Vector]:
- """
- Approximate B-spline by a reduced count of control points, given are the fit points and the degree of the B-spline.
- Args:
- fit_points: all B-spline fit point
- count: count of designated control points
- degree: degree of B-spline
- t_vector: parameter vector
- knots: knot vector
- Returns: BSpline() object
- """
- # source: http://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
- fit_points = Vector.list(fit_points) # data points D
- n = len(fit_points) - 1
- h = count - 1
- d0 = fit_points[0]
- dn = fit_points[n]
- spline = Basis(knots, order=degree + 1, count=len(fit_points))
- # matrix_N[0] == row 0
- matrix_N = [spline.basis(t) for t in t_vector] # 0 .. n
- def Q(k):
- ntk = matrix_N[k]
- return fit_points[k] - d0 * ntk[0] - dn * ntk[h]
- # matrix_Q[0] == row 1
- matrix_Q = [sum(Q(k) * matrix_N[k][i] for k in range(1, n)) for i in range(1, h)]
- matrix_N = Matrix([row[1:h] for row in matrix_N[1:-1]])
- matrix_M = matrix_N.transpose() * matrix_N
- P = matrix_M.gauss_matrix(matrix_Q)
- control_points = [d0]
- control_points.extend(Vector.generate(P.rows()))
- control_points.append(dn)
- return control_points
- class Basis:
- def __init__(self, knots: Iterable[float], order: int, count: int, weights: Sequence[float] = None):
- self.knots = list(knots) # type: List[float]
- self.order = order # type: int
- self.count = count # type: int
- self.weights = weights # type: Optional[Sequence[float]]
- @property
- def max_t(self) -> float:
- return self.knots[-1]
- @property
- def nplusc(self) -> int:
- return self.count + self.order
- def create_nbasis(self, t: float) -> List[float]:
- """
- Calculate the first order basis functions N[i][1]
- Returns: list of basis values
- """
- return [1. if k1 <= t < k2 else 0. for k1, k2 in zip(self.knots, self.knots[1:])]
- def basis(self, t: float) -> List[float]:
- knots = self.knots
- nbasis = self.create_nbasis(t)
- # calculate the higher order basis functions
- for k in range(2, self.order + 1):
- for i in range(self.nplusc - k):
- d = ((t - knots[i]) * nbasis[i]) / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
- e = ((knots[i + k] - t) * nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
- nbasis[i] = d + e
- if isclose(t, self.max_t): # pick up last point
- nbasis[self.count - 1] = 1.
- if self.weights is None:
- return nbasis[:self.count]
- else:
- return self.weighting(nbasis[:self.count])
- def weighting(self, nbasis: Iterable[float]) -> List[float]:
- products = [nb * w for nb, w in zip(nbasis, self.weights)]
- s = sum(products)
- return [0.0] * self.count if s == 0.0 else [p / s for p in products]
- class DBasis(Basis):
- def basis(self, t: float) -> Tuple[List[float], List[float], List[float]]:
- knots = self.knots
- nbasis = self.create_nbasis2(t)
- d1nbasis = [0.] * self.nplusc
- d2nbasis = d1nbasis[:]
- for k in range(2, self.order + 1):
- for i in range(self.nplusc - k):
- # calculate basis functions
- b1 = ((t - knots[i]) * nbasis[i]) / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
- b2 = ((knots[i + k] - t) * nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
- # calculate first derivative
- f1 = nbasis[i] / (knots[i + k - 1] - knots[i]) if nbasis[i] != 0. else 0.
- f2 = -nbasis[i + 1] / (knots[i + k] - knots[i + 1]) if nbasis[i + 1] != 0. else 0.
- f3 = ((t - knots[i]) * d1nbasis[i]) / (knots[i + k - 1] - knots[i]) if d1nbasis[i] != 0. else 0.
- f4 = ((knots[i + k] - t) * d1nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d1nbasis[
- i + 1] != 0. else 0.
- # calculate second derivative
- s1 = (2 * d1nbasis[i]) / (knots[i + k - 1] - knots[i]) if d1nbasis[i] != 0. else 0.
- s2 = (-2 * d1nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d1nbasis[i + 1] != 0. else 0.
- s3 = ((t - knots[i]) * d2nbasis[i]) / (knots[i + k - 1] - knots[i]) if d2nbasis[i] != 0. else 0.
- s4 = ((knots[i + k] - t) * d2nbasis[i + 1]) / (knots[i + k] - knots[i + 1]) if d2nbasis[
- i + 1] != 0. else 0.
- nbasis[i] = b1 + b2
- d1nbasis[i] = f1 + f2 + f3 + f4
- d2nbasis[i] = s1 + s2 + s3 + s4
- count = self.count
- if self.weights is None:
- return nbasis[:count], d1nbasis[:count], d2nbasis[:count]
- else:
- self.weighting(nbasis[:count]), self.weighting(d1nbasis[:count]), self.weighting(d2nbasis[:count])
- def create_nbasis2(self, t: float) -> List[float]:
- nbasis = self.create_nbasis(t)
- if isclose(t, self.max_t):
- nbasis[self.count - 1] = 1.
- return nbasis
- class DBasisU(DBasis):
- def create_nbasis2(self, t: float) -> List[float]:
- nbasis = self.create_nbasis(t)
- if isclose(t, self.knots[self.count]):
- nbasis[self.count - 1] = 1.
- nbasis[self.count] = 0.
- return nbasis
- class BSpline:
- """
- Calculate the points of a B-spline curve, using an uniform open knot vector ("clamped").
- Accepts 2d points as definition points, but output ist always 3d (z-axis is 0).
- """
- def __init__(self, control_points: Iterable['Vertex'],
- order: int = 4,
- knots: Iterable[float] = None,
- weights: Iterable[float] = None):
- self.control_points = Vector.list(control_points)
- self.order = order
- if order > self.count:
- raise DXFValueError('Invalid need more control points for order {}'.format(order))
- if knots is None:
- knots = knot_open_uniform(self.count, self.order)
- else:
- knots = list(knots)
- if len(knots) != self.nplusc:
- raise ValueError("{} knot values required, got {}.".format(self.nplusc, len(knots)))
- self.basis = Basis(knots, self.order, self.count, weights=weights)
- @property
- def nplusc(self) -> int:
- return self.count + self.order
- @property
- def count(self) -> int:
- return len(self.control_points)
- @property
- def max_t(self) -> float:
- return self.basis.max_t
- @property
- def degree(self) -> int:
- return self.order - 1
- def knot_values(self) -> List[float]:
- return self.basis.knots
- def basis_values(self, t: float) -> List[float]:
- return self.basis.basis(t)
- def step_size(self, segments: int) -> float:
- return self.max_t / float(segments)
- def approximate(self, segments: int = 20) -> Iterable[Vector]:
- step = self.step_size(segments)
- for point_index in range(segments + 1):
- yield self.point(point_index * step)
- def point(self, t: float) -> Vector:
- """
- Get point at SplineCurve(t) as tuple (x, y, z).
- Args:
- t: parameter in range [0, max_t]
- Returns: Vector(x, y, z)
- """
- if isclose(t, self.max_t):
- t = self.max_t
- p = Vector()
- for control_point, basis in zip(self.control_points, self.basis_values(t)):
- p += control_point * basis
- return p
- def insert_knot(self, t: float) -> None:
- """
- Insert additional knot, without altering the curve shape.
- Args:
- t: position of new knot 0 < t < max_t
- """
- if self.basis.weights is not None:
- raise DXFValueError('Rational splines not supported.')
- knots = self.basis.knots
- cpoints = self.control_points
- p = self.degree
- def find_knot_index() -> int:
- for knot_index in range(1, len(knots)):
- if knots[knot_index - 1] <= t < knots[knot_index]:
- return knot_index - 1
- def new_point(index: int) -> Vector:
- a = (t - knots[index]) / (knots[index + p] - knots[index])
- return cpoints[index - 1] * (1 - a) + cpoints[index] * a
- if t <= 0. or t >= self.max_t:
- raise DXFValueError('Invalid position t')
- k = find_knot_index()
- if k < p:
- raise DXFValueError('Invalid position t')
- cpoints[k - p + 1:k] = [new_point(i) for i in range(k - p + 1, k + 1)]
- knots.insert(k + 1, t) # knot[k] <= t < knot[k+1]
- self.basis.count = len(cpoints)
- class BSplineU(BSpline):
- """
- Calculate the points of a B-spline curve, uniform (periodic) knot vector (not "clamped").
- """
- def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
- control_points = list(control_points)
- knots = knot_uniform(len(control_points), order)
- super().__init__(control_points, order=order, knots=knots, weights=weights)
- def step_size(self, segments: int) -> float:
- return float(self.count - self.order + 1) / segments
- def approximate(self, segments=20) -> Iterable[Vector]:
- step = self.step_size(segments)
- base = float(self.order - 1)
- for point_index in range(segments + 1):
- yield self.point(base + point_index * step)
- def t_array(self) -> List[float]:
- raise NotImplemented
- class BSplineClosed(BSplineU):
- """
- Calculate the points of a closed uniform B-spline curve.
- """
- def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
- # control points wrap around
- points = list(control_points)
- points.extend(points[:order - 1])
- if weights is not None:
- weights = list(weights)
- weights.extend(weights[:order - 1])
- super().__init__(points, order=order, weights=weights)
- class DerivativePoint: # Mixin
- def point(self, t: float) -> Tuple[Vector, Vector, Vector]:
- """
- Get point, 1st and 2nd derivative at B-spline(t) as tuple (p, d1, d3),
- where p, d1 nad d2 is a tuple (x, y, z).
- Args:
- t: parameter in range [0, max_t]
- """
- if isclose(self.max_t, t):
- t = self.max_t
- nbasis, d1nbasis, d2nbasis = self.basis_values(t)
- point = Vector()
- d1 = Vector()
- d2 = Vector()
- for i, control_point in enumerate(self.control_points):
- point += control_point * nbasis[i]
- d1 += control_point * d1nbasis[i]
- d2 += control_point * d2nbasis[i]
- return point, d1, d2
- class DBSpline(DerivativePoint, BSpline):
- """
- Calculate the Points and Derivative of an open uniform B-spline curve ("clamped").
- """
- def __init__(self, control_points: Iterable['Vertex'],
- order: int = 4,
- knots: Iterable[float] = None,
- weights: Iterable[float] = None):
- super().__init__(control_points, order=order, knots=knots, weights=weights)
- self.basis = DBasis(self.knot_values(), self.order, self.count)
- class DBSplineU(DerivativePoint, BSplineU):
- """
- Calculate the Points and Derivative of an uniform B-spline curve (not "clamped").
- """
- def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
- super().__init__(control_points, order=order, weights=weights)
- self.basis = DBasisU(self.knot_values(), self.order, self.count)
- class DBSplineClosed(DerivativePoint, BSplineClosed):
- """
- Calculate the Points and Derivative of a closed B-spline curve.
- UNTESTED!
- """
- def __init__(self, control_points: Iterable['Vertex'], order: int = 4, weights: Iterable[float] = None):
- super().__init__(control_points, order=order, weights=weights)
- self.basis = DBasisU(self.knot_values(), self.order, self.count)
|