bspy 4.2__py3-none-any.whl → 4.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.
- bspy/__init__.py +1 -1
- bspy/_spline_domain.py +72 -91
- bspy/_spline_evaluation.py +2 -2
- bspy/_spline_fitting.py +4 -4
- bspy/_spline_intersection.py +285 -273
- bspy/_spline_operations.py +45 -41
- bspy/hyperplane.py +7 -3
- bspy/manifold.py +8 -3
- bspy/solid.py +7 -3
- bspy/spline.py +17 -5
- bspy/splineOpenGLFrame.py +346 -303
- bspy/spline_block.py +155 -38
- bspy/viewer.py +20 -11
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/METADATA +5 -3
- bspy-4.3.dist-info/RECORD +18 -0
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/WHEEL +1 -1
- bspy-4.2.dist-info/RECORD +0 -18
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/LICENSE +0 -0
- {bspy-4.2.dist-info → bspy-4.3.dist-info}/top_level.txt +0 -0
bspy/_spline_operations.py
CHANGED
|
@@ -13,47 +13,55 @@ def _shiftPolynomial(polynomial, delta):
|
|
|
13
13
|
|
|
14
14
|
def add(self, other, indMap = None):
|
|
15
15
|
if not(self.nDep == other.nDep): raise ValueError("self and other must have same nDep")
|
|
16
|
-
selfMapped =
|
|
17
|
-
otherMapped = []
|
|
16
|
+
selfMapped = set()
|
|
18
17
|
otherToSelf = {}
|
|
19
18
|
if indMap is not None:
|
|
20
19
|
(self, other) = bspy.Spline.common_basis((self, other), indMap)
|
|
21
20
|
for map in indMap:
|
|
22
|
-
selfMapped.
|
|
23
|
-
otherMapped.append(map[1])
|
|
21
|
+
selfMapped.add(map[0])
|
|
24
22
|
otherToSelf[map[1]] = map[0]
|
|
25
23
|
|
|
26
24
|
# Construct new spline parameters.
|
|
27
|
-
# We index backwards because we're adding transposed coefficients (see below).
|
|
28
25
|
nInd = self.nInd
|
|
29
26
|
order = [*self.order]
|
|
30
27
|
nCoef = [*self.nCoef]
|
|
31
28
|
knots = list(self.knots)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if i not in otherMapped:
|
|
38
|
-
order.append(other.order[other.nInd - 1 - i])
|
|
39
|
-
nCoef.append(other.nCoef[other.nInd - 1 - i])
|
|
40
|
-
knots.append(other.knots[other.nInd - 1 - i])
|
|
41
|
-
permutation.append(self.nInd + i + 1) # Add 1 to account for dependent variables.
|
|
29
|
+
for i in range(other.nInd):
|
|
30
|
+
if i not in otherToSelf:
|
|
31
|
+
order.append(other.order[i])
|
|
32
|
+
nCoef.append(other.nCoef[i])
|
|
33
|
+
knots.append(other.knots[i])
|
|
42
34
|
nInd += 1
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
permutation.append(0) # Account for dependent variables.
|
|
46
|
-
permutation = np.array(permutation)
|
|
35
|
+
|
|
36
|
+
# Build coefs array.
|
|
47
37
|
coefs = np.zeros((self.nDep, *nCoef), self.coefs.dtype)
|
|
48
38
|
|
|
49
|
-
#
|
|
50
|
-
# First, add in self.coefs.
|
|
39
|
+
# Add in self.coefs (you need to transpose coefs for the addition to work properly).
|
|
51
40
|
coefs = coefs.T
|
|
52
41
|
coefs += self.coefs.T
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
42
|
+
coefs = coefs.T
|
|
43
|
+
|
|
44
|
+
# Construct permutation of coefs to transpose coefs to match other.coefs.
|
|
45
|
+
otherUnmappedCount = 0
|
|
46
|
+
permutation = [0] # Account for dependent variables
|
|
47
|
+
for i in range(other.nInd):
|
|
48
|
+
if i in otherToSelf:
|
|
49
|
+
permutation.append(otherToSelf[i] + 1) # Add 1 to account for dependent variables.
|
|
50
|
+
else:
|
|
51
|
+
permutation.append(self.nInd + otherUnmappedCount + 1) # Add 1 to account for dependent variables.
|
|
52
|
+
otherUnmappedCount += 1
|
|
53
|
+
for i in range(self.nInd):
|
|
54
|
+
if i not in selfMapped:
|
|
55
|
+
permutation.append(i + 1) # Add 1 to account for dependent variables.
|
|
56
|
+
|
|
57
|
+
# Permute coefs to match other.coefs
|
|
58
|
+
coefs = coefs.transpose(permutation)
|
|
59
|
+
|
|
60
|
+
# Add in other.coefs (you need to transpose coefs for the addition to work properly).
|
|
61
|
+
coefs = coefs.T
|
|
56
62
|
coefs += other.coefs.T
|
|
63
|
+
coefs = coefs.T
|
|
64
|
+
|
|
57
65
|
# Reverse the permutation.
|
|
58
66
|
coefs = coefs.transpose(np.argsort(permutation))
|
|
59
67
|
|
|
@@ -116,9 +124,9 @@ def confine(self, range_bounds):
|
|
|
116
124
|
if unique[ix] == knot:
|
|
117
125
|
count = (order - 1) - counts[ix]
|
|
118
126
|
if count > 0:
|
|
119
|
-
spline = spline.insert_knots((
|
|
127
|
+
spline = spline.insert_knots(((knot, count),))
|
|
120
128
|
else:
|
|
121
|
-
spline = spline.insert_knots((
|
|
129
|
+
spline = spline.insert_knots(((knot, order - 1),))
|
|
122
130
|
|
|
123
131
|
# Go through the boundary points, assigning boundary coefficients, interpolating between boundary points,
|
|
124
132
|
# and removing knots and coefficients where the curve stalls.
|
|
@@ -643,21 +651,20 @@ def normal_spline(self, indices=None):
|
|
|
643
651
|
knots = None
|
|
644
652
|
counts = None
|
|
645
653
|
maxOrder = 0
|
|
646
|
-
|
|
647
|
-
endInd = 0
|
|
654
|
+
maxMap = []
|
|
648
655
|
# First, collect the order, knots, and number of relevant columns for this independent variable.
|
|
649
656
|
for row in self.block:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
ind = nInd - rowInd
|
|
657
|
+
for map, spline in row:
|
|
658
|
+
if nInd in map:
|
|
659
|
+
ind = map.index(nInd)
|
|
654
660
|
order = spline.order[ind]
|
|
655
661
|
k, c = np.unique(spline.knots[ind][order-1:spline.nCoef[ind]+1], return_counts=True)
|
|
656
662
|
if knots:
|
|
657
663
|
if maxOrder < order:
|
|
658
664
|
counts += order - maxOrder
|
|
659
665
|
maxOrder = order
|
|
660
|
-
|
|
666
|
+
if len(maxMap) < len(map):
|
|
667
|
+
maxMap = map
|
|
661
668
|
for knot, count in zip(k[1:-1], c[1:-1]):
|
|
662
669
|
ix = np.searchsorted(knots, knot)
|
|
663
670
|
if knots[ix] == knot:
|
|
@@ -669,30 +676,27 @@ def normal_spline(self, indices=None):
|
|
|
669
676
|
knots = k
|
|
670
677
|
counts = c
|
|
671
678
|
maxOrder = order
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
679
|
+
maxMap = map
|
|
680
|
+
|
|
675
681
|
break
|
|
676
682
|
|
|
677
|
-
rowInd += spline.nInd
|
|
678
|
-
|
|
679
683
|
# Next, calculate the order of the normal for this independent variable.
|
|
680
684
|
# Note that the total order will be one less than usual, because one of
|
|
681
685
|
# the tangents is the derivative with respect to that independent variable.
|
|
682
686
|
if self.nInd < self.nDep:
|
|
683
687
|
# If this normal involves all tangents, simply add the degree of each,
|
|
684
688
|
# so long as that tangent contains the independent variable.
|
|
685
|
-
order = (maxOrder - 1) * (
|
|
689
|
+
order = (maxOrder - 1) * len(maxMap)
|
|
686
690
|
else:
|
|
687
691
|
# If this normal doesn't involve all tangents, find the max order of
|
|
688
692
|
# each returned combination (as defined by the indices).
|
|
689
693
|
order = 0
|
|
690
|
-
for index in
|
|
694
|
+
for index in maxMap if indices is None else indices:
|
|
691
695
|
# The order will be one larger if this independent variable's tangent is excluded by the index.
|
|
692
696
|
ord = 0 if index != nInd else 1
|
|
693
697
|
# Add the degree of each tangent, so long as that tangent contains the
|
|
694
698
|
# independent variable and is not excluded by the index.
|
|
695
|
-
for ind in
|
|
699
|
+
for ind in maxMap:
|
|
696
700
|
ord += maxOrder - 1 if index != ind else 0
|
|
697
701
|
order = max(order, ord)
|
|
698
702
|
|
bspy/hyperplane.py
CHANGED
|
@@ -18,6 +18,9 @@ class Hyperplane(Manifold):
|
|
|
18
18
|
|
|
19
19
|
tangentSpace : array-like
|
|
20
20
|
A array of tangents that are linearly independent and orthogonal to the normal.
|
|
21
|
+
|
|
22
|
+
metadata : `dict`, optional
|
|
23
|
+
A dictionary of ancillary data to store with the hyperplane. Default is {}.
|
|
21
24
|
|
|
22
25
|
Notes
|
|
23
26
|
-----
|
|
@@ -28,11 +31,12 @@ class Hyperplane(Manifold):
|
|
|
28
31
|
maxAlignment = 0.9999 # 1 - 1/10^4
|
|
29
32
|
""" If the absolute value of the dot product of two unit normals is greater than maxAlignment, the manifolds are parallel."""
|
|
30
33
|
|
|
31
|
-
def __init__(self, normal, point, tangentSpace):
|
|
34
|
+
def __init__(self, normal, point, tangentSpace, metadata = {}):
|
|
32
35
|
self._normal = np.atleast_1d(normal)
|
|
33
36
|
self._point = np.atleast_1d(point)
|
|
34
37
|
self._tangentSpace = np.atleast_1d(tangentSpace)
|
|
35
38
|
if not np.allclose(self._tangentSpace.T @ self._normal, 0.0): raise ValueError("normal must be orthogonal to tangent space")
|
|
39
|
+
self.metadata = dict(metadata)
|
|
36
40
|
|
|
37
41
|
def __repr__(self):
|
|
38
42
|
return "Hyperplane({0}, {1}, {2})".format(self._normal, self._point, self._tangentSpace)
|
|
@@ -207,7 +211,7 @@ class Hyperplane(Manifold):
|
|
|
207
211
|
--------
|
|
208
212
|
`to_dict` : Return a `dict` with `Hyperplane` data.
|
|
209
213
|
"""
|
|
210
|
-
return Hyperplane(dictionary["normal"], dictionary["point"], dictionary["tangentSpace"])
|
|
214
|
+
return Hyperplane(dictionary["normal"], dictionary["point"], dictionary["tangentSpace"], dictionary.get("metadata", {}))
|
|
211
215
|
|
|
212
216
|
def full_domain(self):
|
|
213
217
|
"""
|
|
@@ -455,7 +459,7 @@ class Hyperplane(Manifold):
|
|
|
455
459
|
--------
|
|
456
460
|
`from_dict` : Create a `Hyperplane` from a data in a `dict`.
|
|
457
461
|
"""
|
|
458
|
-
return {"type" : "Hyperplane", "normal" : self._normal, "point" : self._point, "tangentSpace" : self._tangentSpace}
|
|
462
|
+
return {"type" : "Hyperplane", "normal" : self._normal, "point" : self._point, "tangentSpace" : self._tangentSpace, "metadata" : self.metadata}
|
|
459
463
|
|
|
460
464
|
def transform(self, matrix, matrixInverseTranspose = None):
|
|
461
465
|
"""
|
bspy/manifold.py
CHANGED
|
@@ -5,6 +5,11 @@ class Manifold:
|
|
|
5
5
|
"""
|
|
6
6
|
A manifold is an abstract base class for differentiable functions with
|
|
7
7
|
normals and tangent spaces whose range is one dimension higher than their domain.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
metadata : `dict`, optional
|
|
12
|
+
A dictionary of ancillary data to store with the manifold. Default is {}.
|
|
8
13
|
"""
|
|
9
14
|
|
|
10
15
|
minSeparation = 0.0001
|
|
@@ -17,8 +22,8 @@ class Manifold:
|
|
|
17
22
|
factory = {}
|
|
18
23
|
"""Factory dictionary for creating manifolds."""
|
|
19
24
|
|
|
20
|
-
def __init__(self):
|
|
21
|
-
|
|
25
|
+
def __init__(self, metadata = {}):
|
|
26
|
+
self.metadata = dict(metadata)
|
|
22
27
|
|
|
23
28
|
def cached_intersect(self, other, cache = None):
|
|
24
29
|
"""
|
|
@@ -322,7 +327,7 @@ class Manifold:
|
|
|
322
327
|
--------
|
|
323
328
|
`from_dict` : Create a `Manifold` from a data in a `dict`.
|
|
324
329
|
"""
|
|
325
|
-
return
|
|
330
|
+
return {"metadata" : self.metadata}
|
|
326
331
|
|
|
327
332
|
def transform(self, matrix, matrixInverseTranspose = None):
|
|
328
333
|
"""
|
bspy/solid.py
CHANGED
|
@@ -59,6 +59,9 @@ class Solid:
|
|
|
59
59
|
|
|
60
60
|
containsInfinity : `bool`
|
|
61
61
|
Indicates whether or not the solid contains infinity.
|
|
62
|
+
|
|
63
|
+
metadata : `dict`, optional
|
|
64
|
+
A dictionary of ancillary data to store with the solid. Default is {}.
|
|
62
65
|
|
|
63
66
|
See also
|
|
64
67
|
--------
|
|
@@ -70,10 +73,11 @@ class Solid:
|
|
|
70
73
|
|
|
71
74
|
Solids can be of zero dimension, typically acting as the domain of boundary endpoints. Zero-dimension solids have no boundaries, they only contain infinity or not.
|
|
72
75
|
"""
|
|
73
|
-
def __init__(self, dimension, containsInfinity):
|
|
76
|
+
def __init__(self, dimension, containsInfinity, metadata = {}):
|
|
74
77
|
assert dimension >= 0
|
|
75
78
|
self.dimension = dimension
|
|
76
79
|
self.containsInfinity = containsInfinity
|
|
80
|
+
self.metadata = dict(metadata)
|
|
77
81
|
self.boundaries = []
|
|
78
82
|
self.bounds = None
|
|
79
83
|
|
|
@@ -356,7 +360,7 @@ class Solid:
|
|
|
356
360
|
`save` : Save a solids and/or manifolds in json format to the specified filename (full path).
|
|
357
361
|
"""
|
|
358
362
|
def from_dict(dictionary):
|
|
359
|
-
solid = Solid(dictionary["dimension"], dictionary["containsInfinity"])
|
|
363
|
+
solid = Solid(dictionary["dimension"], dictionary["containsInfinity"], dictionary.get("metadata", {}))
|
|
360
364
|
for boundary in dictionary["boundaries"]:
|
|
361
365
|
manifold = boundary["manifold"]
|
|
362
366
|
solid.add_boundary(Boundary(Manifold.factory[manifold.get("type", "Spline")].from_dict(manifold), from_dict(boundary["domain"])))
|
|
@@ -428,7 +432,7 @@ class Solid:
|
|
|
428
432
|
if isinstance(obj, Boundary):
|
|
429
433
|
return {"type" : "Boundary", "manifold" : obj.manifold, "domain" : obj.domain}
|
|
430
434
|
if isinstance(obj, Solid):
|
|
431
|
-
return {"type" : "Solid", "dimension" : obj.dimension, "containsInfinity" : obj.containsInfinity, "boundaries" : obj.boundaries}
|
|
435
|
+
return {"type" : "Solid", "dimension" : obj.dimension, "containsInfinity" : obj.containsInfinity, "boundaries" : obj.boundaries, "metadata" : obj.metadata}
|
|
432
436
|
return super().default(obj)
|
|
433
437
|
|
|
434
438
|
with open(fileName, 'w', encoding='utf-8') as file:
|
bspy/spline.py
CHANGED
|
@@ -79,7 +79,7 @@ class Spline(Manifold):
|
|
|
79
79
|
|
|
80
80
|
def __repr__(self):
|
|
81
81
|
return f"Spline({self.nInd}, {self.nDep}, {self.order}, " + \
|
|
82
|
-
f"{self.nCoef}, {self.knots} {self.coefs}, {self.metadata})"
|
|
82
|
+
f"{self.nCoef}, {self.knots}, {self.coefs}, {self.metadata})"
|
|
83
83
|
|
|
84
84
|
def __add__(self, other):
|
|
85
85
|
if isinstance(other, Spline):
|
|
@@ -969,7 +969,10 @@ class Spline(Manifold):
|
|
|
969
969
|
|
|
970
970
|
f : Python function
|
|
971
971
|
The function to approximate. It is a vector-valued function of nDep
|
|
972
|
-
components in nInd variables.
|
|
972
|
+
components in nInd variables. Alternatively, it can return a spline of any
|
|
973
|
+
number independent variables and nDep dependent variables. In this case, the
|
|
974
|
+
resulting spline function will have nInd + number of independent variables
|
|
975
|
+
in the splines returned independent variables and nDep dependent variables.
|
|
973
976
|
|
|
974
977
|
order : `array-like`
|
|
975
978
|
An optional integer array of length nInd which specifies the polynomial
|
|
@@ -1109,7 +1112,7 @@ class Spline(Manifold):
|
|
|
1109
1112
|
`to_dict` : Return a `dict` with `Spline` data.
|
|
1110
1113
|
"""
|
|
1111
1114
|
return Spline(dictionary["nInd"], dictionary["nDep"], dictionary["order"], dictionary["nCoef"],
|
|
1112
|
-
[np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary
|
|
1115
|
+
[np.array(knots) for knots in dictionary["knots"]], np.array(dictionary["coefs"]), dictionary.get("metadata", {}))
|
|
1113
1116
|
|
|
1114
1117
|
def full_domain(self):
|
|
1115
1118
|
"""
|
|
@@ -1211,7 +1214,12 @@ class Spline(Manifold):
|
|
|
1211
1214
|
----------
|
|
1212
1215
|
newKnots : `iterable` of length `nInd`
|
|
1213
1216
|
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.
|
|
1217
|
+
len(newKnots[ind]) == 0 if no knots are to be added for the `ind` independent variable.
|
|
1218
|
+
|
|
1219
|
+
Each knot may be specified as its knot value or a tuple indicating the knot value and its multiplicity.
|
|
1220
|
+
For example, spline.insert_knots([[0.1, (0.3, 2)], [(.5, 3), .2, .5]]) will insert 0.1 once and 0.3 twice into
|
|
1221
|
+
the knots of the first independent variable, and will insert 0.2 once and 0.5 four times into the knots of the
|
|
1222
|
+
second independent variable. Knots do not need to be sorted.
|
|
1215
1223
|
|
|
1216
1224
|
Returns
|
|
1217
1225
|
-------
|
|
@@ -2065,7 +2073,11 @@ class Spline(Manifold):
|
|
|
2065
2073
|
--------
|
|
2066
2074
|
`join` : Join a list of splines together into a single spline.
|
|
2067
2075
|
"""
|
|
2068
|
-
|
|
2076
|
+
splineArray = bspy.spline_block.SplineBlock(self).split(minContinuity, breaks)
|
|
2077
|
+
splines = splineArray.ravel()
|
|
2078
|
+
for i, block in enumerate(splines):
|
|
2079
|
+
splines[i] = block.block[0][0][1]
|
|
2080
|
+
return splines.reshape(splineArray.shape)
|
|
2069
2081
|
|
|
2070
2082
|
def subtract(self, other, indMap = None):
|
|
2071
2083
|
"""
|