bspy 4.2__tar.gz → 4.3__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.3}/PKG-INFO +5 -3
- {bspy-4.2 → bspy-4.3}/README.md +3 -1
- {bspy-4.2 → bspy-4.3}/bspy/__init__.py +1 -1
- {bspy-4.2 → bspy-4.3}/bspy/_spline_domain.py +72 -91
- {bspy-4.2 → bspy-4.3}/bspy/_spline_evaluation.py +2 -2
- {bspy-4.2 → bspy-4.3}/bspy/_spline_fitting.py +4 -4
- {bspy-4.2 → bspy-4.3}/bspy/_spline_intersection.py +285 -273
- {bspy-4.2 → bspy-4.3}/bspy/_spline_operations.py +45 -41
- {bspy-4.2 → bspy-4.3}/bspy/hyperplane.py +7 -3
- {bspy-4.2 → bspy-4.3}/bspy/manifold.py +8 -3
- {bspy-4.2 → bspy-4.3}/bspy/solid.py +7 -3
- {bspy-4.2 → bspy-4.3}/bspy/spline.py +17 -5
- {bspy-4.2 → bspy-4.3}/bspy/splineOpenGLFrame.py +346 -303
- {bspy-4.2 → bspy-4.3}/bspy/spline_block.py +155 -38
- {bspy-4.2 → bspy-4.3}/bspy/viewer.py +20 -11
- {bspy-4.2 → bspy-4.3}/bspy.egg-info/PKG-INFO +5 -3
- {bspy-4.2 → bspy-4.3}/setup.cfg +2 -2
- {bspy-4.2 → bspy-4.3}/LICENSE +0 -0
- {bspy-4.2 → bspy-4.3}/bspy.egg-info/SOURCES.txt +0 -0
- {bspy-4.2 → bspy-4.3}/bspy.egg-info/dependency_links.txt +0 -0
- {bspy-4.2 → bspy-4.3}/bspy.egg-info/requires.txt +0 -0
- {bspy-4.2 → bspy-4.3}/bspy.egg-info/top_level.txt +0 -0
- {bspy-4.2 → bspy-4.3}/pyproject.toml +0 -0
{bspy-4.2 → bspy-4.3}/PKG-INFO
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bspy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.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
|
|
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
|
|
@@ -47,7 +47,9 @@ The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has
|
|
|
47
47
|
|
|
48
48
|
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.
|
|
49
49
|
|
|
50
|
-
The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
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.
|
|
51
53
|
|
|
52
54
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
53
55
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
|
{bspy-4.2 → bspy-4.3}/README.md
RENAMED
|
@@ -16,7 +16,9 @@ The [Hyperplane](https://ericbrec.github.io/BSpy/bspy/hyperplane.html) class has
|
|
|
16
16
|
|
|
17
17
|
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.
|
|
18
18
|
|
|
19
|
-
The [SplineBlock](https://ericbrec.github.io/BSpy/bspy/spline_block.html) class has methods to
|
|
19
|
+
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.
|
|
20
|
+
|
|
21
|
+
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.
|
|
20
22
|
|
|
21
23
|
The [SplineOpenGLFrame](https://ericbrec.github.io/BSpy/bspy/splineOpenGLFrame.html) class is an
|
|
22
24
|
[OpenGLFrame](https://pypi.org/project/pyopengltk/) with custom shaders to render spline curves and surfaces. Spline surfaces with more
|
|
@@ -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,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
|
|
@@ -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:
|
|
@@ -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
|
|