bspy 2.2.2__py3-none-any.whl → 3.0.1__py3-none-any.whl
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.
- bspy/__init__.py +1 -1
- bspy/_spline_domain.py +17 -16
- bspy/_spline_evaluation.py +14 -6
- bspy/_spline_fitting.py +166 -137
- bspy/_spline_intersection.py +3 -3
- bspy/_spline_operations.py +60 -46
- bspy/bspyApp.py +36 -39
- bspy/drawableSpline.py +11 -13
- bspy/spline.py +202 -69
- bspy/splineOpenGLFrame.py +24 -16
- {bspy-2.2.2.dist-info → bspy-3.0.1.dist-info}/METADATA +14 -8
- bspy-3.0.1.dist-info/RECORD +15 -0
- bspy-2.2.2.dist-info/RECORD +0 -15
- {bspy-2.2.2.dist-info → bspy-3.0.1.dist-info}/LICENSE +0 -0
- {bspy-2.2.2.dist-info → bspy-3.0.1.dist-info}/WHEEL +0 -0
- {bspy-2.2.2.dist-info → bspy-3.0.1.dist-info}/top_level.txt +0 -0
bspy/__init__.py
CHANGED
bspy/_spline_domain.py
CHANGED
|
@@ -11,10 +11,11 @@ def clamp(self, left, right):
|
|
|
11
11
|
|
|
12
12
|
return self.trim(bounds)
|
|
13
13
|
|
|
14
|
-
def common_basis(
|
|
14
|
+
def common_basis(splines, indMap):
|
|
15
15
|
# Step 1: Compute the order for each aligned independent variable.
|
|
16
16
|
orders = []
|
|
17
|
-
|
|
17
|
+
if indMap is None:
|
|
18
|
+
indMap = [len(splines) * [iInd] for iInd in range(splines[0].nInd)]
|
|
18
19
|
for map in indMap:
|
|
19
20
|
if not(len(map) == len(splines)): raise ValueError("Invalid map")
|
|
20
21
|
order = 0
|
|
@@ -161,7 +162,7 @@ def elevate_and_insert_knots(self, m, newKnots):
|
|
|
161
162
|
# Set new coefs to the elevated zeroth derivative coefficients with variables swapped back.
|
|
162
163
|
coefs = coefs[0].swapaxes(0, ind + 1)
|
|
163
164
|
|
|
164
|
-
return type(self)(self.nInd, self.nDep, order, nCoef, knots, coefs, self.
|
|
165
|
+
return type(self)(self.nInd, self.nDep, order, nCoef, knots, coefs, self.metadata)
|
|
165
166
|
|
|
166
167
|
def extrapolate(self, newDomain, continuityOrder):
|
|
167
168
|
if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
|
|
@@ -268,10 +269,10 @@ def extrapolate(self, newDomain, continuityOrder):
|
|
|
268
269
|
# Swap dependent and independent variables back.
|
|
269
270
|
dCoefs = dCoefs.swapaxes(1, ind + 2)
|
|
270
271
|
|
|
271
|
-
return type(self)(self.nInd, self.nDep, self.order, nCoef, knots, dCoefs[0], self.
|
|
272
|
+
return type(self)(self.nInd, self.nDep, self.order, nCoef, knots, dCoefs[0], self.metadata)
|
|
272
273
|
|
|
273
274
|
def fold(self, foldedInd):
|
|
274
|
-
if not(0 <= len(foldedInd)
|
|
275
|
+
if not(0 <= len(foldedInd) <= self.nInd): raise ValueError("Invalid foldedInd")
|
|
275
276
|
foldedOrder = []
|
|
276
277
|
foldedNCoef = []
|
|
277
278
|
foldedKnots = []
|
|
@@ -299,8 +300,8 @@ def fold(self, foldedInd):
|
|
|
299
300
|
foldedCoefs = np.moveaxis(self.coefs, coefficientMoveFrom, coefficientMoveTo).reshape((foldedNDep, *foldedNCoef))
|
|
300
301
|
coefficientlessCoefs = np.empty((0, *coefficientlessNCoef), self.coefs.dtype)
|
|
301
302
|
|
|
302
|
-
foldedSpline = type(self)(len(foldedOrder), foldedNDep, foldedOrder, foldedNCoef, foldedKnots, foldedCoefs, self.
|
|
303
|
-
coefficientlessSpline = type(self)(len(coefficientlessOrder), 0, coefficientlessOrder, coefficientlessNCoef, coefficientlessKnots, coefficientlessCoefs, self.
|
|
303
|
+
foldedSpline = type(self)(len(foldedOrder), foldedNDep, foldedOrder, foldedNCoef, foldedKnots, foldedCoefs, self.metadata)
|
|
304
|
+
coefficientlessSpline = type(self)(len(coefficientlessOrder), 0, coefficientlessOrder, coefficientlessNCoef, coefficientlessKnots, coefficientlessCoefs, self.metadata)
|
|
304
305
|
return foldedSpline, coefficientlessSpline
|
|
305
306
|
|
|
306
307
|
def insert_knots(self, newKnots):
|
|
@@ -327,7 +328,7 @@ def insert_knots(self, newKnots):
|
|
|
327
328
|
if self.coefs is coefs:
|
|
328
329
|
return self
|
|
329
330
|
else:
|
|
330
|
-
return type(self)(self.nInd, self.nDep, self.order, coefs.shape[1:], knots, coefs, self.
|
|
331
|
+
return type(self)(self.nInd, self.nDep, self.order, coefs.shape[1:], knots, coefs, self.metadata)
|
|
331
332
|
|
|
332
333
|
def join(splineList):
|
|
333
334
|
# Make sure all the splines in the list are curves
|
|
@@ -364,7 +365,7 @@ def join(splineList):
|
|
|
364
365
|
newKnots = [list(workingSpline.knots[0]) + list(spl.knots[0][maxOrder:])]
|
|
365
366
|
newCoefs = [list(workingCoefs) + list(splCoefs) for workingCoefs, splCoefs in zip(workingSpline.coefs, spl.coefs)]
|
|
366
367
|
workingSpline = type(workingSpline)(1, numDep, workingSpline.order, [workingSpline.nCoef[0] + spl.nCoef[0]],
|
|
367
|
-
newKnots, newCoefs,
|
|
368
|
+
newKnots, newCoefs, workingSpline.metadata)
|
|
368
369
|
return workingSpline.reparametrize([[0.0, 1.0]]).remove_knots()
|
|
369
370
|
|
|
370
371
|
def remove_knot(self, iKnot):
|
|
@@ -377,7 +378,7 @@ def remove_knot(self, iKnot):
|
|
|
377
378
|
myKnots = self.knots[0]
|
|
378
379
|
thisKnot = myKnots[iKnot]
|
|
379
380
|
|
|
380
|
-
# Form the
|
|
381
|
+
# Form the bi-diagonal system
|
|
381
382
|
for ix in range(1, myOrder):
|
|
382
383
|
alpha = (myKnots[iKnot + ix] - thisKnot) / (myKnots[iKnot + ix] - myKnots[iKnot + ix - myOrder])
|
|
383
384
|
diag0.append(alpha)
|
|
@@ -470,7 +471,7 @@ def reparametrize(self, newDomain):
|
|
|
470
471
|
for i in range(1-order, 0):
|
|
471
472
|
knots[i] = max(knots[i], nD[1])
|
|
472
473
|
knotList.append(knots)
|
|
473
|
-
return type(self)(self.nInd, self.nDep, self.order, self.nCoef, knotList, self.coefs, self.
|
|
474
|
+
return type(self)(self.nInd, self.nDep, self.order, self.nCoef, knotList, self.coefs, self.metadata)
|
|
474
475
|
|
|
475
476
|
def reverse(self, variable = 0):
|
|
476
477
|
# Check to make sure variable is in range
|
|
@@ -488,8 +489,8 @@ def reverse(self, variable = 0):
|
|
|
488
489
|
newCoefs = []
|
|
489
490
|
for iDep in range(folded.nDep):
|
|
490
491
|
newCoefs.append(self.coefs[iDep][::-1])
|
|
491
|
-
|
|
492
|
-
return
|
|
492
|
+
newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
|
|
493
|
+
return newFolded.unfold(myIndices, basisInfo)
|
|
493
494
|
|
|
494
495
|
def transpose(self, axes=None):
|
|
495
496
|
if axes is None:
|
|
@@ -503,7 +504,7 @@ def transpose(self, axes=None):
|
|
|
503
504
|
nCoef.append(self.nCoef[axis])
|
|
504
505
|
knots.append(self.knots[axis])
|
|
505
506
|
coefAxes.append(axis + 1)
|
|
506
|
-
return type(self)(self.nInd, self.nDep, order, nCoef, knots, np.transpose(self.coefs, coefAxes), self.
|
|
507
|
+
return type(self)(self.nInd, self.nDep, order, nCoef, knots, np.transpose(self.coefs, coefAxes), self.metadata)
|
|
507
508
|
|
|
508
509
|
def trim(self, newDomain):
|
|
509
510
|
if not(len(newDomain) == self.nInd): raise ValueError("Invalid newDomain")
|
|
@@ -552,7 +553,7 @@ def trim(self, newDomain):
|
|
|
552
553
|
coefIndex.append(slice(leftIndex, rightIndex))
|
|
553
554
|
coefs = spline.coefs[tuple(coefIndex)]
|
|
554
555
|
|
|
555
|
-
return type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.
|
|
556
|
+
return type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
|
|
556
557
|
|
|
557
558
|
def unfold(self, foldedInd, coefficientlessSpline):
|
|
558
559
|
if not(len(foldedInd) == coefficientlessSpline.nInd): raise ValueError("Invalid coefficientlessSpline")
|
|
@@ -582,5 +583,5 @@ def unfold(self, foldedInd, coefficientlessSpline):
|
|
|
582
583
|
coefficientMoveFrom = range(1, coefficientlessSpline.nInd + 1)
|
|
583
584
|
unfoldedCoefs = np.moveaxis(self.coefs.reshape(unfoldedNDep, *coefficientlessSpline.nCoef, *self.nCoef), coefficientMoveFrom, coefficientMoveTo)
|
|
584
585
|
|
|
585
|
-
unfoldedSpline = type(self)(len(unfoldedOrder), unfoldedNDep, unfoldedOrder, unfoldedNCoef, unfoldedKnots, unfoldedCoefs, self.
|
|
586
|
+
unfoldedSpline = type(self)(len(unfoldedOrder), unfoldedNDep, unfoldedOrder, unfoldedNCoef, unfoldedKnots, unfoldedCoefs, self.metadata)
|
|
586
587
|
return unfoldedSpline
|
bspy/_spline_evaluation.py
CHANGED
|
@@ -62,19 +62,23 @@ def curvature(self, uv):
|
|
|
62
62
|
self = self.graph()
|
|
63
63
|
fp = self.derivative([1], uv)
|
|
64
64
|
fpp = self.derivative([2], uv)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
denom =
|
|
65
|
+
fpDotFp = fp @ fp
|
|
66
|
+
fpDotFpp = fp @ fpp
|
|
67
|
+
denom = fpDotFp ** 1.5
|
|
68
68
|
if self.nDep == 2:
|
|
69
|
-
|
|
69
|
+
numerator = fp[0] * fpp[1] - fp[1] * fpp[0]
|
|
70
70
|
else:
|
|
71
|
-
|
|
72
|
-
return
|
|
71
|
+
numerator = np.sqrt((fpp @ fpp) * fpDotFp - fpDotFpp ** 2)
|
|
72
|
+
return numerator / denom
|
|
73
73
|
|
|
74
74
|
def derivative(self, with_respect_to, uvw):
|
|
75
75
|
# Make work for scalar valued functions
|
|
76
76
|
uvw = np.atleast_1d(uvw)
|
|
77
77
|
|
|
78
|
+
# Check for the correct number of independent variables
|
|
79
|
+
if len(uvw) != self.nInd:
|
|
80
|
+
raise ValueError(f"Incorrect number of parameter values: {len(uvw)}")
|
|
81
|
+
|
|
78
82
|
# Check for evaluation point inside domain
|
|
79
83
|
dom = self.domain()
|
|
80
84
|
for ix in range(self.nInd):
|
|
@@ -104,6 +108,10 @@ def evaluate(self, uvw):
|
|
|
104
108
|
# Make work for scalar valued functions
|
|
105
109
|
uvw = np.atleast_1d(uvw)
|
|
106
110
|
|
|
111
|
+
# Check for the correct number of independent variables
|
|
112
|
+
if len(uvw) != self.nInd:
|
|
113
|
+
raise ValueError(f"Incorrect number of parameter values: {len(uvw)}")
|
|
114
|
+
|
|
107
115
|
# Check for evaluation point inside domain
|
|
108
116
|
dom = self.domain()
|
|
109
117
|
for ix in range(self.nInd):
|
bspy/_spline_fitting.py
CHANGED
|
@@ -10,6 +10,17 @@ def circular_arc(radius, angle, tolerance = None):
|
|
|
10
10
|
samples = int(max(np.ceil(((1.1536e-5 * radius / tolerance)**(1/8)) * angle / 90), 2.0)) + 1
|
|
11
11
|
return bspy.Spline.section([(radius * np.cos(u * angle * np.pi / 180), radius * np.sin(u * angle * np.pi / 180), 90 + u * angle, 1.0 / radius) for u in np.linspace(0.0, 1.0, samples)])
|
|
12
12
|
|
|
13
|
+
def cone(radius1, radius2, height, tolerance = None):
|
|
14
|
+
if tolerance is None:
|
|
15
|
+
tolerance = 1.0e-12
|
|
16
|
+
bigRadius = max(radius1, radius2)
|
|
17
|
+
radius1 /= bigRadius
|
|
18
|
+
radius2 /= bigRadius
|
|
19
|
+
bottom = [[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]] @ bspy.Spline.circular_arc(bigRadius, 360.0, tolerance)
|
|
20
|
+
top = radius2 * bottom + [0.0, 0.0, height]
|
|
21
|
+
bottom = radius1 * bottom
|
|
22
|
+
return bspy.Spline.ruled_surface(bottom, top)
|
|
23
|
+
|
|
13
24
|
# Courtesy of Michael Epton - Translated from his F77 code lgnzro
|
|
14
25
|
def _legendre_polynomial_zeros(degree):
|
|
15
26
|
def legendre(degree, x):
|
|
@@ -271,11 +282,18 @@ def contour(F, knownXValues, dF = None, epsilon = None, metadata = {}):
|
|
|
271
282
|
|
|
272
283
|
# Rescale x(t) back to original data points.
|
|
273
284
|
coefs = (coefsMin + coefs.T * coefsMaxMinusMin).T
|
|
274
|
-
spline = bspy.Spline(1, nDep, (order,), (nCoef,), (knots,), coefs,
|
|
285
|
+
spline = bspy.Spline(1, nDep, (order,), (nCoef,), (knots,), coefs, metadata)
|
|
275
286
|
if isinstance(F, bspy.Spline):
|
|
276
287
|
spline = spline.confine(F.domain())
|
|
277
288
|
return spline
|
|
278
289
|
|
|
290
|
+
def cylinder(radius, height, tolerance = None):
|
|
291
|
+
if tolerance is None:
|
|
292
|
+
tolerance = 1.0e-12
|
|
293
|
+
bottom = [[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]] @ bspy.Spline.circular_arc(radius, 360.0, tolerance)
|
|
294
|
+
top = bottom + [0.0, 0.0, height]
|
|
295
|
+
return bspy.Spline.ruled_surface(bottom, top)
|
|
296
|
+
|
|
279
297
|
def four_sided_patch(bottom, right, top, left, surfParam = 0.5):
|
|
280
298
|
if bottom.nInd != 1 or right.nInd != 1 or top.nInd != 1 or left.nInd != 1:
|
|
281
299
|
raise ValueError("Input curves must have one independent variable")
|
|
@@ -328,7 +346,7 @@ def four_sided_patch(bottom, right, top, left, surfParam = 0.5):
|
|
|
328
346
|
biLinear = bspy.Spline.ruled_surface(bottomLine, topLine)
|
|
329
347
|
coons = bottomTop.add(leftRight, ((0,1), (1,0))) - biLinear
|
|
330
348
|
|
|
331
|
-
# Determine the Greville
|
|
349
|
+
# Determine the Greville abscissae to use as collocation points
|
|
332
350
|
|
|
333
351
|
uPts = coons.greville(0)[1 : -1]
|
|
334
352
|
vPts = coons.greville(1)[1 : -1]
|
|
@@ -365,143 +383,153 @@ def four_sided_patch(bottom, right, top, left, surfParam = 0.5):
|
|
|
365
383
|
|
|
366
384
|
return (1.0 - surfParam) * coons + surfParam * laplace
|
|
367
385
|
|
|
368
|
-
def least_squares(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
for knots in knotList:
|
|
399
|
-
nCf = int(math.ceil(len(knots) * scaling))
|
|
400
|
-
nCoef.append(nCf)
|
|
401
|
-
totalCoef *= nCf
|
|
402
|
-
|
|
403
|
-
# Compute "ideal" knots for each independent variable, based on the number of coefficients and the sample values.
|
|
404
|
-
# Piegl, Les A., and Wayne Tiller. "Surface approximation to scanned data." The visual computer 16 (2000): 386-395.
|
|
405
|
-
newKnotList = []
|
|
406
|
-
for iInd, ord, nCf, knots in zip(range(nInd), order, nCoef, knotList):
|
|
407
|
-
degree = ord - 1
|
|
408
|
-
newKnots = [knots[0]] * ord
|
|
409
|
-
inc = len(knots)/nCf
|
|
410
|
-
low = 0
|
|
411
|
-
d = -1
|
|
412
|
-
w = np.empty((nCf,), float)
|
|
413
|
-
for i in range(nCf):
|
|
414
|
-
d += inc
|
|
415
|
-
high = int(d + 0.5 + 1) # Paper's algorithm sets high to d + 0.5, but only references high + 1
|
|
416
|
-
w[i] = np.mean(knots[low:high])
|
|
417
|
-
low = high
|
|
418
|
-
for i in range(1, nCf - degree):
|
|
419
|
-
newKnots.append(np.mean(w[i:i + degree]))
|
|
420
|
-
newKnots += [knots[-1]] * ord
|
|
421
|
-
newKnotList.append(np.array(newKnots, knots.dtype))
|
|
422
|
-
knotList = newKnotList
|
|
386
|
+
def least_squares(uValues, dataPoints, order = None, knots = None, compression = 0.0,
|
|
387
|
+
tolerance = None, fixEnds = False, metadata = {}):
|
|
388
|
+
|
|
389
|
+
# Preprocess all the input if everything is a spline
|
|
390
|
+
|
|
391
|
+
dataPoints = np.array(dataPoints)
|
|
392
|
+
flatView = np.ravel(dataPoints)
|
|
393
|
+
splineInput = False
|
|
394
|
+
if type(flatView[0]) is bspy.Spline:
|
|
395
|
+
splineInput = True
|
|
396
|
+
nInd = flatView[0].nInd
|
|
397
|
+
nDep = flatView[0].nDep
|
|
398
|
+
splineDomain = flatView[0].domain()
|
|
399
|
+
for spline in flatView:
|
|
400
|
+
if spline.nInd != nInd: raise ValueError("Input splines have different number of independent variables")
|
|
401
|
+
if spline.nDep != nDep: raise ValueError("Input splines have different number of dependent variables")
|
|
402
|
+
if not np.array_equal(spline.domain(), splineDomain): raise ValueError("Input splines have different domains")
|
|
403
|
+
commonSplines = bspy.Spline.common_basis(flatView)
|
|
404
|
+
foldedData = []
|
|
405
|
+
for spline in commonSplines:
|
|
406
|
+
folded, unfoldInfo = spline.fold(list(range(nInd)))
|
|
407
|
+
foldedData.append(folded())
|
|
408
|
+
foldedData = np.array(foldedData).T
|
|
409
|
+
dataPoints = np.reshape(foldedData, (foldedData.shape[0],) + dataPoints.shape)
|
|
410
|
+
|
|
411
|
+
# Preprocess the parameters values for the points
|
|
412
|
+
|
|
413
|
+
if np.isscalar(uValues[0]):
|
|
414
|
+
nInd = 1
|
|
415
|
+
uValues = [uValues]
|
|
423
416
|
else:
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
newKnotList.append(np.array(knots))
|
|
433
|
-
if not(totalCoef <= totalDataPoints): raise ValueError(f"Insufficient number of data points. You need at least {totalCoef}.")
|
|
434
|
-
knotList = newKnotList
|
|
417
|
+
nInd = len(uValues)
|
|
418
|
+
domain = []
|
|
419
|
+
for iInd in range(nInd):
|
|
420
|
+
uMin = np.min(uValues[iInd])
|
|
421
|
+
uMax = np.max(uValues[iInd])
|
|
422
|
+
domain.append([uMin, uMax])
|
|
423
|
+
for i in range(len(uValues[iInd]) - 1):
|
|
424
|
+
if uValues[iInd][i] > uValues[iInd][i + 1]: raise ValueError("Independent variable values are out of order")
|
|
435
425
|
|
|
436
|
-
#
|
|
437
|
-
# b contains point values for the dependent variables, and the x contains the desired coefficients.
|
|
438
|
-
A = np.zeros((totalDataPoints, totalCoef), type(dataPoints[0][0]))
|
|
439
|
-
b = np.empty((totalDataPoints, nDep), A.dtype)
|
|
440
|
-
|
|
441
|
-
# Fill in the bspline values in A and the dependent point values in b at row at a time.
|
|
442
|
-
# Note that if a data point also specifies first derivatives, it fills out nInd + 1 rows (the point and its derivatives).
|
|
443
|
-
row = 0
|
|
444
|
-
for point in dataPoints:
|
|
445
|
-
hasDerivatives = len(point) == nInd + nDep * (nInd + 1)
|
|
446
|
-
|
|
447
|
-
# Compute the bspline values (and their first derivatives as needed).
|
|
448
|
-
bValueData = []
|
|
449
|
-
for knots, ord, nCf, u in zip(knotList, order, nCoef, point[:nInd]):
|
|
450
|
-
ix = np.searchsorted(knots, u, 'right')
|
|
451
|
-
ix = min(ix, nCf)
|
|
452
|
-
bValueData.append((ix, bspy.Spline.bspline_values(ix, knots, ord, u), \
|
|
453
|
-
bspy.Spline.bspline_values(ix, knots, ord, u, 1) if hasDerivatives else None))
|
|
454
|
-
|
|
455
|
-
# Compute the values for the A array.
|
|
456
|
-
# It's a little tricky because we have to multiply nInd different bspline arrays of different sizes
|
|
457
|
-
# and index into flattened A array. The solution is to loop through the total number of entries
|
|
458
|
-
# being changed (totalOrder), and compute the array indices via mods and multiplies.
|
|
459
|
-
indices = [0] * nInd
|
|
460
|
-
for i in range(totalOrder):
|
|
461
|
-
column = 0
|
|
462
|
-
bValues = np.ones((nInd + 1,), A.dtype)
|
|
463
|
-
for j, ord, nCf, index, (ix, values, dValues) in zip(range(1, nInd + 1), order, nCoef, indices, bValueData):
|
|
464
|
-
column = column * nCf + ix - ord + index
|
|
465
|
-
# Compute the bspline value for this specific element of A.
|
|
466
|
-
bValues[0] *= values[index]
|
|
467
|
-
if hasDerivatives:
|
|
468
|
-
# Compute the first derivative values for each independent variable.
|
|
469
|
-
for k in range(1, nInd + 1):
|
|
470
|
-
bValues[k] *= dValues[index] if k == j else values[index]
|
|
471
|
-
|
|
472
|
-
# Assign all the values and derivatives.
|
|
473
|
-
A[row, column] = bValues[0]
|
|
474
|
-
if hasDerivatives:
|
|
475
|
-
for k in range(1, nInd + 1):
|
|
476
|
-
A[row + k, column] = bValues[k]
|
|
477
|
-
|
|
478
|
-
# Increment the bspline indices.
|
|
479
|
-
for j in range(nInd - 1, -1, -1):
|
|
480
|
-
indices[j] = (indices[j] + 1) % order[j]
|
|
481
|
-
if indices[j] > 0:
|
|
482
|
-
break
|
|
426
|
+
# Preprocess the data points
|
|
483
427
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
428
|
+
if len(dataPoints.shape) != nInd + 1: raise ValueError("dataPoints has the wrong shape")
|
|
429
|
+
nDep = dataPoints.shape[0]
|
|
430
|
+
pointsPerDirection = list(dataPoints.shape)[1:]
|
|
431
|
+
nPoints = 1
|
|
432
|
+
for i, nu in enumerate(pointsPerDirection):
|
|
433
|
+
if nu != len(uValues[i]): raise ValueError("Wrong number of parameter values in one or more directions")
|
|
434
|
+
nPoints *= nu
|
|
489
435
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
436
|
+
# Make sure the order makes sense
|
|
437
|
+
|
|
438
|
+
if order is None:
|
|
439
|
+
order = [min(4, nu) for nu in pointsPerDirection]
|
|
440
|
+
for nP, nOrder in zip(pointsPerDirection, order):
|
|
441
|
+
if nP < nOrder: raise ValueError("Not enough points in one or more directions")
|
|
442
|
+
|
|
443
|
+
# Determine the (initial) knots array
|
|
496
444
|
|
|
497
|
-
|
|
498
|
-
|
|
445
|
+
if not(0.0 <= compression <= 1.0): raise ValueError("compression not between 0.0 and 1.0")
|
|
446
|
+
if tolerance is None:
|
|
447
|
+
tolerance = np.finfo(float).max
|
|
448
|
+
else:
|
|
449
|
+
compression = 1.0
|
|
450
|
+
if knots is None:
|
|
451
|
+
knots = [np.array(order[iInd] * [domain[iInd][0]] + order[iInd] * [domain[iInd][1]]) for iInd in range(nInd)]
|
|
452
|
+
for iInd, (nP, nOrder) in enumerate(zip(pointsPerDirection, order)):
|
|
453
|
+
knotsToAdd = int((nP - nOrder) * (1.0 - compression) + 0.9999999999)
|
|
454
|
+
addSpots = np.linspace(0.0, nP - 1.0, knotsToAdd + 2)[1 : -1]
|
|
455
|
+
newKnots = []
|
|
456
|
+
for newSpot in addSpots:
|
|
457
|
+
ix = int(newSpot)
|
|
458
|
+
alpha = newSpot - ix
|
|
459
|
+
newKnots.append((1.0 - alpha) * uValues[iInd][ix] + alpha * uValues[iInd][ix + 1])
|
|
460
|
+
knots[iInd] = np.sort(np.append(knots[iInd], newKnots))
|
|
461
|
+
else:
|
|
462
|
+
knots = tuple(np.array(kk) for kk in knots)
|
|
463
|
+
for iInd in range(nInd):
|
|
464
|
+
if domain[iInd][0] < knots[iInd][order[iInd] - 1] or \
|
|
465
|
+
domain[iInd][1] > knots[iInd][-order[iInd]]: raise ValueError("One or more dataPoints are outside the domain of the spline")
|
|
499
466
|
|
|
500
|
-
#
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
467
|
+
# Loop through each independent variable and fit all of the data
|
|
468
|
+
|
|
469
|
+
for iInd in range(nInd):
|
|
470
|
+
nRows = pointsPerDirection[iInd]
|
|
471
|
+
b = np.swapaxes(dataPoints, 0, iInd + 1)
|
|
472
|
+
loopDep = nDep * nPoints // nRows
|
|
473
|
+
b = np.reshape(b, (nRows, loopDep))
|
|
474
|
+
done = False
|
|
475
|
+
while not done:
|
|
476
|
+
nCols = len(knots[iInd]) - order[iInd]
|
|
477
|
+
A = np.zeros((nRows, nCols))
|
|
478
|
+
u = -np.finfo(float).max
|
|
479
|
+
fixedRows = []
|
|
480
|
+
for iRow in range(nRows):
|
|
481
|
+
uNew = uValues[iInd][iRow]
|
|
482
|
+
if uNew != u:
|
|
483
|
+
iDerivative = 0
|
|
484
|
+
u = uNew
|
|
485
|
+
ix = np.searchsorted(knots[iInd], u, 'right')
|
|
486
|
+
ix = min(ix, nCols)
|
|
487
|
+
else:
|
|
488
|
+
iDerivative += 1
|
|
489
|
+
row = bspy.Spline.bspline_values(ix, knots[iInd], order[iInd], u, iDerivative)
|
|
490
|
+
A[iRow, ix - order[iInd] : ix] = row
|
|
491
|
+
if fixEnds and (u == uValues[iInd][0] or u == uValues[iInd][-1]):
|
|
492
|
+
fixedRows.append(iRow)
|
|
493
|
+
nInterpolationConditions = len(fixedRows)
|
|
494
|
+
if nInterpolationConditions != 0:
|
|
495
|
+
C = np.take(A, fixedRows, 0)
|
|
496
|
+
d = np.take(b, fixedRows, 0)
|
|
497
|
+
AUse = np.delete(A, fixedRows, 0)
|
|
498
|
+
bUse = np.delete(b, fixedRows, 0)
|
|
499
|
+
U, Sigma, VT = np.linalg.svd(C)
|
|
500
|
+
d = U.T @ d
|
|
501
|
+
for iRow in range(nInterpolationConditions):
|
|
502
|
+
d[iRow] = d[iRow] / Sigma[iRow]
|
|
503
|
+
rangeCols = np.take(VT.T, range(nInterpolationConditions), 1)
|
|
504
|
+
nullspace = np.delete(VT.T, range(nInterpolationConditions), 1)
|
|
505
|
+
x1 = rangeCols @ d
|
|
506
|
+
b1 = bUse - AUse @ x1
|
|
507
|
+
xNullspace, residuals, rank, s = np.linalg.lstsq(AUse @ nullspace, b1, rcond = None)
|
|
508
|
+
x = x1 + nullspace @ xNullspace
|
|
509
|
+
else:
|
|
510
|
+
x, residuals, rank, s = np.linalg.lstsq(A, b, rcond = None)
|
|
511
|
+
residuals = b - A @ x
|
|
512
|
+
maxError = 0.0
|
|
513
|
+
for iRow in range(nRows):
|
|
514
|
+
rowError = np.linalg.norm(residuals[iRow, :])
|
|
515
|
+
if rowError > maxError:
|
|
516
|
+
maxError = rowError
|
|
517
|
+
maxRow = iRow
|
|
518
|
+
if maxError <= tolerance / nInd:
|
|
519
|
+
done = True
|
|
520
|
+
else:
|
|
521
|
+
ix = np.searchsorted(knots[iInd], uValues[iInd][maxRow], 'right')
|
|
522
|
+
ix = min(ix, nCols)
|
|
523
|
+
knots[iInd] = np.sort(np.append(knots[iInd], 0.5 * (knots[iInd][ix - 1] + knots[iInd][ix])))
|
|
524
|
+
pointsPerDirection[iInd] = nDep
|
|
525
|
+
x = np.reshape(x, [nCols] + pointsPerDirection)
|
|
526
|
+
dataPoints = np.swapaxes(x, 0, iInd + 1)
|
|
527
|
+
pointsPerDirection[iInd] = nCols
|
|
528
|
+
nPoints = nCols * nPoints // nRows
|
|
529
|
+
splineFit = bspy.Spline(nInd, nDep, order, pointsPerDirection, knots, dataPoints, metadata = metadata)
|
|
530
|
+
if splineInput:
|
|
531
|
+
splineFit = splineFit.unfold(range(unfoldInfo.nInd), unfoldInfo)
|
|
532
|
+
return splineFit
|
|
505
533
|
|
|
506
534
|
def line(startPoint, endPoint):
|
|
507
535
|
startPoint = bspy.Spline.point(startPoint)
|
|
@@ -528,14 +556,15 @@ def ruled_surface(curve1, curve2):
|
|
|
528
556
|
# Ensure that the splines are compatible
|
|
529
557
|
if curve1.nInd != curve2.nInd: raise ValueError("Splines must have the same number of independent variables")
|
|
530
558
|
if curve1.nDep != curve2.nDep: raise ValueError("Splines must have the same number of dependent variables")
|
|
531
|
-
|
|
532
|
-
[newCurve1, newCurve2] = curve1.common_basis([curve2], indMap)
|
|
559
|
+
[newCurve1, newCurve2] = bspy.Spline.common_basis([curve1, curve2])
|
|
533
560
|
|
|
534
561
|
# Generate the ruled spline between them
|
|
562
|
+
myCoefs1 = np.reshape(newCurve1.coefs, newCurve1.coefs.shape + (1,))
|
|
563
|
+
myCoefs2 = np.reshape(newCurve2.coefs, newCurve2.coefs.shape + (1,))
|
|
564
|
+
newCoefs = np.append(myCoefs1, myCoefs2, newCurve1.nInd + 1)
|
|
535
565
|
return bspy.Spline(curve1.nInd + 1, curve1.nDep, list(newCurve1.order) + [2],
|
|
536
566
|
list(newCurve1.nCoef) + [2], list(newCurve1.knots) + [[0.0, 0.0, 1.0, 1.0]],
|
|
537
|
-
|
|
538
|
-
accuracy = max(newCurve1.accuracy, newCurve2.accuracy))
|
|
567
|
+
newCoefs)
|
|
539
568
|
|
|
540
569
|
def section(xytk):
|
|
541
570
|
def twoPointSection(startPointX, startPointY, startAngle, startKappa, endPointX, endPointY, endAngle, endKappa):
|
bspy/_spline_intersection.py
CHANGED
|
@@ -522,9 +522,9 @@ def contours(self):
|
|
|
522
522
|
continue # Try a different theta
|
|
523
523
|
|
|
524
524
|
# Find turning points by combining self and turningPointDeterminant into a system and processing its zeros.
|
|
525
|
-
systemSelf, systemTurningPointDeterminant =
|
|
525
|
+
systemSelf, systemTurningPointDeterminant = bspy.Spline.common_basis((self, turningPointDeterminant))
|
|
526
526
|
system = type(systemSelf)(self.nInd, self.nInd, systemSelf.order, systemSelf.nCoef, systemSelf.knots, \
|
|
527
|
-
np.concatenate((systemSelf.coefs, systemTurningPointDeterminant.coefs)), systemSelf.
|
|
527
|
+
np.concatenate((systemSelf.coefs, systemTurningPointDeterminant.coefs)), systemSelf.metadata)
|
|
528
528
|
zeros = system.zeros()
|
|
529
529
|
for uvw in zeros:
|
|
530
530
|
if isinstance(uvw, tuple):
|
|
@@ -566,7 +566,7 @@ def contours(self):
|
|
|
566
566
|
degree = self.order[1] - 1
|
|
567
567
|
for i in range(1, self.nCoef[1]):
|
|
568
568
|
panelCoefs[self.nDep, :, i] = panelCoefs[self.nDep, :, i - 1] + ((self.knots[1][degree + i] - self.knots[1][i]) / degree) * sinTheta
|
|
569
|
-
panel = type(self)(self.nInd, self.nInd, self.order, self.nCoef, self.knots, panelCoefs, self.
|
|
569
|
+
panel = type(self)(self.nInd, self.nInd, self.order, self.nCoef, self.knots, panelCoefs, self.metadata)
|
|
570
570
|
|
|
571
571
|
# Okay, we have everything we need to determine the contour topology and points along each contour.
|
|
572
572
|
# We've done the first two steps of Grandine and Klein's algorithm:
|