bspy 1.2.1__py3-none-any.whl → 1.2.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.
@@ -67,12 +67,15 @@ def zeros_using_interval_newton(self):
67
67
  adjustedLeftStep = min(leftNewtonStep, rightNewtonStep) - 0.5 * epsilon
68
68
  adjustedRightStep = max(leftNewtonStep, rightNewtonStep) + 0.5 * epsilon
69
69
  if derivativeBounds[0] * derivativeBounds[1] >= 0.0: # Refine interval
70
- projectedLeftStep = max(0.0, adjustedLeftStep)
71
- projectedRightStep = min(1.0, adjustedRightStep)
72
- if projectedLeftStep <= projectedRightStep:
73
- trimmedSpline = mySpline.trim(((projectedLeftStep, projectedRightStep),))
74
- myZeros = refine(trimmedSpline, intervalSize, functionMax)
75
- else:
70
+ projectedLeftStep = max(0.0, adjustedLeftStep)
71
+ projectedRightStep = min(1.0, adjustedRightStep)
72
+ if projectedLeftStep <= projectedRightStep:
73
+ if projectedRightStep - projectedLeftStep <= epsilon:
74
+ myZeros = [0.5 * (projectedLeftStep + projectedRightStep)]
75
+ else:
76
+ trimmedSpline = mySpline.trim(((projectedLeftStep, projectedRightStep),))
77
+ myZeros = refine(trimmedSpline, intervalSize, functionMax)
78
+ else:
76
79
  return []
77
80
  else: # . . . or split as needed
78
81
  myZeros = []
@@ -206,7 +209,16 @@ def _refine_projected_polyhedron(interval):
206
209
  machineEpsilon = np.finfo(interval.spline.coefs.dtype).eps
207
210
  root = None
208
211
  intervals = []
209
- scale = np.abs(interval.spline.range_bounds()).max()
212
+ scale = 0.0
213
+ # Go through each nDep of the spline, checking bounds.
214
+ for coefs in interval.spline.coefs:
215
+ coefsMin = coefs.min()
216
+ coefsMax = coefs.max()
217
+ if coefsMax < -evaluationEpsilon or coefsMin > evaluationEpsilon:
218
+ # No roots in this interval.
219
+ return root, intervals
220
+ scale = max(scale, abs(coefsMin), abs(coefsMax))
221
+
210
222
  if scale < epsilon:
211
223
  # Return the bounds of the interval within which the spline is zero.
212
224
  root = (interval.intercept, interval.slope + interval.intercept)
@@ -349,9 +361,24 @@ def contours(self):
349
361
  Point = namedtuple('Point', ('d', 'det', 'onBoundary', 'uvw'))
350
362
  epsilon = np.sqrt(np.finfo(self.coefs.dtype).eps)
351
363
  evaluationEpsilon = np.sqrt(epsilon)
364
+
365
+ # Go through each nDep of the spline, checking bounds.
366
+ for coefs in self.coefs:
367
+ coefsMin = coefs.min()
368
+ coefsMax = coefs.max()
369
+ if coefsMax < -evaluationEpsilon or coefsMin > evaluationEpsilon:
370
+ # No contours for this spline.
371
+ return []
372
+
373
+ # Record self's original domain and then reparametrize self's domain to [0, 1]^nInd.
374
+ domain = self.domain().T
375
+ self = self.reparametrize(((0.0, 1.0),) * self.nInd)
376
+
377
+ # Construct self's tangents and normal.
352
378
  tangents = []
353
379
  for nInd in range(self.nInd):
354
380
  tangents.append(self.differentiate(nInd))
381
+ normal = self.normal_spline((0, 1)) # We only need the first two indices
355
382
 
356
383
  theta = np.sqrt(2) # Arbitrary starting value for theta (picked one in [0, pi/2] unlikely to be a stationary point)
357
384
  # Try different theta values until no border or turning points are degenerate.
@@ -363,7 +390,6 @@ def contours(self):
363
390
  abort = False
364
391
 
365
392
  # Construct the turning point determinant.
366
- normal = self.normal_spline((0, 1)) # We only need the first two indices
367
393
  turningPointDeterminant = normal.dot((cosTheta, sinTheta))
368
394
 
369
395
  # Find intersections with u and v boundaries.
@@ -478,6 +504,20 @@ def contours(self):
478
504
  # (3) Take all the points found in Step (1) and Step (2) and order them by distance in the theta direction from the origin.
479
505
  points.sort()
480
506
 
507
+ # Extra step not in the paper: Add a panel between two consecutive turning points to uniquely determine contours between them.
508
+ if len(points) > 1:
509
+ i = 0
510
+ previousPoint = points[i]
511
+ while i < len(points) - 1:
512
+ i += 1
513
+ point = points[i]
514
+ if not previousPoint.onBoundary and not point.onBoundary and point.d - previousPoint.d > epsilon:
515
+ # We have two consecutive turning points on separate panels.
516
+ # Insert a panel in between them, with the uvw value of None, since there is no zero associated.
517
+ points.insert(i, Point(0.5 * (previousPoint.d + point.d), 0.0, False, None))
518
+ i += 1
519
+ previousPoint = point
520
+
481
521
  # (4) Initialize an ordered list of contours. No contours will be on the list at first.
482
522
  currentContourPoints = [] # Holds contours currently being identified
483
523
  contourPoints = [] # Hold contours already identified
@@ -523,29 +563,34 @@ def contours(self):
523
563
  # the list. Go back to Step (5).
524
564
  # First, construct panel, whose zeros lie along the panel boundary, u * cosTheta + v * sinTheta - d = 0.
525
565
  panel.coefs[self.nDep] -= point.d
526
- # Split panel below and above the known zero point.
527
- # This avoids extra computation and the high-zero at the known zero point.
528
- panelPoints = [point.uvw]
529
- # To split the panel, we need to determine the offset from the point.
530
- # Since the objective function (self) is zero and its derivative is zero at the point,
531
- # we use second derivatives to determine when the objective function will likely grow
532
- # evaluationEpsilon above zero again.
533
- wrt = [0] * self.nInd
534
- wrt[0] = 2
535
- selfUU = self.derivative(wrt, point.uvw)
536
- wrt[0] = 1
537
- wrt[1] = 1
538
- selfUV = self.derivative(wrt, point.uvw)
539
- wrt[0] = 0
540
- wrt[1] = 2
541
- selfVV = self.derivative(wrt, point.uvw)
542
- offset = np.sqrt(2.0 * evaluationEpsilon / \
543
- np.linalg.norm(selfUU * sinTheta * sinTheta - 2.0 * selfUV * sinTheta * cosTheta + selfVV * cosTheta * cosTheta))
544
- # Now, we can find the zeros of the split panel, checking to ensure each panel is within bounds first.
545
- if point.uvw[0] + sinTheta * offset < 1.0 - epsilon and epsilon < point.uvw[1] - cosTheta * offset:
546
- panelPoints += panel.trim(((point.uvw[0] + sinTheta * offset, 1.0), (0.0, point.uvw[1] - cosTheta * offset)) + ((None, None),) * (self.nInd - 2)).zeros()
547
- if epsilon < point.uvw[0] - sinTheta * offset and point.uvw[1] + cosTheta * offset < 1.0 - epsilon:
548
- panelPoints += panel.trim(((0.0, point.uvw[0] - sinTheta * offset), (point.uvw[1] + cosTheta * offset, 1.0)) + ((None, None),) * (self.nInd - 2)).zeros()
566
+
567
+ if point.uvw is None:
568
+ # For an inserted panel between two consecutive turning points, just find zeros along the panel.
569
+ panelPoints = panel.zeros()
570
+ else:
571
+ # Split panel below and above the known zero point.
572
+ # This avoids extra computation and the high-zero at the known zero point.
573
+ panelPoints = [point.uvw]
574
+ # To split the panel, we need to determine the offset from the point.
575
+ # Since the objective function (self) is zero and its derivative is zero at the point,
576
+ # we use second derivatives to determine when the objective function will likely grow
577
+ # evaluationEpsilon above zero again.
578
+ wrt = [0] * self.nInd
579
+ wrt[0] = 2
580
+ selfUU = self.derivative(wrt, point.uvw)
581
+ wrt[0] = 1
582
+ wrt[1] = 1
583
+ selfUV = self.derivative(wrt, point.uvw)
584
+ wrt[0] = 0
585
+ wrt[1] = 2
586
+ selfVV = self.derivative(wrt, point.uvw)
587
+ offset = np.sqrt(2.0 * evaluationEpsilon / \
588
+ np.linalg.norm(selfUU * sinTheta * sinTheta - 2.0 * selfUV * sinTheta * cosTheta + selfVV * cosTheta * cosTheta))
589
+ # Now, we can find the zeros of the split panel, checking to ensure each panel is within bounds first.
590
+ if point.uvw[0] + sinTheta * offset < 1.0 - epsilon and epsilon < point.uvw[1] - cosTheta * offset:
591
+ panelPoints += panel.trim(((point.uvw[0] + sinTheta * offset, 1.0), (0.0, point.uvw[1] - cosTheta * offset)) + ((None, None),) * (self.nInd - 2)).zeros()
592
+ if epsilon < point.uvw[0] - sinTheta * offset and point.uvw[1] + cosTheta * offset < 1.0 - epsilon:
593
+ panelPoints += panel.trim(((0.0, point.uvw[0] - sinTheta * offset), (point.uvw[1] + cosTheta * offset, 1.0)) + ((None, None),) * (self.nInd - 2)).zeros()
549
594
  # Sort zero points by their position along the panel boundary (using vector orthogonal to its normal).
550
595
  panelPoints.sort(key=lambda uvw: uvw[1] * cosTheta - uvw[0] * sinTheta)
551
596
  # Add d back to prepare for next turning point.
@@ -553,7 +598,7 @@ def contours(self):
553
598
  # Go through panel points, adding them to existing contours, creating new ones, or closing old ones.
554
599
  adjustment = 0 # Adjust index after a contour point is added or removed.
555
600
  for i, uvw in zip(range(len(panelPoints)), panelPoints):
556
- if np.allclose(point.uvw, uvw):
601
+ if point.uvw is not None and np.allclose(point.uvw, uvw):
557
602
  if point.det > 0.0:
558
603
  # Insert the turning point twice (second one appears before the first one in the points list).
559
604
  currentContourPoints.insert(i, [True, point.uvw]) # True indicates end point
@@ -576,6 +621,9 @@ def contours(self):
576
621
  # Now we just need to create splines for those contours using the Spline.contour method.
577
622
  splineContours = []
578
623
  for points in contourPoints:
579
- splineContours.append(bspy.spline.Spline.contour(self, points[1:])) # Skip endPoint boolean at start of points list
624
+ contour = bspy.spline.Spline.contour(self, points[1:]) # Skip endPoint boolean at start of points list
625
+ # Transform the contour to self's original domain.
626
+ contour.coefs = (contour.coefs.T * (domain[1] - domain[0]) + domain[0]).T
627
+ splineContours.append(contour)
580
628
 
581
629
  return splineContours
@@ -573,10 +573,10 @@ def normal_spline(self, indices=None):
573
573
  ord += order - 1 if ccm[i, j] and index != j else 0
574
574
  newOrd = max(newOrd, ord)
575
575
  newOrder.append(newOrd)
576
- uniqueKnots, counts = np.unique(knots, return_counts=True)
576
+ uniqueKnots, counts = np.unique(knots[order - 1:self.nCoef[i] + 1], return_counts=True)
577
577
  counts += newOrd - order + 1 # Because we're multiplying all the tangents, the knot elevation is one more
578
- counts[0] -= 1 # But not at the endpoints, which get reduced by one when taking the derivative
579
- counts[-1] -= 1 # But not at the endpoints, which get reduced by one when taking the derivative
578
+ counts[0] = newOrd # But not at the endpoints, which are full order as usual
579
+ counts[-1] = newOrd # But not at the endpoints, which are full order as usual
580
580
  newKnots.append(np.repeat(uniqueKnots, counts))
581
581
  # Also calculate the total number of coefficients, capturing how it progressively increases, and
582
582
  # using that calculation to span uvw from the starting knot to the end for each variable.
bspy/spline.py CHANGED
@@ -457,6 +457,22 @@ class Spline:
457
457
  indMap = [(*(mapping if _isIterable(mapping) else (mapping, mapping)), True) for mapping in indMap]
458
458
  return bspy._spline_operations.multiplyAndConvolve(self, other, indMap, productType)
459
459
 
460
+ def copy(self, metadata={}):
461
+ """
462
+ Create a copy of a spline.
463
+
464
+ Parameters
465
+ ----------
466
+ metadata : `dict`, optional
467
+ A dictionary of ancillary data to store with the spline. Default is {}.
468
+
469
+ Returns
470
+ -------
471
+ spline : `Spline`
472
+ The spline copy.
473
+ """
474
+ return type(self)(self.nInd, self.nDep, self.order, self.nCoef, self.knots, self.coefs, self.accuracy, metadata)
475
+
460
476
  def cross(self, vector):
461
477
  """
462
478
  Cross product a spline with `nDep` of 2 or 3 by the given vector.
@@ -898,6 +914,30 @@ class Spline:
898
914
 
899
915
  @staticmethod
900
916
  def load(fileName, splineType=None):
917
+ """
918
+ Load a spline from the specified filename (full path).
919
+
920
+ Parameters
921
+ ----------
922
+ fileName : `string`
923
+ The full path to the file containing the spline. Can be a relative path.
924
+
925
+ splineType : `type`, optional
926
+ The class type that should be created. It must be an instance of Spline (the default).
927
+
928
+ Returns
929
+ -------
930
+ spline : `Spline`
931
+ The loaded spline.
932
+
933
+ See Also
934
+ --------
935
+ `save` : Save a spline to the specified filename (full path).
936
+
937
+ Notes
938
+ -----
939
+ Uses numpy's load function.
940
+ """
901
941
  kw = np.load(fileName)
902
942
  order = kw["order"]
903
943
  nInd = len(order)
@@ -1094,6 +1134,22 @@ class Spline:
1094
1134
  return bspy._spline_domain.reparametrize(self, newDomain)
1095
1135
 
1096
1136
  def save(self, fileName):
1137
+ """
1138
+ Save a spline to the specified filename (full path).
1139
+
1140
+ Parameters
1141
+ ----------
1142
+ fileName : `string`
1143
+ The full path to the file containing the spline. Can be a relative path.
1144
+
1145
+ See Also
1146
+ --------
1147
+ `load` : Load a spline from the specified filename (full path).
1148
+
1149
+ Notes
1150
+ -----
1151
+ Uses numpy's savez function. Accuracy and metadata are not saved.
1152
+ """
1097
1153
  kw = {}
1098
1154
  kw["order"] = order=np.array(self.order, np.int32)
1099
1155
  for i in range(len(self.knots)):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bspy
3
- Version: 1.2.1
3
+ Version: 1.2.3
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
@@ -2,14 +2,14 @@ bspy/__init__.py,sha256=XRBelbYtbhB6AWKh8DTsWVMDIteVxUm7Cf_S1fkqeEs,851
2
2
  bspy/_spline_domain.py,sha256=29_Mp0bn9_8z4ltu__DG9_NZuCiSuol0tRgAeIuzXZI,27826
3
3
  bspy/_spline_evaluation.py,sha256=i0qPy6UNe1MeoCOxF5LjTtloP6HUnxtxPHseAUAlmJU,6376
4
4
  bspy/_spline_fitting.py,sha256=NKNUfHfHYkQLlK_4i5oF7MIS59wrCqVKkPXHDcDGwIc,20552
5
- bspy/_spline_intersection.py,sha256=-KrbgKY4Nqnz7PAQg53kjwKEjHIALrMJI5F8QgbgW8g,30801
6
- bspy/_spline_operations.py,sha256=mkp1FeFNRDRyWln6-9Z0GBHZUB9FnXYwjpJWYHccs5U,35722
5
+ bspy/_spline_intersection.py,sha256=olDQpLohlF6DhNshMY0kriitlypXHNVXPcMcY3NUnGs,33110
6
+ bspy/_spline_operations.py,sha256=8VIVLGgYln7lLplgVBpnMVHMWbjigvir-LHE8tusZ-A,35715
7
7
  bspy/bspyApp.py,sha256=UxmNp_IGaG7gMVexUmA7YfuwX5iuMBr7ibW3DRan8R8,14524
8
8
  bspy/drawableSpline.py,sha256=Ha7wh__aRTpGTr3cEDG49SeAyRrKtfMUQYkEZ3w9OJw,17004
9
- bspy/spline.py,sha256=Rs-YcOF4MU_Y46AboT2OH24XNIMXRMG21GCFtu0Sco0,57454
9
+ bspy/spline.py,sha256=WIPhSQu09laxQdtjpp4XDO8RscYRin4hF8ERVHLeE1o,59087
10
10
  bspy/splineOpenGLFrame.py,sha256=77rikAbAood76cf5w1Idj7wciTwPV-Bn-yGSuRFvlJM,60616
11
- bspy-1.2.1.dist-info/LICENSE,sha256=nLfJULN68Jw6GfCJp4xeMksGuRdyWNdgEsZGjw2twig,1091
12
- bspy-1.2.1.dist-info/METADATA,sha256=kYsWQUqTFTBZKz6Tq6Re9Fu0QvXaOKqr-vUl4OLrxDw,3777
13
- bspy-1.2.1.dist-info/WHEEL,sha256=AtBG6SXL3KF_v0NxLf0ehyVOh0cold-JbJYXNGorC6Q,92
14
- bspy-1.2.1.dist-info/top_level.txt,sha256=fotZnJn6aCwgUbBEV3hslIko7Nw-eqtHLq2eyJLlFsY,5
15
- bspy-1.2.1.dist-info/RECORD,,
11
+ bspy-1.2.3.dist-info/LICENSE,sha256=nLfJULN68Jw6GfCJp4xeMksGuRdyWNdgEsZGjw2twig,1091
12
+ bspy-1.2.3.dist-info/METADATA,sha256=uEuJAnmqnP85Zs8qInKp4JzRYPo_GTp7OJ08bisn9No,3777
13
+ bspy-1.2.3.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
14
+ bspy-1.2.3.dist-info/top_level.txt,sha256=fotZnJn6aCwgUbBEV3hslIko7Nw-eqtHLq2eyJLlFsY,5
15
+ bspy-1.2.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.0)
2
+ Generator: bdist_wheel (0.41.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes