bspy 4.2__tar.gz → 4.4__tar.gz
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-4.2 → bspy-4.4}/PKG-INFO +14 -11
- {bspy-4.2 → bspy-4.4}/README.md +10 -8
- {bspy-4.2 → bspy-4.4}/bspy/__init__.py +1 -1
- {bspy-4.2 → bspy-4.4}/bspy/_spline_domain.py +74 -92
- {bspy-4.2 → bspy-4.4}/bspy/_spline_evaluation.py +3 -3
- {bspy-4.2 → bspy-4.4}/bspy/_spline_fitting.py +33 -12
- {bspy-4.2 → bspy-4.4}/bspy/_spline_intersection.py +294 -279
- bspy-4.4/bspy/_spline_milling.py +233 -0
- {bspy-4.2 → bspy-4.4}/bspy/_spline_operations.py +98 -81
- {bspy-4.2 → bspy-4.4}/bspy/hyperplane.py +7 -3
- {bspy-4.2 → bspy-4.4}/bspy/manifold.py +8 -3
- {bspy-4.2 → bspy-4.4}/bspy/solid.py +8 -4
- {bspy-4.2 → bspy-4.4}/bspy/spline.py +124 -21
- {bspy-4.2 → bspy-4.4}/bspy/splineOpenGLFrame.py +346 -303
- {bspy-4.2 → bspy-4.4}/bspy/spline_block.py +155 -38
- {bspy-4.2 → bspy-4.4}/bspy/viewer.py +20 -11
- {bspy-4.2 → bspy-4.4}/bspy.egg-info/PKG-INFO +14 -11
- {bspy-4.2 → bspy-4.4}/bspy.egg-info/SOURCES.txt +1 -0
- {bspy-4.2 → bspy-4.4}/setup.cfg +2 -2
- {bspy-4.2 → bspy-4.4}/LICENSE +0 -0
- {bspy-4.2 → bspy-4.4}/bspy.egg-info/dependency_links.txt +0 -0
- {bspy-4.2 → bspy-4.4}/bspy.egg-info/requires.txt +0 -0
- {bspy-4.2 → bspy-4.4}/bspy.egg-info/top_level.txt +0 -0
- {bspy-4.2 → bspy-4.4}/pyproject.toml +0 -0
{bspy-4.2 → bspy-4.4}/PKG-INFO
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bspy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4
|
|
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
|
|
7
7
|
Author-email: ericbrec@msn.com
|
|
8
8
|
License: MIT
|
|
9
9
|
Project-URL: Bug Tracker, http://github.com/ericbrec/BSpy/issues
|
|
10
|
-
Keywords:
|
|
10
|
+
Keywords: bspline,B-spline,nub,solid,solid modeling,geometry,csg,opengl,tkinter
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Environment :: Win32 (MS Windows)
|
|
13
13
|
Classifier: Environment :: Console
|
|
@@ -28,14 +28,13 @@ Requires-Dist: numpy
|
|
|
28
28
|
Requires-Dist: scipy
|
|
29
29
|
Requires-Dist: PyOpenGL
|
|
30
30
|
Requires-Dist: pyopengltk
|
|
31
|
+
Dynamic: license-file
|
|
31
32
|
|
|
32
33
|
# BSpy
|
|
33
34
|
Library for manipulating and rendering B-spline curves, surfaces, and multidimensional manifolds with non-uniform knots in each dimension.
|
|
34
35
|
|
|
35
|
-
The [
|
|
36
|
-
|
|
37
|
-
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), and to geodesics.
|
|
38
|
-
Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
|
|
36
|
+
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), geodesics, offsets, and lines of curvature.
|
|
37
|
+
Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, four-sided patches, and compositions of splines.
|
|
39
38
|
Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
|
|
40
39
|
There are methods to evaluate spline values, derivatives, normals, integrals, continuity, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions.
|
|
41
40
|
In addition, there are methods to manipulate the domain of splines, including trim, join, split, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold.
|
|
@@ -43,14 +42,18 @@ There are methods to manipulate the range of splines, including dot product, cro
|
|
|
43
42
|
Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
|
|
44
43
|
Splines can be saved and loaded in json format.
|
|
45
44
|
|
|
45
|
+
The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to process an array-like collection of splines that represent a system of equations. There are highly-optimized methods to compute the contours and zeros of a spline block, as well as a variety of methods to manipulate and evaluate a spline block and its derivatives.
|
|
46
|
+
|
|
47
|
+
The [BSpyConvert](https://pypi.org/project/BSpyConvert/) package converts BSpy splines and solid models to and from [OpenCascade (OCCT)](https://dev.opencascade.org/) equivalents and a variety of geometry and CAD file formats, including STEP, IGES, and STL.
|
|
48
|
+
|
|
46
49
|
The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has methods to create individual hyperplanes in any dimension, along with axis-aligned hyperplanes and hypercubes.
|
|
47
50
|
|
|
48
|
-
The [
|
|
51
|
+
The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
|
|
49
52
|
|
|
50
|
-
The [
|
|
53
|
+
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids. Solids can be saved and loaded in json format.
|
|
51
54
|
|
|
52
55
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
53
|
-
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and
|
|
56
|
+
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves, surfaces, and solids. Spline surfaces with more
|
|
54
57
|
than 3 dependent variables will have their added dimensions rendered as colors (up to 6 dependent variables are supported). Only tested on Windows systems.
|
|
55
58
|
|
|
56
59
|
The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
@@ -58,7 +61,7 @@ The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
|
58
61
|
[SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
|
|
59
62
|
a tree view full of solids and splines, and a set of controls to adjust and view the selected solids and splines. Only tested on Windows systems.
|
|
60
63
|
|
|
61
|
-
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
|
|
64
|
+
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display solids and splines.
|
|
62
65
|
It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
|
|
63
66
|
in [jupyter](https://jupyter.org/) notebooks and other scripting environments. Only tested on Windows systems.
|
|
64
67
|
|
{bspy-4.2 → bspy-4.4}/README.md
RENAMED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# BSpy
|
|
2
2
|
Library for manipulating and rendering B-spline curves, surfaces, and multidimensional manifolds with non-uniform knots in each dimension.
|
|
3
3
|
|
|
4
|
-
The [
|
|
5
|
-
|
|
6
|
-
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), and to geodesics.
|
|
7
|
-
Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, and four-sided patches.
|
|
4
|
+
The [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html) class has a method to fit multidimensional data for scalar and vector functions of single and multiple variables. It also can fit splines to functions, to solutions for ordinary differential equations (ODEs), geodesics, offsets, and lines of curvature.
|
|
5
|
+
Spline has methods to create points, lines, circular arcs, spheres, cones, cylinders, tori, ruled surfaces, surfaces of revolution, four-sided patches, and compositions of splines.
|
|
8
6
|
Other methods add, subtract, and multiply splines, as well as confine spline curves to a given range.
|
|
9
7
|
There are methods to evaluate spline values, derivatives, normals, integrals, continuity, curvature, and the Jacobian, as well as methods that return spline representations of derivatives, normals, integrals, graphs, and convolutions.
|
|
10
8
|
In addition, there are methods to manipulate the domain of splines, including trim, join, split, reparametrize, transpose, reverse, add and remove knots, elevate and extrapolate, and fold and unfold.
|
|
@@ -12,14 +10,18 @@ There are methods to manipulate the range of splines, including dot product, cro
|
|
|
12
10
|
Finally, there are methods to compute the zeros and contours of a spline and to intersect two splines.
|
|
13
11
|
Splines can be saved and loaded in json format.
|
|
14
12
|
|
|
13
|
+
The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to process an array-like collection of splines that represent a system of equations. There are highly-optimized methods to compute the contours and zeros of a spline block, as well as a variety of methods to manipulate and evaluate a spline block and its derivatives.
|
|
14
|
+
|
|
15
|
+
The [BSpyConvert](https://pypi.org/project/BSpyConvert/) package converts BSpy splines and solid models to and from [OpenCascade (OCCT)](https://dev.opencascade.org/) equivalents and a variety of geometry and CAD file formats, including STEP, IGES, and STL.
|
|
16
|
+
|
|
15
17
|
The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has methods to create individual hyperplanes in any dimension, along with axis-aligned hyperplanes and hypercubes.
|
|
16
18
|
|
|
17
|
-
The [
|
|
19
|
+
The [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) abstract base class for [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) and [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html).
|
|
18
20
|
|
|
19
|
-
The [
|
|
21
|
+
The [Solid](https://ericbrec.github.io/BSpy/bspy/solid.html) class has methods to construct n-dimensional solids from trimmed [Manifold](https://ericbrec.github.io/BSpy/bspy/manifold.html) boundaries. Each solid consists of a list of boundaries and a Boolean value that indicates if the solid contains infinity. Each [Boundary](https://ericbrec.github.io/BSpy/bspy/solid.html) consists of a manifold (currently a [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) or [Spline](https://ericbrec.github.io/BSpy/bspy/spline.html)) and a domain solid that trims the manifold. Solids have methods to form the intersection, union, difference, and complement of solids. There are methods to compute point containment, winding numbers, surface integrals, and volume integrals. There are also methods to translate, transform, and slice solids. Solids can be saved and loaded in json format.
|
|
20
22
|
|
|
21
23
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
22
|
-
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and
|
|
24
|
+
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves, surfaces, and solids. Spline surfaces with more
|
|
23
25
|
than 3 dependent variables will have their added dimensions rendered as colors (up to 6 dependent variables are supported). Only tested on Windows systems.
|
|
24
26
|
|
|
25
27
|
The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
@@ -27,7 +29,7 @@ The [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) class is a
|
|
|
27
29
|
[SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html),
|
|
28
30
|
a tree view full of solids and splines, and a set of controls to adjust and view the selected solids and splines. Only tested on Windows systems.
|
|
29
31
|
|
|
30
|
-
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display splines.
|
|
32
|
+
The [Graphics](https://ericbrec.github.io/BSpy/bspy/viewer.html#Graphics) class is a graphics engine to display solids and splines.
|
|
31
33
|
It launches a [Viewer](https://ericbrec.github.io/BSpy/bspy/viewer.html) and issues commands to the viewer for use
|
|
32
34
|
in [jupyter](https://jupyter.org/) notebooks and other scripting environments. Only tested on Windows systems.
|
|
33
35
|
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -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
|
|