bspy 4.2__py3-none-any.whl → 4.3__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 +72 -91
- bspy/_spline_evaluation.py +2 -2
- bspy/_spline_fitting.py +4 -4
- bspy/_spline_intersection.py +285 -273
- bspy/_spline_operations.py +45 -41
- bspy/hyperplane.py +7 -3
- bspy/manifold.py +8 -3
- bspy/solid.py +7 -3
- bspy/spline.py +17 -5
- bspy/splineOpenGLFrame.py +346 -303
- bspy/spline_block.py +155 -38
- bspy/viewer.py +20 -11
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/METADATA +5 -3
- bspy-4.3.dist-info/RECORD +18 -0
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/WHEEL +1 -1
- bspy-4.2.dist-info/RECORD +0 -18
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/LICENSE +0 -0
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/top_level.txt +0 -0
bspy/__init__.py
CHANGED
|
@@ -12,7 +12,7 @@ Available subpackages
|
|
|
12
12
|
`bspy.spline` : Provides the `Spline` subclass of `Manifold` that models, represents, and processes
|
|
13
13
|
piecewise polynomial tensor product functions (spline functions) as linear combinations of B-splines.
|
|
14
14
|
|
|
15
|
-
`bspy.spline_block` : Provides the `SplineBlock` class that
|
|
15
|
+
`bspy.spline_block` : Provides the `SplineBlock` class that processes an array-like collection of splines which represent a system of equations.
|
|
16
16
|
|
|
17
17
|
`bspy.splineOpenGLFrame` : Provides the `SplineOpenGLFrame` class, a tkinter `OpenGLFrame` with shaders to display splines.
|
|
18
18
|
|
bspy/_spline_domain.py
CHANGED
|
@@ -310,26 +310,73 @@ def fold(self, foldedInd):
|
|
|
310
310
|
coefficientlessSpline = type(self)(len(coefficientlessOrder), 0, coefficientlessOrder, coefficientlessNCoef, coefficientlessKnots, coefficientlessCoefs, self.metadata)
|
|
311
311
|
return foldedSpline, coefficientlessSpline
|
|
312
312
|
|
|
313
|
-
def insert_knots(self,
|
|
314
|
-
if not(len(
|
|
315
|
-
knotsList = list(self.knots)
|
|
316
|
-
coefs = self.coefs
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
313
|
+
def insert_knots(self, newKnotList):
|
|
314
|
+
if not(len(newKnotList) == self.nInd): raise ValueError("Invalid newKnots")
|
|
315
|
+
knotsList = list(self.knots) # Create a new knot list
|
|
316
|
+
coefs = self.coefs # Set initial value for coefs to check later if it's changed
|
|
317
|
+
|
|
318
|
+
# Insert new knots into each independent variable.
|
|
319
|
+
for ind, (order, knots, newKnots) in enumerate(zip(self.order, self.knots, newKnotList)):
|
|
320
|
+
coefs = coefs.swapaxes(0, ind + 1) # Swap dependent and independent variable (swap back later)
|
|
321
|
+
degree = order - 1
|
|
322
|
+
for knot in newKnots:
|
|
323
|
+
# Determine new knot multiplicity.
|
|
324
|
+
if np.isscalar(knot):
|
|
325
|
+
multiplicity = 1
|
|
324
326
|
else:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
327
|
+
multiplicity = knot[1]
|
|
328
|
+
knot = knot[0]
|
|
329
|
+
if multiplicity < 1:
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
# Check if knot and its total multiplicity is valid.
|
|
333
|
+
if knot < knots[degree] or knot > knots[-order]:
|
|
334
|
+
raise ValueError(f"Knot insertion outside domain: {knot}")
|
|
335
|
+
position = np.searchsorted(knots, knot, 'right')
|
|
336
|
+
oldMultiplicity = 0
|
|
337
|
+
for k in knots[position - 1::-1]:
|
|
338
|
+
if knot == k:
|
|
339
|
+
oldMultiplicity += 1
|
|
340
|
+
else:
|
|
341
|
+
break
|
|
342
|
+
if oldMultiplicity + multiplicity > order:
|
|
343
|
+
raise ValueError("Knot multiplicity > order")
|
|
344
|
+
|
|
345
|
+
# Initialize oldCoefs and expanded coefs array with multiplicity new coefficients, as well as some indices.
|
|
346
|
+
oldCoefs = coefs[position - order:position].copy()
|
|
347
|
+
lastKnotIndex = position - oldMultiplicity
|
|
348
|
+
firstCoefIndex = position - degree
|
|
349
|
+
coefs = np.insert(coefs, firstCoefIndex, oldCoefs[:multiplicity], axis=0)
|
|
350
|
+
# Compute inserted coefficients (multiplicity of them) and the degree - oldMultiplicity - 1 number of changed coefficients.
|
|
351
|
+
for j in range(multiplicity):
|
|
352
|
+
# Allocate new coefficients for the current multiplicity.
|
|
353
|
+
size = degree - oldMultiplicity - j
|
|
354
|
+
if size < 1:
|
|
355
|
+
# Full multiplicity knot, so use oldCoefs.
|
|
356
|
+
coefs[firstCoefIndex + j] = oldCoefs[0]
|
|
357
|
+
else:
|
|
358
|
+
# Otherwise, allocate space for newCoefs.
|
|
359
|
+
newCoefs = np.empty((size, *coefs.shape[1:]), coefs.dtype)
|
|
360
|
+
|
|
361
|
+
# Compute the new coefficients.
|
|
362
|
+
for i, k in zip(range(size), range(lastKnotIndex - size, lastKnotIndex)):
|
|
363
|
+
alpha = (knot - knots[k]) / (knots[k + degree - j] - knots[k])
|
|
364
|
+
newCoefs[i] = (1.0 - alpha) * oldCoefs[i] + alpha * oldCoefs[i + 1]
|
|
365
|
+
|
|
366
|
+
# Assign the ends of the new coefficients into their respective positions.
|
|
367
|
+
coefs[firstCoefIndex + j] = newCoefs[0]
|
|
368
|
+
if size > 1:
|
|
369
|
+
coefs[lastKnotIndex + multiplicity - j - 2] = newCoefs[-1]
|
|
370
|
+
oldCoefs = newCoefs
|
|
371
|
+
|
|
372
|
+
# Assign remaining computed coefficients (the ones in the middle).
|
|
373
|
+
if size > 2:
|
|
374
|
+
coefs[firstCoefIndex + multiplicity:firstCoefIndex + multiplicity + size - 2] = newCoefs[1:-1]
|
|
375
|
+
|
|
376
|
+
# Insert the inserted coefficients and inserted knots.
|
|
377
|
+
knotsList[ind] = knots = np.insert(knots, position, (knot,) * multiplicity)
|
|
378
|
+
|
|
379
|
+
coefs = coefs.swapaxes(0, ind + 1) # Swap back
|
|
333
380
|
|
|
334
381
|
if self.coefs is coefs:
|
|
335
382
|
return self
|
|
@@ -518,66 +565,6 @@ def reverse(self, variable = 0):
|
|
|
518
565
|
newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
|
|
519
566
|
return newFolded.unfold(myIndices, basisInfo)
|
|
520
567
|
|
|
521
|
-
def split(self, minContinuity = 0, breaks = None):
|
|
522
|
-
if minContinuity < 0: raise ValueError("minContinuity must be >= 0")
|
|
523
|
-
if breaks is not None and len(breaks) != self.nInd: raise ValueError("Invalid breaks")
|
|
524
|
-
if self.nInd < 1: return self
|
|
525
|
-
|
|
526
|
-
# Step 1: Determine the knots to insert.
|
|
527
|
-
newKnotsList = []
|
|
528
|
-
for i, order, knots in zip(range(self.nInd), self.order, self.knots):
|
|
529
|
-
unique, counts = np.unique(knots, return_counts=True)
|
|
530
|
-
newKnots = []
|
|
531
|
-
for knot, count in zip(unique, counts):
|
|
532
|
-
assert count <= order
|
|
533
|
-
if count > order - 1 - minContinuity:
|
|
534
|
-
newKnots += [knot] * (order - count)
|
|
535
|
-
if breaks is not None:
|
|
536
|
-
for knot in breaks[i]:
|
|
537
|
-
if knot not in unique:
|
|
538
|
-
newKnots += [knot] * order
|
|
539
|
-
newKnotsList.append(newKnots)
|
|
540
|
-
|
|
541
|
-
# Step 2: Insert the knots.
|
|
542
|
-
spline = self.insert_knots(newKnotsList)
|
|
543
|
-
if spline is self:
|
|
544
|
-
return np.full((1,) * spline.nInd, spline)
|
|
545
|
-
|
|
546
|
-
# Step 3: Store the indices of the full order knots.
|
|
547
|
-
indexList = []
|
|
548
|
-
splineCount = []
|
|
549
|
-
totalSplineCount = 1
|
|
550
|
-
for order, knots in zip(spline.order, spline.knots):
|
|
551
|
-
unique, counts = np.unique(knots, return_counts=True)
|
|
552
|
-
indices = np.searchsorted(knots, unique)
|
|
553
|
-
fullOrder = []
|
|
554
|
-
for ix, count in zip(indices, counts):
|
|
555
|
-
if count == order:
|
|
556
|
-
fullOrder.append(ix)
|
|
557
|
-
indexList.append(fullOrder)
|
|
558
|
-
splines = len(fullOrder) - 1
|
|
559
|
-
splineCount.append(splines)
|
|
560
|
-
totalSplineCount *= splines
|
|
561
|
-
|
|
562
|
-
# Step 4: Slice up the spline.
|
|
563
|
-
splineArray = np.empty(totalSplineCount, object)
|
|
564
|
-
for i in range(totalSplineCount):
|
|
565
|
-
knotsList = []
|
|
566
|
-
coefIndex = [slice(None)] # First index is for nDep
|
|
567
|
-
ix = i
|
|
568
|
-
for order, knots, splines, indices in zip(spline.order, spline.knots, splineCount, indexList):
|
|
569
|
-
j = ix % splines
|
|
570
|
-
ix = ix // splines
|
|
571
|
-
leftIndex = indices[j]
|
|
572
|
-
rightIndex = indices[j + 1]
|
|
573
|
-
knotsList.append(knots[leftIndex:rightIndex + order])
|
|
574
|
-
coefIndex.append(slice(leftIndex, rightIndex))
|
|
575
|
-
coefs = spline.coefs[tuple(coefIndex)]
|
|
576
|
-
splineArray[i] = type(spline)(spline.nInd, spline.nDep, spline.order, coefs.shape[1:], knotsList, coefs, spline.metadata)
|
|
577
|
-
|
|
578
|
-
# Return the transpose because we put the splines into splineArray dimensions in reverse order.
|
|
579
|
-
return splineArray.reshape(tuple(reversed(splineCount))).T
|
|
580
|
-
|
|
581
568
|
def transpose(self, axes=None):
|
|
582
569
|
if axes is None:
|
|
583
570
|
axes = range(self.nInd)[::-1]
|
|
@@ -614,17 +601,14 @@ def trim(self, newDomain):
|
|
|
614
601
|
if unique[i] - bounds[0] < epsilon:
|
|
615
602
|
bounds[0] = unique[i]
|
|
616
603
|
multiplicity = order - counts[i]
|
|
617
|
-
if i > 0:
|
|
618
|
-
noChange = False
|
|
619
604
|
elif i > 0 and bounds[0] - unique[i - 1] < epsilon:
|
|
620
605
|
bounds[0] = unique[i - 1]
|
|
621
606
|
multiplicity = order - counts[i - 1]
|
|
622
|
-
if i - 1 > 0:
|
|
623
|
-
noChange = False
|
|
624
607
|
else:
|
|
625
608
|
multiplicity = order
|
|
626
|
-
|
|
627
|
-
|
|
609
|
+
if multiplicity > 0:
|
|
610
|
+
newKnots.append((bounds[0], multiplicity))
|
|
611
|
+
noChange = False
|
|
628
612
|
|
|
629
613
|
if not np.isnan(bounds[1]):
|
|
630
614
|
if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
|
|
@@ -634,19 +618,16 @@ def trim(self, newDomain):
|
|
|
634
618
|
if unique[i] - bounds[1] < epsilon:
|
|
635
619
|
bounds[1] = unique[i]
|
|
636
620
|
multiplicity = order - counts[i]
|
|
637
|
-
if i < len(unique) - 1:
|
|
638
|
-
noChange = False
|
|
639
621
|
elif i > 0 and bounds[1] - unique[i - 1] < epsilon:
|
|
640
622
|
bounds[1] = unique[i - 1]
|
|
641
623
|
multiplicity = order - counts[i - i]
|
|
642
|
-
noChange = False # i < len(unique) - 1
|
|
643
624
|
else:
|
|
644
625
|
multiplicity = order
|
|
645
|
-
|
|
626
|
+
if multiplicity > 0:
|
|
627
|
+
newKnots.append((bounds[1], multiplicity))
|
|
628
|
+
noChange = False
|
|
646
629
|
|
|
647
630
|
newKnotsList.append(newKnots)
|
|
648
|
-
if len(newKnots) > 0:
|
|
649
|
-
noChange = False
|
|
650
631
|
|
|
651
632
|
if noChange:
|
|
652
633
|
return self
|
bspy/_spline_evaluation.py
CHANGED
|
@@ -236,8 +236,8 @@ def normal(self, uvw, normalize=True, indices=None):
|
|
|
236
236
|
normal = np.empty(nDep, dtype)
|
|
237
237
|
else:
|
|
238
238
|
normal = np.empty(len(indices), dtype)
|
|
239
|
-
for i in indices:
|
|
240
|
-
normal[
|
|
239
|
+
for ix, i in enumerate(indices):
|
|
240
|
+
normal[ix] = sign * ((-1) ** i) * np.linalg.det(tangentSpace[[j for j in range(nDep) if i != j]])
|
|
241
241
|
|
|
242
242
|
# Normalize the result as needed.
|
|
243
243
|
if normalize:
|
bspy/_spline_fitting.py
CHANGED
|
@@ -904,11 +904,11 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
904
904
|
for i, knot in enumerate(uniqueKnots):
|
|
905
905
|
howMany = currentGuess.order[0] - indices[i + 1] + indices[i] - nOrder
|
|
906
906
|
if howMany > 0:
|
|
907
|
-
knotsToAdd
|
|
907
|
+
knotsToAdd.append((knot, howMany))
|
|
908
908
|
if howMany < 0:
|
|
909
|
-
knotsToRemove
|
|
909
|
+
knotsToRemove.append((knot, abs(howMany)))
|
|
910
910
|
currentGuess = currentGuess.insert_knots([knotsToAdd])
|
|
911
|
-
for
|
|
911
|
+
for (knot, howMany) in knotsToRemove:
|
|
912
912
|
ix = np.searchsorted(currentGuess.knots[0], knot, side = 'left')
|
|
913
913
|
for iy in range(howMany):
|
|
914
914
|
currentGuess, residual = currentGuess.remove_knot(ix, nLeft, nRight)
|
|
@@ -1065,7 +1065,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
1065
1065
|
knotsToAdd = []
|
|
1066
1066
|
for knot0, knot1 in zip(currentGuess.knots[0][:-1], currentGuess.knots[0][1:]):
|
|
1067
1067
|
if knot0 < knot1:
|
|
1068
|
-
knotsToAdd
|
|
1068
|
+
knotsToAdd.append((0.5 * (knot0 + knot1), currentGuess.order[0] - nOrder))
|
|
1069
1069
|
previousGuess = currentGuess
|
|
1070
1070
|
currentGuess = currentGuess.insert_knots([knotsToAdd])
|
|
1071
1071
|
|