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/spline.py CHANGED
@@ -5,8 +5,9 @@ from bspy.manifold import Manifold
5
5
  import bspy.spline_block
6
6
  import bspy._spline_domain
7
7
  import bspy._spline_evaluation
8
- import bspy._spline_intersection
9
8
  import bspy._spline_fitting
9
+ import bspy._spline_intersection
10
+ import bspy._spline_milling
10
11
  import bspy._spline_operations
11
12
 
12
13
  @Manifold.register
@@ -59,8 +60,8 @@ class Spline(Manifold):
59
60
  self.knots = tuple(np.array(kk) for kk in knots)
60
61
  for knots, order, nCoef in zip(self.knots, self.order, self.nCoef):
61
62
  for i in range(nCoef):
62
- if not(knots[i] <= knots[i + 1] and knots[i] < knots[i + order]):
63
- raise ValueError("Improperly ordered knot sequence")
63
+ if not(knots[i] <= knots[i + 1] and knots[i + order] - knots[i] > 0):
64
+ raise ValueError("Improper knot order or multiplicity")
64
65
  totalCoefs = 1
65
66
  for nCoef in self.nCoef:
66
67
  totalCoefs *= nCoef
@@ -79,7 +80,7 @@ class Spline(Manifold):
79
80
 
80
81
  def __repr__(self):
81
82
  return f"Spline({self.nInd}, {self.nDep}, {self.order}, " + \
82
- f"{self.nCoef}, {self.knots} {self.coefs}, {self.metadata})"
83
+ f"{self.nCoef}, {self.knots}, {self.coefs}, {self.metadata})"
83
84
 
84
85
  def __add__(self, other):
85
86
  if isinstance(other, Spline):
@@ -969,17 +970,20 @@ class Spline(Manifold):
969
970
 
970
971
  f : Python function
971
972
  The function to approximate. It is a vector-valued function of nDep
972
- components in nInd variables.
973
+ components in nInd variables. Alternatively, it can return a spline of any
974
+ number independent variables and nDep dependent variables. In this case, the
975
+ resulting spline function will have nInd + number of independent variables
976
+ in the splines returned independent variables and nDep dependent variables.
973
977
 
974
- order : `array-like`
978
+ order : `array-like`, optional
975
979
  An optional integer array of length nInd which specifies the polynomial
976
980
  order to use in each of the independent variables. It will default to order
977
981
  4 (degree 3) if None is specified (the default)
978
982
 
979
- knots : `array-like`
983
+ knots : `array-like`, optional
980
984
  The initial knot sequence to use, if given
981
985
 
982
- tolerance : `scalar`
986
+ tolerance : `scalar`, optional
983
987
  The maximum 2-norm of the difference between the given function and the
984
988
  spline fit. Defaults to 1.0e-4.
985
989
 
@@ -1109,7 +1113,7 @@ class Spline(Manifold):
1109
1113
  `to_dict` : Return a `dict` with `Spline` data.
1110
1114
  """
1111
1115
  return Spline(dictionary["nInd"], dictionary["nDep"], dictionary["order"], dictionary["nCoef"],
1112
- [np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary["metadata"])
1116
+ [np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary.get("metadata", {}))
1113
1117
 
1114
1118
  def full_domain(self):
1115
1119
  """
@@ -1126,7 +1130,7 @@ class Spline(Manifold):
1126
1130
  """
1127
1131
  return bspy._spline_intersection.full_domain(self)
1128
1132
 
1129
- def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-6):
1133
+ def geodesic(self, uvStart, uvEnd, tolerance = 1.0e-5):
1130
1134
  """
1131
1135
  Determine a geodesic between two points on a surface
1132
1136
 
@@ -1138,9 +1142,9 @@ class Spline(Manifold):
1138
1142
  uvEnd : `array-like`
1139
1143
  The parameter values for the surface at the other end of the desired geodesic.
1140
1144
 
1141
- tolerance : scalar
1145
+ tolerance : scalar, optional
1142
1146
  The maximum error in parameter space to which the geodesic should get computed.
1143
- Defaults to 1.0e-6.
1147
+ Defaults to 1.0e-5.
1144
1148
 
1145
1149
  Returns
1146
1150
  -------
@@ -1211,7 +1215,12 @@ class Spline(Manifold):
1211
1215
  ----------
1212
1216
  newKnots : `iterable` of length `nInd`
1213
1217
  An iterable that specifies the knots to be added to each independent variable's knots.
1214
- len(newKnots[ind]) == 0 if no knots are to be added for the `ind` independent variable.
1218
+ len(newKnots[ind]) == 0 if no knots are to be added for the `ind` independent variable.
1219
+
1220
+ Each knot may be specified as its knot value or a tuple indicating the knot value and its multiplicity.
1221
+ For example, spline.insert_knots([[0.1, (0.3, 2)], [(.5, 3), .2, .5]]) will insert 0.1 once and 0.3 twice into
1222
+ the knots of the first independent variable, and will insert 0.2 once and 0.5 four times into the knots of the
1223
+ second independent variable. Knots do not need to be sorted.
1215
1224
 
1216
1225
  Returns
1217
1226
  -------
@@ -1491,6 +1500,36 @@ class Spline(Manifold):
1491
1500
  """
1492
1501
  return bspy._spline_fitting.line(startPoint, endPoint)
1493
1502
 
1503
+ def line_of_curvature(self, uvStart, is_max = True, tolerance = 1.0e-3):
1504
+ """
1505
+ Determine a line of curvature along a surface
1506
+
1507
+ Parameters
1508
+ ----------
1509
+ uvStart : `array-like`
1510
+ The parameter values for the surface at one end of the desired line of curvature.
1511
+
1512
+ is_max : `bool`, optional
1513
+ Boolean value indicating that the line of curvature should be the maximal curvature line.
1514
+ If False, the minimal curvature line is returned. Defaults to True.
1515
+
1516
+ tolerance : scalar, optional
1517
+ The maximum error in parameter space to which the geodesic should get computed.
1518
+ Defaults to 1.0e-3.
1519
+
1520
+ Returns
1521
+ -------
1522
+ spline : `Spline`
1523
+ A spline curve whose range is in the domain of the given surface. The range of the
1524
+ curve is the locus of points whose image under the surface map form the line of curvature
1525
+ starting at the given point.
1526
+
1527
+ See Also
1528
+ --------
1529
+ `solve_ode` : Solve an ordinary differential equation using spline collocation.
1530
+ """
1531
+ return bspy._spline_milling.line_of_curvature(self, uvStart, is_max, tolerance)
1532
+
1494
1533
  @staticmethod
1495
1534
  def load(fileName):
1496
1535
  """
@@ -1645,6 +1684,61 @@ class Spline(Manifold):
1645
1684
  the matrix formed by the tangents of the spline. If the null space is greater than one dimension, the normal will be zero.
1646
1685
  """
1647
1686
  return bspy._spline_operations.normal_spline(bspy.spline_block.SplineBlock(self), indices)
1687
+
1688
+ def offset(self, edgeRadius, bitRadius=None, angle=np.pi / 2.2, path=None, subtract=False, removeCusps=False, tolerance = 1.0e-4):
1689
+ """
1690
+ Compute the offset of a spline to a given tolerance.
1691
+
1692
+ Parameters
1693
+ ----------
1694
+ edgeRadius : scalar
1695
+ The radius of offset. If a bit radius is specified, the edge radius is the
1696
+ smaller radius of the cutting edge of the drill bit, whereas bit radius specifies
1697
+ half of the full width of the drill bit.
1698
+
1699
+ bitRadius : scalar, optional
1700
+ The radius of the drill bit (half its full width). For a ball nose cutter (the default),
1701
+ the bit radius is the same as the edge radius. For an end mill,
1702
+ the bit radius is larger (typically much larger) than the edge radius.
1703
+
1704
+ angle : scalar, optional
1705
+ The angle at which the drill bit transitions from the edge radius to the
1706
+ flatter bottom of the drill bit. The angle must be in the range [0, pi/2).
1707
+ Defaults to pi / 2.2.
1708
+
1709
+ path : `Spline`, optional
1710
+ The path along self that the drill bit should contact.
1711
+ If specified, the path must be a 2D curve in the domain of self, self must be a 3D surface,
1712
+ and the offset returned is a 3D curve providing the 3D position of the drill bit,
1713
+ rather than the full offset surface. Defaults to None.
1714
+
1715
+ subtract : boolean, optional
1716
+ Flag indicating if the drill bit should be subtracted from the spline instead of added.
1717
+ Subtracting the drill bit returns the tool path that cuts out the spline. Defaults to False.
1718
+
1719
+ removeCusps : boolean, optional
1720
+ Flag indicating if cusps and their associated self-intersections should be removed from the
1721
+ offset. Only applicable to offset curves and paths along offset surfaces. Defaults to False.
1722
+
1723
+ tolerance : `scalar`, optional
1724
+ The maximum 2-norm of the difference between the offset and the
1725
+ spline fit. Defaults to 1.0e-4.
1726
+
1727
+ Returns
1728
+ -------
1729
+ offset : `Spline`
1730
+ The spline that represents the offset.
1731
+
1732
+ See Also
1733
+ --------
1734
+ `fit` : Fit the function f with a spline to a given tolerance.
1735
+
1736
+ Notes
1737
+ -----
1738
+ The offset is only defined for 2D curves and 3D surfaces with well-defined normals.
1739
+ The bottom of the drill bit is tangent to its lowest y value.
1740
+ """
1741
+ return bspy._spline_milling.offset(self, edgeRadius, bitRadius, angle, path, subtract, removeCusps, tolerance)
1648
1742
 
1649
1743
  @staticmethod
1650
1744
  def point(point):
@@ -1963,7 +2057,7 @@ class Spline(Manifold):
1963
2057
  """
1964
2058
  return bspy._spline_fitting.section(xytk)
1965
2059
 
1966
- def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = ()):
2060
+ def solve_ode(self, nLeft, nRight, FAndF_u, tolerance = 1.0e-6, args = (), includeEstimate = False):
1967
2061
  """
1968
2062
  Numerically solve an ordinary differential equation with boundary conditions.
1969
2063
 
@@ -1986,26 +2080,31 @@ class Spline(Manifold):
1986
2080
  FAndF_u : Python function
1987
2081
  FAndF_u must have exactly this calling sequence: FAndF_u(t, uData, *args). t is a scalar set
1988
2082
  to the desired value of the independent variable of the ODE. uData will be a numpy matrix of shape
1989
- (self.nDep, nOrder) whose columns are (u, ... , u^(nOrder - 1). It must return a numpy
2083
+ (self.nDep, nOrder) whose columns are u, ... , u^(nOrder - 1). It must return a numpy
1990
2084
  vector of length self.nDep and a numpy array whose shape is (self.nDep, self.nDep, nOrder).
1991
2085
  The first output vector is the value of the forcing function F at (t, uData). The numpy
1992
2086
  array is the array of partial derivatives with respect to all the numbers in uData. Thus, if
1993
2087
  this array is called jacobian, then jacobian[:, i, j] is the gradient of the forcing function with
1994
2088
  respect to uData[i, j].
1995
2089
 
1996
- tolerance : scalar
1997
- The relative error to which the ODE should get solved.
2090
+ tolerance : scalar, optional
2091
+ The relative error to which the ODE should get solved. Default is 1.0e-6.
1998
2092
 
1999
- args : tuple
2093
+ args : tuple, optional
2000
2094
  Additional arguments to pass to the user-defined function FAndF_u. For example, if FAndF_u has the
2001
- FAndF_u(t, uData, a, b, c), then args must be a tuple of length 3.
2095
+ FAndF_u(t, uData, a, b, c), then args must be a tuple of length 3. Default is ().
2096
+
2097
+ includeEstimate : bool, optional
2098
+ If `includeEstimate` is True, the uData passed to `FAndF_u` will be a numpy matrix of shape
2099
+ (self.nDep, nOrder + 1) whose columns are u, ... , u^(nOrder). The last column will be the most
2100
+ recent estimate of u^(nOrder)(t). Default is False.
2002
2101
 
2003
2102
  Notes
2004
2103
  =====
2005
2104
  This method uses B-splines as finite elements. The ODE itself is discretized using
2006
2105
  collocation.
2007
2106
  """
2008
- return bspy._spline_fitting.solve_ode(self, nLeft, nRight, FAndF_u, tolerance, args)
2107
+ return bspy._spline_fitting.solve_ode(self, nLeft, nRight, FAndF_u, tolerance, args, includeEstimate)
2009
2108
 
2010
2109
  @staticmethod
2011
2110
  def sphere(radius, tolerance = None):
@@ -2065,7 +2164,11 @@ class Spline(Manifold):
2065
2164
  --------
2066
2165
  `join` : Join a list of splines together into a single spline.
2067
2166
  """
2068
- return bspy._spline_domain.split(self, minContinuity, breaks)
2167
+ splineArray = bspy.spline_block.SplineBlock(self).split(minContinuity, breaks)
2168
+ splines = splineArray.ravel()
2169
+ for i, block in enumerate(splines):
2170
+ splines[i] = block.block[0][0][1]
2171
+ return splines.reshape(splineArray.shape)
2069
2172
 
2070
2173
  def subtract(self, other, indMap = None):
2071
2174
  """