bspy 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Library for manipulating and rendering non-uniform b-splines
5
5
  Home-page: http://github.com/ericbrec/bspy
6
6
  Author: Eric Brechner
@@ -35,7 +35,7 @@ Library for manipulating and rendering b-spline curves, surfaces, and multidimen
35
35
  The [Spline](https://ericbrec.github.io/bspy/bspy/spline.html) class has a method to fit multidimensional data for
36
36
  scalar and vector functions of single and multiple variables. It also has methods to create circular arcs, ruled surfaces, and surfaces of revolution.
37
37
  Other methods add, subtract, multiply, and linearly transform splines, as well as confine spline curves to a given range.
38
- There are methods to evaluate spline values, derivatives, integrals, normals, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
38
+ There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
39
39
 
40
40
  The [SplineOpenGLFrame](https://ericbrec.github.io/bspy/bspy/splineOpenGLFrame.html) class is an
41
41
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces.
@@ -4,7 +4,7 @@ Library for manipulating and rendering b-spline curves, surfaces, and multidimen
4
4
  The [Spline](https://ericbrec.github.io/bspy/bspy/spline.html) class has a method to fit multidimensional data for
5
5
  scalar and vector functions of single and multiple variables. It also has methods to create circular arcs, ruled surfaces, and surfaces of revolution.
6
6
  Other methods add, subtract, multiply, and linearly transform splines, as well as confine spline curves to a given range.
7
- There are methods to evaluate spline values, derivatives, integrals, normals, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
7
+ There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
8
8
 
9
9
  The [SplineOpenGLFrame](https://ericbrec.github.io/bspy/bspy/splineOpenGLFrame.html) class is an
10
10
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces.
@@ -51,7 +51,7 @@ def common_basis(self, splines, indMap):
51
51
  alignedSplines = []
52
52
  for i, spline in enumerate(splines):
53
53
  m = spline.nInd * [0]
54
- newKnots = [[] for ix in range(spline.nInd)]
54
+ newKnots = [[] for ix in spline.order]
55
55
  for (map, order, multiplicities) in zip(indMap, orders, knots):
56
56
  ind = map[i]
57
57
  m[ind] = order - spline.order[ind]
@@ -56,6 +56,21 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
56
56
  b += 1
57
57
  return basis
58
58
 
59
+ def curvature(self, uv):
60
+ if self.nInd == 1:
61
+ if self.nDep == 1:
62
+ self = self.graph()
63
+ fp = self.derivative([1], uv)
64
+ fpp = self.derivative([2], uv)
65
+ fpdotfp = fp @ fp
66
+ fpdotfpp = fp @ fpp
67
+ denom = fpdotfp ** 1.5
68
+ if self.nDep == 2:
69
+ numer = fp[0] * fpp[1] - fp[1] * fpp[0]
70
+ else:
71
+ numer = np.sqrt((fpp @ fpp) * fpdotfp - fpdotfpp ** 2)
72
+ return numer / denom
73
+
59
74
  def derivative(self, with_respect_to, uvw):
60
75
  # Make work for scalar valued functions
61
76
  uvw = np.atleast_1d(uvw)
@@ -2,7 +2,9 @@ import numpy as np
2
2
  import bspy.spline
3
3
  import math
4
4
 
5
- def circular_arc(radius, angle, tolerance):
5
+ def circular_arc(radius, angle, tolerance = None):
6
+ if tolerance is None:
7
+ tolerance = np.finfo(float).eps
6
8
  if radius < 0.0 or angle < 0.0 or tolerance < 0.0: raise ValueError("The radius, angle, and tolerance must be positive.")
7
9
 
8
10
  samples = int(max(np.ceil(((1.1536e-5 * radius / tolerance)**(1/8)) * angle / 90), 2.0)) + 1
@@ -418,7 +420,7 @@ def revolve(self, angle):
418
420
  maxRadius = max(abs(self.coefs[0].min()), self.coefs[0].max())
419
421
  arc = ((1.0 / maxRadius, 0.0),
420
422
  (0.0, 1.0 / maxRadius),
421
- (0.0, 0.0)) @ bspy.Spline.circular_arc(maxRadius, angle, np.finfo(self.coefs.dtype).eps) + (0.0, 0.0, 1.0)
423
+ (0.0, 0.0)) @ bspy.Spline.circular_arc(maxRadius, angle) + (0.0, 0.0, 1.0)
422
424
  radiusHeight = ((1.0, 0.0),
423
425
  (1.0, 0.0),
424
426
  (0.0, 1.0)) @ self
@@ -252,6 +252,20 @@ def dot(self, vector):
252
252
  coefs = coefs.reshape(1, *coefs.shape)
253
253
  return type(self)(self.nInd, 1, self.order, self.nCoef, self.knots, coefs, self.accuracy, self.metadata)
254
254
 
255
+ def graph(self):
256
+ splineDomain = self.domain()
257
+ uvwSplines = [bspy.Spline(1, 1, [2], [2], [[uLow, uLow, uHigh, uHigh]],
258
+ [[uLow, uHigh]]) for uLow, uHigh in splineDomain]
259
+ graphSpline = uvwSplines[0]
260
+ for nextSpline in uvwSplines[1:]:
261
+ graphMat = list(np.block([[np.identity(graphSpline.nInd)], [0.0]]))
262
+ nextMat = list(np.block([[np.zeros((graphSpline.nInd, 1))], [1.0]]))
263
+ graphSpline = (graphMat @ graphSpline).add(nextMat @ nextSpline)
264
+ graphMat = list(np.block([[np.identity(graphSpline.nInd)], [np.zeros((self.nDep, graphSpline.nInd))]]))
265
+ selfMat = list(np.block([[np.zeros((graphSpline.nInd, self.nDep))], [np.identity(self.nDep)]]))
266
+ finalGraph = graphMat @ graphSpline + selfMat @ self
267
+ return finalGraph
268
+
255
269
  def integrate(self, with_respect_to = 0):
256
270
  if not(0 <= with_respect_to < self.nInd): raise ValueError("Invalid with_respect_to")
257
271
 
@@ -732,10 +746,12 @@ def transform(self, matrix, maxSingularValue=None):
732
746
 
733
747
  if maxSingularValue is None:
734
748
  maxSingularValue = np.linalg.svd(matrix, compute_uv=False)[0]
735
-
736
- return type(self)(self.nInd, matrix.shape[0], self.order, self.nCoef, self.knots, matrix @ self.coefs, maxSingularValue * self.accuracy, self.metadata)
749
+ swapped = np.swapaxes(self.coefs, 0, -2)
750
+ newCoefs = np.swapaxes(matrix @ swapped, 0, -2)
751
+ return type(self)(self.nInd, matrix.shape[0], self.order, self.nCoef, self.knots, newCoefs, maxSingularValue * self.accuracy, self.metadata)
737
752
 
738
753
  def translate(self, translationVector):
754
+ translationVector = np.atleast_1d(translationVector)
739
755
  if not(len(translationVector) == self.nDep): raise ValueError("Invalid translationVector")
740
756
 
741
757
  coefs = np.array(self.coefs)
@@ -6,14 +6,6 @@ import bspy._spline_intersection
6
6
  import bspy._spline_fitting
7
7
  import bspy._spline_operations
8
8
 
9
- def _isIterable(object):
10
- result = True
11
- try:
12
- iterator = iter(object)
13
- except TypeError:
14
- result = False
15
- return result
16
-
17
9
  class Spline:
18
10
  """
19
11
  A class to model, represent, and process piecewise polynomial tensor product
@@ -84,8 +76,8 @@ class Spline:
84
76
  self.accuracy = accuracy
85
77
  self.metadata = dict(metadata)
86
78
 
87
- def __call__(self, uvw):
88
- return self.evaluate(uvw)
79
+ def __call__(self, *uvw, **kwargs):
80
+ return self.evaluate(*uvw, **kwargs)
89
81
 
90
82
  def __repr__(self):
91
83
  return f"Spline({self.nInd}, {self.nDep}, {self.order}, " + \
@@ -95,77 +87,59 @@ class Spline:
95
87
  def __add__(self, other):
96
88
  if isinstance(other, Spline):
97
89
  return self.add(other, [(ix, ix) for ix in range(min(self.nInd, other.nInd))])
98
- elif _isIterable(other):
99
- return self.translate(other)
100
90
  else:
101
- return NotImplemented
91
+ return self.translate(other)
102
92
 
103
93
  def __radd__(self, other):
104
94
  if isinstance(other, Spline):
105
95
  return other.add(self, [(ix, ix) for ix in range(min(self.nInd, other.nInd))])
106
- elif _isIterable(other):
107
- return self.translate(other)
108
96
  else:
109
- return NotImplemented
97
+ return self.translate(other)
110
98
 
111
99
  def __matmul__(self, other):
112
100
  if isinstance(other, Spline):
113
101
  return self.multiply(other, [(ix, ix) for ix in range(min(self.nInd, other.nInd))], 'D')
114
- elif _isIterable(other):
115
- if not isinstance(other, np.ndarray):
116
- other = np.array(other)
117
- if len(other.shape) == 2:
102
+ else:
103
+ other = np.atleast_1d(other)
104
+ if len(other.shape) > 1:
118
105
  return self.transform(other.T)
119
106
  else:
120
107
  return self.dot(other)
121
- else:
122
- return NotImplemented
123
108
 
124
109
  def __rmatmul__(self, other):
125
110
  if isinstance(other, Spline):
126
111
  return other.multiply(self, [(ix, ix) for ix in range(min(self.nInd, other.nInd))], 'D')
127
- elif _isIterable(other):
128
- if not isinstance(other, np.ndarray):
129
- other = np.array(other)
130
- if len(other.shape) == 2:
112
+ else:
113
+ other = np.atleast_1d(other)
114
+ if len(other.shape) > 1:
131
115
  return self.transform(other)
132
116
  else:
133
117
  return self.dot(other)
134
- else:
135
- return NotImplemented
136
118
 
137
119
  def __mul__(self, other):
138
120
  if isinstance(other, Spline):
139
121
  return self.multiply(other, [(ix, ix) for ix in range(min(self.nInd, other.nInd))], 'S')
140
- elif np.isscalar(other) or _isIterable(other):
141
- return self.scale(other)
142
122
  else:
143
- return NotImplemented
123
+ return self.scale(other)
144
124
 
145
125
  def __rmul__(self, other):
146
126
  if isinstance(other, Spline):
147
127
  return other.multiply(self, [(ix, ix) for ix in range(min(self.nInd, other.nInd))], 'S')
148
- elif np.isscalar(other) or _isIterable(other):
149
- return self.scale(other)
150
128
  else:
151
- return NotImplemented
129
+ return self.scale(other)
152
130
 
153
131
  def __sub__(self, other):
154
132
  if isinstance(other, Spline):
155
133
  return self.subtract(other, [(ix, ix) for ix in range(min(self.nInd, other.nInd))])
156
- elif _isIterable(other):
157
- return self.translate(-np.array(other))
158
134
  else:
159
- return NotImplemented
135
+ return self.translate(-np.atleast_1d(other))
160
136
 
161
137
  def __rsub__(self, other):
162
138
  if isinstance(other, Spline):
163
139
  return other.subtract(self, [(ix, ix) for ix in range(min(self.nInd, other.nInd))])
164
- elif _isIterable(other):
140
+ else:
165
141
  spline = self.scale(-1.0)
166
142
  return spline.translate(other)
167
- else:
168
- return NotImplemented
169
143
 
170
144
  def add(self, other, indMap = None):
171
145
  """
@@ -203,7 +177,7 @@ class Spline:
203
177
  Uses `common_basis` to ensure mapped variables share the same order and knots.
204
178
  """
205
179
  if indMap is not None:
206
- indMap = [mapping if _isIterable(mapping) else (mapping, mapping) for mapping in indMap]
180
+ indMap = [(mapping, mapping) if np.isscalar(mapping) else mapping for mapping in indMap]
207
181
  return bspy._spline_operations.add(self, other, indMap)
208
182
 
209
183
  def blossom(self, uvw):
@@ -274,7 +248,7 @@ class Spline:
274
248
  return bspy._spline_evaluation.bspline_values(knot, knots, splineOrder, u, derivativeOrder, taylorCoefs)
275
249
 
276
250
  @staticmethod
277
- def circular_arc(radius, angle, tolerance):
251
+ def circular_arc(radius, angle, tolerance = None):
278
252
  """
279
253
  Create a 2D circular arc for a given radius and angle accurate to within a given tolerance.
280
254
 
@@ -286,8 +260,8 @@ class Spline:
286
260
  angle : `float`
287
261
  The angle of the circular arc measured in degrees starting at the x-axis rotating counterclockwise.
288
262
 
289
- tolerance : `float`
290
- The maximum allowed error in the circular arc.
263
+ tolerance : `float`, optional
264
+ The maximum allowed error in the circular arc (default is machine epsilon).
291
265
 
292
266
  Returns
293
267
  -------
@@ -521,7 +495,7 @@ class Spline:
521
495
  Computer Aided Geometric Design 11, no. 6 (1994): 597-620.
522
496
  """
523
497
  if indMap is not None:
524
- indMap = [(*(mapping if _isIterable(mapping) else (mapping, mapping)), True) for mapping in indMap]
498
+ indMap = [(mapping, mapping, True) if np.isscalar(mapping) else (*mapping, True) for mapping in indMap]
525
499
  return bspy._spline_operations.multiplyAndConvolve(self, other, indMap, productType)
526
500
 
527
501
  def copy(self, metadata={}):
@@ -560,7 +534,28 @@ class Spline:
560
534
  """
561
535
  return bspy._spline_operations.cross(self, vector)
562
536
 
563
- def derivative(self, with_respect_to, uvw):
537
+ def curvature(self, uvw):
538
+ """
539
+ Compute the curvature of a univariate spline.
540
+
541
+ Parameters
542
+ ----------
543
+ uvw : `iterable`
544
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
545
+
546
+ Returns
547
+ -------
548
+ value : scalar
549
+ The value of the curvature at the given point on the curve.
550
+
551
+ Notes
552
+ -----
553
+ Computes the curvature of the graph of the function if nDep == 1. If nDep == 1 or 2,
554
+ then the curvature is computed as a signed quantity.
555
+ """
556
+ return bspy._spline_evaluation.curvature(self, uvw)
557
+
558
+ def derivative(self, with_respect_to, *uvw, **kwargs):
564
559
  """
565
560
  Compute the derivative of the spline at given parameter values.
566
561
 
@@ -570,13 +565,18 @@ class Spline:
570
565
  An iterable of length `nInd` that specifies the integer order of derivative for each independent variable.
571
566
  A zero-order derivative just evaluates the spline normally.
572
567
 
573
- uvw : `iterable`
574
- An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
568
+ *uvw : `iterable` or iterable of iterables
569
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values), or
570
+ an iterable of iterables of length `nInd` that specifies values for each independent variable (numpy ufunc style).
571
+
572
+ **kwargs:
573
+ For other keyword-only arguments, see the `ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
575
574
 
576
575
  Returns
577
576
  -------
578
577
  value : `numpy.array`
579
- The value of the derivative of the spline at the given parameter values.
578
+ The value of the derivative of the spline at the given parameter values (array of size nDep). If multiple points are
579
+ passed (numpy ufunc style), then multiple arrays are returned, one for each dependent variable.
580
580
 
581
581
  See Also
582
582
  --------
@@ -592,7 +592,13 @@ class Spline:
592
592
  evaluated, then the dot product of those B-splines with the vector of
593
593
  B-spline coefficients is computed.
594
594
  """
595
- return bspy._spline_evaluation.derivative(self, with_respect_to, uvw)
595
+ if len(uvw) > 1 or (not np.isscalar(uvw[0]) and len(uvw[0]) > self.nInd):
596
+ def vectorized(*uvwInstance):
597
+ return tuple(bspy._spline_evaluation.derivative(self, with_respect_to, uvwInstance))
598
+ uFunc = np.frompyfunc(vectorized, self.nInd, self.nDep)
599
+ return uFunc(*uvw, **kwargs)
600
+ else:
601
+ return bspy._spline_evaluation.derivative(self, with_respect_to, *uvw)
596
602
 
597
603
  def differentiate(self, with_respect_to = 0):
598
604
  """
@@ -715,19 +721,24 @@ class Spline:
715
721
  """
716
722
  return bspy._spline_domain.elevate_and_insert_knots(self, m, newKnots)
717
723
 
718
- def evaluate(self, uvw):
724
+ def evaluate(self, *uvw, **kwargs):
719
725
  """
720
726
  Compute the value of the spline at given parameter values.
721
727
 
722
728
  Parameters
723
729
  ----------
724
- uvw : `iterable`
725
- An iterable of length `nInd` that specifies the values of each independent variable (the parameter values).
730
+ *uvw : `iterable` or iterable of iterables
731
+ An iterable of length `nInd` that specifies the values of each independent variable (the parameter values), or
732
+ an iterable of iterables of length `nInd` that specifies values for each independent variable (numpy ufunc style).
733
+
734
+ **kwargs:
735
+ For other keyword-only arguments, see the `ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
726
736
 
727
737
  Returns
728
738
  -------
729
739
  value : `numpy.array`
730
- The value of the spline at the given parameter values.
740
+ The value of the spline at the given parameter values (array of size nDep). If multiple points are
741
+ passed (numpy ufunc style), then multiple arrays are returned, one for each dependent variable.
731
742
 
732
743
  See Also
733
744
  --------
@@ -735,12 +746,20 @@ class Spline:
735
746
 
736
747
  Notes
737
748
  -----
749
+ Equivalent to spline(*uvw, **kwargs).
750
+
738
751
  The evaluate method uses the de Boor recurrence relations for a B-spline
739
752
  series to evaluate a spline. The non-zero B-splines are
740
753
  evaluated, then the dot product of those B-splines with the vector of
741
754
  B-spline coefficients is computed.
742
755
  """
743
- return bspy._spline_evaluation.evaluate(self, uvw)
756
+ if len(uvw) > 1 or (not np.isscalar(uvw[0]) and len(uvw[0]) > self.nInd):
757
+ def vectorized(*uvwInstance):
758
+ return tuple(bspy._spline_evaluation.evaluate(self, uvwInstance))
759
+ uFunc = np.frompyfunc(vectorized, self.nInd, self.nDep)
760
+ return uFunc(*uvw, **kwargs)
761
+ else:
762
+ return bspy._spline_evaluation.evaluate(self, *uvw)
744
763
 
745
764
  def extrapolate(self, newDomain, continuityOrder):
746
765
  """
@@ -803,6 +822,31 @@ class Spline:
803
822
  """
804
823
  return bspy._spline_domain.fold(self, foldedInd)
805
824
 
825
+ def graph(self):
826
+ """
827
+ Generate the spline which is the graph of the given spline.
828
+
829
+ Parameters
830
+ ----------
831
+
832
+ Returns
833
+ -------
834
+ Given a spline with n independent variables and m dependent variables, generate a new
835
+ spline with n independent variables which has n + m dependent variables, the first n of
836
+ which are just the independent variables themselves. For example, given a scalar
837
+ valued function f of two variables u and v, return the spline of two variables whose
838
+ three dependent variables are (u, v, f(u,v)).
839
+
840
+ See Also
841
+ --------
842
+ `add` : Add two splines together
843
+
844
+ Notes
845
+ -----
846
+ Makes use of matrix spline multiply and tensor product addition for splines.
847
+ """
848
+ return bspy._spline_operations.graph(self)
849
+
806
850
  def insert_knots(self, newKnots):
807
851
  """
808
852
  Insert new knots into a spline.
@@ -1112,7 +1156,7 @@ class Spline:
1112
1156
  Computer Aided Geometric Design 11, no. 6 (1994): 597-620.
1113
1157
  """
1114
1158
  if indMap is not None:
1115
- indMap = [(*(mapping if _isIterable(mapping) else (mapping, mapping)), False) for mapping in indMap]
1159
+ indMap = [(mapping, mapping, False) if np.isscalar(mapping) else (*mapping, False) for mapping in indMap]
1116
1160
  return bspy._spline_operations.multiplyAndConvolve(self, other, indMap, productType)
1117
1161
 
1118
1162
  def normal(self, uvw, normalize=True, indices=None):
@@ -1443,7 +1487,7 @@ class Spline:
1443
1487
  Uses `common_basis` to ensure mapped variables share the same order and knots.
1444
1488
  """
1445
1489
  if indMap is not None:
1446
- indMap = [mapping if _isIterable(mapping) else (mapping, mapping) for mapping in indMap]
1490
+ indMap = [(mapping, mapping) if np.isscalar(mapping) else mapping for mapping in indMap]
1447
1491
  return self.add(other.scale(-1.0), indMap)
1448
1492
 
1449
1493
  def transform(self, matrix, maxSingularValue=None):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Library for manipulating and rendering non-uniform b-splines
5
5
  Home-page: http://github.com/ericbrec/bspy
6
6
  Author: Eric Brechner
@@ -35,7 +35,7 @@ Library for manipulating and rendering b-spline curves, surfaces, and multidimen
35
35
  The [Spline](https://ericbrec.github.io/bspy/bspy/spline.html) class has a method to fit multidimensional data for
36
36
  scalar and vector functions of single and multiple variables. It also has methods to create circular arcs, ruled surfaces, and surfaces of revolution.
37
37
  Other methods add, subtract, multiply, and linearly transform splines, as well as confine spline curves to a given range.
38
- There are methods to evaluate spline values, derivatives, integrals, normals, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
38
+ There are methods to evaluate spline values, derivatives, integrals, normals, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions. In addition, there are methods to manipulate the domain of splines, including trim, join, reparametrize, reverse, add and remove knots, elevate and extrapolate, and fold and unfold. Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
39
39
 
40
40
  The [SplineOpenGLFrame](https://ericbrec.github.io/bspy/bspy/splineOpenGLFrame.html) class is an
41
41
  [OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces.
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = bspy
3
- version = 2.0.0
3
+ version = 2.1.0
4
4
  author = Eric Brechner
5
5
  author_email = ericbrec@msn.com
6
6
  description = Library for manipulating and rendering non-uniform b-splines
@@ -700,6 +700,14 @@ def test_cross():
700
700
  maxError = max(maxError, np.sqrt(xyz @ xyz))
701
701
  assert maxError <= np.sqrt(np.finfo(float).eps)
702
702
 
703
+ def test_curvature():
704
+ testCurve = bspy.Spline.section([[1.0, 0.0, 90.0, 1.0], [0.0, 1.0, 180.0, 2.0]])
705
+ assert abs(testCurve.curvature(0.0) - 1.0) < 2.0e-15
706
+ assert abs(testCurve.curvature(1.0) - 2.0) < 2.0e-15
707
+ testCurve = testCurve @ [0, 1]
708
+ testVals = [testCurve.curvature(u) for u in np.linspace(0.0, 1.0, 101)]
709
+ return
710
+
703
711
  def test_derivative():
704
712
  maxError = 0.0
705
713
  myDerivative = myCurve.differentiate()
@@ -821,6 +829,16 @@ def test_fold_unfold():
821
829
  maxError = max(maxError, abs(unfolded.coefs[i, j, k, l] - spline.coefs[i, j, k, l]))
822
830
  assert maxError <= np.finfo(float).eps
823
831
 
832
+ def test_graph():
833
+ simpleFunc = bspy.Spline(2, 1, [3, 4], [4, 5], [[0.0, 0, 0, 0.4, 1, 1, 1],
834
+ [0.0, 0, 0, 0, 0.6, 1, 1, 1, 1]], [[1.0, 2, 3, 4, 2, 3, 4, 5,
835
+ 3, 4, 5, 6, 4, 5, 6, 7, 5, 6, 7, 8]])
836
+ graphFunc = simpleFunc.graph()
837
+ uvfPoint = graphFunc([0.27, 0.83])
838
+ assert abs(uvfPoint[0] - 0.27) <= 4.0 * np.finfo(float).eps
839
+ assert abs(uvfPoint[1] - 0.83) <= 4.0 * np.finfo(float).eps
840
+ assert abs(uvfPoint[2] - simpleFunc([0.27, 0.83])[0]) <= 4.0 * np.finfo(float).eps
841
+
824
842
  def test_insert_knots():
825
843
  maxError = 0.0
826
844
  newCurve = myCurve.insert_knots([[.2, .3]])
@@ -1167,6 +1185,14 @@ def test_transform():
1167
1185
  [xTest, yTest] = transformedCurve.evaluate([u])
1168
1186
  maxError = max(maxError, (xTest - (2.0 * x + 3.0 * y)) ** 2 + (yTest - (-1.0 * x - 4.0 * y)) ** 2)
1169
1187
  assert maxError <= np.finfo(float).eps
1188
+ shape234 = bspy.Spline(2, 2, [3, 4], [3, 4], [[0.0, 0, 0, 1, 1, 1], [0.0, 0, 0, 0, 1, 1, 1, 1]],
1189
+ [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
1190
+ [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]])
1191
+ shape334 = [[2, 0], [0, 0.5], [1, 1]] @ shape234
1192
+ assert shape334.coefs[0,1,0] == 8
1193
+ assert shape334.coefs[0,2,1] == 18
1194
+ assert shape334.coefs[1,0,2] == 11
1195
+ assert shape334.coefs[2,2,3] == 42
1170
1196
 
1171
1197
  def test_translate():
1172
1198
  maxError = 0.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes