bspy 4.2__py3-none-any.whl → 4.4__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 +74 -92
- bspy/_spline_evaluation.py +3 -3
- bspy/_spline_fitting.py +33 -12
- bspy/_spline_intersection.py +294 -279
- bspy/_spline_milling.py +233 -0
- bspy/_spline_operations.py +98 -81
- bspy/hyperplane.py +7 -3
- bspy/manifold.py +8 -3
- bspy/solid.py +8 -4
- bspy/spline.py +124 -21
- bspy/splineOpenGLFrame.py +346 -303
- bspy/spline_block.py +155 -38
- bspy/viewer.py +20 -11
- {bspy-4.2.dist-info → bspy-4.4.dist-info}/METADATA +14 -11
- bspy-4.4.dist-info/RECORD +19 -0
- {bspy-4.2.dist-info → bspy-4.4.dist-info}/WHEEL +1 -1
- bspy-4.2.dist-info/RECORD +0 -18
- {bspy-4.2.dist-info → bspy-4.4.dist-info/licenses}/LICENSE +0 -0
- {bspy-4.2.dist-info → bspy-4.4.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,74 @@ 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
|
+
knot = knots.dtype.type(knot) # Cast to correct type
|
|
334
|
+
if knot < knots[degree] or knot > knots[-order]:
|
|
335
|
+
raise ValueError(f"Knot insertion outside domain: {knot}")
|
|
336
|
+
position = np.searchsorted(knots, knot, 'right')
|
|
337
|
+
oldMultiplicity = 0
|
|
338
|
+
for k in knots[position - 1::-1]:
|
|
339
|
+
if knot == k:
|
|
340
|
+
oldMultiplicity += 1
|
|
341
|
+
else:
|
|
342
|
+
break
|
|
343
|
+
if oldMultiplicity + multiplicity > order:
|
|
344
|
+
raise ValueError("Knot multiplicity > order")
|
|
345
|
+
|
|
346
|
+
# Initialize oldCoefs and expanded coefs array with multiplicity new coefficients, as well as some indices.
|
|
347
|
+
oldCoefs = coefs[position - order:position].copy()
|
|
348
|
+
lastKnotIndex = position - oldMultiplicity
|
|
349
|
+
firstCoefIndex = position - degree
|
|
350
|
+
coefs = np.insert(coefs, firstCoefIndex, oldCoefs[:multiplicity], axis=0)
|
|
351
|
+
# Compute inserted coefficients (multiplicity of them) and the degree - oldMultiplicity - 1 number of changed coefficients.
|
|
352
|
+
for j in range(multiplicity):
|
|
353
|
+
# Allocate new coefficients for the current multiplicity.
|
|
354
|
+
size = degree - oldMultiplicity - j
|
|
355
|
+
if size < 1:
|
|
356
|
+
# Full multiplicity knot, so use oldCoefs.
|
|
357
|
+
coefs[firstCoefIndex + j] = oldCoefs[0]
|
|
358
|
+
else:
|
|
359
|
+
# Otherwise, allocate space for newCoefs.
|
|
360
|
+
newCoefs = np.empty((size, *coefs.shape[1:]), coefs.dtype)
|
|
361
|
+
|
|
362
|
+
# Compute the new coefficients.
|
|
363
|
+
for i, k in zip(range(size), range(lastKnotIndex - size, lastKnotIndex)):
|
|
364
|
+
alpha = (knot - knots[k]) / (knots[k + degree - j] - knots[k])
|
|
365
|
+
newCoefs[i] = (1.0 - alpha) * oldCoefs[i] + alpha * oldCoefs[i + 1]
|
|
366
|
+
|
|
367
|
+
# Assign the ends of the new coefficients into their respective positions.
|
|
368
|
+
coefs[firstCoefIndex + j] = newCoefs[0]
|
|
369
|
+
if size > 1:
|
|
370
|
+
coefs[lastKnotIndex + multiplicity - j - 2] = newCoefs[-1]
|
|
371
|
+
oldCoefs = newCoefs
|
|
372
|
+
|
|
373
|
+
# Assign remaining computed coefficients (the ones in the middle).
|
|
374
|
+
if size > 2:
|
|
375
|
+
coefs[firstCoefIndex + multiplicity:firstCoefIndex + multiplicity + size - 2] = newCoefs[1:-1]
|
|
376
|
+
|
|
377
|
+
# Insert the inserted coefficients and inserted knots.
|
|
378
|
+
knotsList[ind] = knots = np.insert(knots, position, (knot,) * multiplicity)
|
|
379
|
+
|
|
380
|
+
coefs = coefs.swapaxes(0, ind + 1) # Swap back
|
|
333
381
|
|
|
334
382
|
if self.coefs is coefs:
|
|
335
383
|
return self
|
|
@@ -452,7 +500,7 @@ def remove_knots(self, tolerance, nLeft = 0, nRight = 0):
|
|
|
452
500
|
foldedIndices = list(filter(lambda x: x != id, indIndex))
|
|
453
501
|
currentFold, foldedBasis = currentSpline.fold(foldedIndices)
|
|
454
502
|
while True:
|
|
455
|
-
bestError = np.finfo(
|
|
503
|
+
bestError = np.finfo(self.coefs.dtype).max
|
|
456
504
|
bestSpline = currentFold
|
|
457
505
|
ix = currentFold.order[0]
|
|
458
506
|
while ix < currentFold.nCoef[0]:
|
|
@@ -518,66 +566,6 @@ def reverse(self, variable = 0):
|
|
|
518
566
|
newFolded = type(self)(folded.nInd, folded.nDep, folded.order, folded.nCoef, (newKnots,), newCoefs, folded.metadata)
|
|
519
567
|
return newFolded.unfold(myIndices, basisInfo)
|
|
520
568
|
|
|
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
569
|
def transpose(self, axes=None):
|
|
582
570
|
if axes is None:
|
|
583
571
|
axes = range(self.nInd)[::-1]
|
|
@@ -614,17 +602,14 @@ def trim(self, newDomain):
|
|
|
614
602
|
if unique[i] - bounds[0] < epsilon:
|
|
615
603
|
bounds[0] = unique[i]
|
|
616
604
|
multiplicity = order - counts[i]
|
|
617
|
-
if i > 0:
|
|
618
|
-
noChange = False
|
|
619
605
|
elif i > 0 and bounds[0] - unique[i - 1] < epsilon:
|
|
620
606
|
bounds[0] = unique[i - 1]
|
|
621
607
|
multiplicity = order - counts[i - 1]
|
|
622
|
-
if i - 1 > 0:
|
|
623
|
-
noChange = False
|
|
624
608
|
else:
|
|
625
609
|
multiplicity = order
|
|
626
|
-
|
|
627
|
-
|
|
610
|
+
if multiplicity > 0:
|
|
611
|
+
newKnots.append((bounds[0], multiplicity))
|
|
612
|
+
noChange = False
|
|
628
613
|
|
|
629
614
|
if not np.isnan(bounds[1]):
|
|
630
615
|
if not(knots[order - 1] <= bounds[1] <= knots[-order]): raise ValueError("Invalid newDomain")
|
|
@@ -634,19 +619,16 @@ def trim(self, newDomain):
|
|
|
634
619
|
if unique[i] - bounds[1] < epsilon:
|
|
635
620
|
bounds[1] = unique[i]
|
|
636
621
|
multiplicity = order - counts[i]
|
|
637
|
-
if i < len(unique) - 1:
|
|
638
|
-
noChange = False
|
|
639
622
|
elif i > 0 and bounds[1] - unique[i - 1] < epsilon:
|
|
640
623
|
bounds[1] = unique[i - 1]
|
|
641
624
|
multiplicity = order - counts[i - i]
|
|
642
|
-
noChange = False # i < len(unique) - 1
|
|
643
625
|
else:
|
|
644
626
|
multiplicity = order
|
|
645
|
-
|
|
627
|
+
if multiplicity > 0:
|
|
628
|
+
newKnots.append((bounds[1], multiplicity))
|
|
629
|
+
noChange = False
|
|
646
630
|
|
|
647
631
|
newKnotsList.append(newKnots)
|
|
648
|
-
if len(newKnots) > 0:
|
|
649
|
-
noChange = False
|
|
650
632
|
|
|
651
633
|
if noChange:
|
|
652
634
|
return self
|
bspy/_spline_evaluation.py
CHANGED
|
@@ -5,7 +5,7 @@ def bspline_values(knot, knots, splineOrder, u, derivativeOrder = 0, taylorCoefs
|
|
|
5
5
|
basis = np.zeros(splineOrder, knots.dtype)
|
|
6
6
|
if knot is None:
|
|
7
7
|
knot = np.searchsorted(knots, u, side = 'right')
|
|
8
|
-
knot = min(knot, len(knots) - splineOrder)
|
|
8
|
+
knot = min(max(knot, splineOrder), len(knots) - splineOrder)
|
|
9
9
|
if derivativeOrder >= splineOrder:
|
|
10
10
|
return knot, basis
|
|
11
11
|
basis[-1] = 1.0
|
|
@@ -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import scipy as sp
|
|
3
3
|
import bspy.spline
|
|
4
|
+
import bspy.spline_block
|
|
4
5
|
import math
|
|
5
6
|
|
|
6
7
|
def circular_arc(radius, angle, tolerance = None):
|
|
@@ -12,14 +13,33 @@ def circular_arc(radius, angle, tolerance = None):
|
|
|
12
13
|
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)])
|
|
13
14
|
|
|
14
15
|
def composition(splines, tolerance):
|
|
16
|
+
# Collect domains and check range bounds
|
|
17
|
+
domains = [None]
|
|
18
|
+
domain = None
|
|
19
|
+
for i, spline in enumerate(splines):
|
|
20
|
+
if domain is not None:
|
|
21
|
+
if len(domain) != spline.nDep:
|
|
22
|
+
raise ValueError(f"Domain dimension of spline {i-1} does not match range dimension of spline {i}")
|
|
23
|
+
rangeBounds = spline.range_bounds()
|
|
24
|
+
for ix in range(spline.nDep):
|
|
25
|
+
if rangeBounds[ix][0] < domain[ix][0] or rangeBounds[ix][1] > domain[ix][1]:
|
|
26
|
+
raise ValueError(f"Range of spline {i} exceeds domain of spline {i-1}")
|
|
27
|
+
domains.append(domain)
|
|
28
|
+
domain = spline.domain()
|
|
29
|
+
|
|
15
30
|
# Define the callback function
|
|
16
31
|
def composition_of_splines(u):
|
|
17
|
-
for
|
|
18
|
-
u =
|
|
32
|
+
for spline, domain in zip(splines[::-1], domains[::-1]):
|
|
33
|
+
u = spline(u)
|
|
34
|
+
if domain is not None:
|
|
35
|
+
# We've already checked that the range of spline is within the domain
|
|
36
|
+
# of its successor, but numerics may cause the spline value to slightly
|
|
37
|
+
# exceed its range, so we clip the spline value accordingly.
|
|
38
|
+
u = np.clip(u, domain[:, 0], domain[:, 1])
|
|
19
39
|
return u
|
|
20
40
|
|
|
21
41
|
# Approximate this composition
|
|
22
|
-
return bspy.Spline.fit(
|
|
42
|
+
return bspy.Spline.fit(domain, composition_of_splines, tolerance = tolerance)
|
|
23
43
|
|
|
24
44
|
def cone(radius1, radius2, height, tolerance = None):
|
|
25
45
|
if tolerance is None:
|
|
@@ -366,9 +386,10 @@ def fit(domain, f, order = None, knots = None, tolerance = 1.0e-4):
|
|
|
366
386
|
indices = nInd * [0]
|
|
367
387
|
iLast = nInd
|
|
368
388
|
while iLast >= 0:
|
|
389
|
+
# Create a tuple for the u value (must be a tuple to use it as a dictionary key)
|
|
369
390
|
uValue = tuple([uvw[i][indices[i]] for i in range(nInd)])
|
|
370
391
|
if not uValue in fDictionary:
|
|
371
|
-
fDictionary[uValue] = f(uValue)
|
|
392
|
+
fDictionary[uValue] = f(np.array(uValue))
|
|
372
393
|
fValues.append(fDictionary[uValue])
|
|
373
394
|
iLast = nInd - 1
|
|
374
395
|
while iLast >= 0:
|
|
@@ -518,7 +539,7 @@ def four_sided_patch(bottom, right, top, left, surfParam = 0.5):
|
|
|
518
539
|
|
|
519
540
|
return (1.0 - surfParam) * coons + surfParam * laplace
|
|
520
541
|
|
|
521
|
-
def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-
|
|
542
|
+
def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-5):
|
|
522
543
|
# Check validity of input
|
|
523
544
|
if self.nInd != 2: raise ValueError("Surface must have two independent variables")
|
|
524
545
|
if len(uvStart) != 2: raise ValueError("uvStart must have two components")
|
|
@@ -616,7 +637,7 @@ def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-6):
|
|
|
616
637
|
initialGuess = line(uvStart, uvEnd).elevate([2])
|
|
617
638
|
|
|
618
639
|
# Solve the ODE and return the geodesic
|
|
619
|
-
solution = initialGuess.solve_ode(1, 1, geodesicCallback,
|
|
640
|
+
solution = initialGuess.solve_ode(1, 1, geodesicCallback, tolerance, (self, uvDomain))
|
|
620
641
|
return solution
|
|
621
642
|
|
|
622
643
|
def least_squares(uValues, dataPoints, order = None, knots = None, compression = 0.0,
|
|
@@ -878,7 +899,7 @@ def section(xytk):
|
|
|
878
899
|
# Join the pieces together and return
|
|
879
900
|
return bspy.Spline.join(mySections)
|
|
880
901
|
|
|
881
|
-
def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
902
|
+
def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = (), includeEstimate = False):
|
|
882
903
|
# Ensure that the ODE is properly formulated
|
|
883
904
|
|
|
884
905
|
if nLeft < 0: raise ValueError("Invalid number of left hand boundary conditions")
|
|
@@ -904,11 +925,11 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
904
925
|
for i, knot in enumerate(uniqueKnots):
|
|
905
926
|
howMany = currentGuess.order[0] - indices[i + 1] + indices[i] - nOrder
|
|
906
927
|
if howMany > 0:
|
|
907
|
-
knotsToAdd
|
|
928
|
+
knotsToAdd.append((knot, howMany))
|
|
908
929
|
if howMany < 0:
|
|
909
|
-
knotsToRemove
|
|
930
|
+
knotsToRemove.append((knot, abs(howMany)))
|
|
910
931
|
currentGuess = currentGuess.insert_knots([knotsToAdd])
|
|
911
|
-
for
|
|
932
|
+
for (knot, howMany) in knotsToRemove:
|
|
912
933
|
ix = np.searchsorted(currentGuess.knots[0], knot, side = 'left')
|
|
913
934
|
for iy in range(howMany):
|
|
914
935
|
currentGuess, residual = currentGuess.remove_knot(ix, nLeft, nRight)
|
|
@@ -970,7 +991,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
970
991
|
residuals = np.append(residuals, np.zeros((nLeft * nDep,)))
|
|
971
992
|
collocationMatrix[bandWidth, 0 : nLeft * nDep] = 1.0
|
|
972
993
|
for iPoint, t in enumerate(collocationPoints[iFirstPoint : iNextPoint]):
|
|
973
|
-
uData = np.array([workingSpline.derivative([i], t) for i in range(nOrder)]).T
|
|
994
|
+
uData = np.array([workingSpline.derivative([i], t) for i in range(nOrder + 1 if includeEstimate else nOrder)]).T
|
|
974
995
|
F, F_u = FAndF_u(t, uData, *args)
|
|
975
996
|
residuals = np.append(residuals, workingSpline.derivative([nOrder], t) - continuation * F)
|
|
976
997
|
ix = None
|
|
@@ -1065,7 +1086,7 @@ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
|
|
|
1065
1086
|
knotsToAdd = []
|
|
1066
1087
|
for knot0, knot1 in zip(currentGuess.knots[0][:-1], currentGuess.knots[0][1:]):
|
|
1067
1088
|
if knot0 < knot1:
|
|
1068
|
-
knotsToAdd
|
|
1089
|
+
knotsToAdd.append((0.5 * (knot0 + knot1), currentGuess.order[0] - nOrder))
|
|
1069
1090
|
previousGuess = currentGuess
|
|
1070
1091
|
currentGuess = currentGuess.insert_knots([knotsToAdd])
|
|
1071
1092
|
|