bspy 4.0__py3-none-any.whl → 4.1__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 +13 -3
- bspy/_spline_domain.py +9 -0
- bspy/_spline_evaluation.py +16 -10
- bspy/_spline_fitting.py +56 -82
- bspy/_spline_intersection.py +187 -23
- bspy/_spline_operations.py +8 -2
- bspy/hyperplane.py +540 -0
- bspy/manifold.py +332 -29
- bspy/solid.py +839 -0
- bspy/spline.py +176 -27
- bspy/splineOpenGLFrame.py +262 -14
- bspy/viewer.py +130 -87
- {bspy-4.0.dist-info → bspy-4.1.dist-info}/METADATA +8 -3
- bspy-4.1.dist-info/RECORD +17 -0
- bspy-4.0.dist-info/RECORD +0 -15
- {bspy-4.0.dist-info → bspy-4.1.dist-info}/LICENSE +0 -0
- {bspy-4.0.dist-info → bspy-4.1.dist-info}/WHEEL +0 -0
- {bspy-4.0.dist-info → bspy-4.1.dist-info}/top_level.txt +0 -0
bspy/manifold.py
CHANGED
|
@@ -14,9 +14,194 @@ class Manifold:
|
|
|
14
14
|
Coincidence = namedtuple('Coincidence', ('left', 'right', 'alignment', 'transform', 'inverse', 'translation'))
|
|
15
15
|
"""Return type for intersect."""
|
|
16
16
|
|
|
17
|
+
factory = {}
|
|
18
|
+
"""Factory dictionary for creating manifolds."""
|
|
19
|
+
|
|
17
20
|
def __init__(self):
|
|
18
21
|
pass
|
|
19
22
|
|
|
23
|
+
def cached_intersect(self, other, cache = None):
|
|
24
|
+
"""
|
|
25
|
+
Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
other : `Manifold`
|
|
30
|
+
The `Manifold` intersecting the manifold.
|
|
31
|
+
|
|
32
|
+
cache : `dict`, optional
|
|
33
|
+
A dictionary to cache `Manifold` intersections, speeding computation. The default is `None`.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
intersections : `list` (or `NotImplemented` if other is an unknown type of Manifold)
|
|
38
|
+
A list of intersections between the two manifolds.
|
|
39
|
+
Each intersection records either a crossing or a coincident region.
|
|
40
|
+
|
|
41
|
+
For a crossing, intersection is a Manifold.Crossing: (left, right)
|
|
42
|
+
* left : `Manifold` in the manifold's domain where the manifold and the other cross.
|
|
43
|
+
* right : `Manifold` in the other's domain where the manifold and the other cross.
|
|
44
|
+
* Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
|
|
45
|
+
|
|
46
|
+
For a coincident region, intersection is Manifold.Coincidence: (left, right, alignment, transform, inverse, translation)
|
|
47
|
+
* left : `Solid` in the manifold's domain within which the manifold and the other are coincident.
|
|
48
|
+
* right : `Solid` in the other's domain within which the manifold and the other are coincident.
|
|
49
|
+
* alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
|
|
50
|
+
* transform : `numpy.array` holding the matrix transform from the boundary's domain to the other's domain.
|
|
51
|
+
* inverse : `numpy.array` holding the matrix inverse transform from the other's domain to the boundary's domain.
|
|
52
|
+
* translation : `numpy.array` holding the 1D translation from the manifold's domain to the other's domain.
|
|
53
|
+
* Together transform, inverse, and translation form the mapping from the manifold's domain to the other's domain and vice-versa.
|
|
54
|
+
|
|
55
|
+
isTwin : `bool`
|
|
56
|
+
True if this intersection is the twin from the cache (the intersection with self and other swapped).
|
|
57
|
+
|
|
58
|
+
See Also
|
|
59
|
+
--------
|
|
60
|
+
`intersect` : Intersect two manifolds.
|
|
61
|
+
`Solid.slice` : slice the solid by a manifold.
|
|
62
|
+
|
|
63
|
+
Notes
|
|
64
|
+
-----
|
|
65
|
+
To invert the mapping to go from the other's domain to the manifold's domain, you first subtract the translation and then multiply by the inverse of the transform.
|
|
66
|
+
"""
|
|
67
|
+
intersections = None
|
|
68
|
+
isTwin = False
|
|
69
|
+
# Check cache for previously computed manifold intersections.
|
|
70
|
+
if cache is not None:
|
|
71
|
+
# First, check for the twin (opposite order of arguments).
|
|
72
|
+
intersections = cache.get((other, self))
|
|
73
|
+
if intersections is not None:
|
|
74
|
+
isTwin = True
|
|
75
|
+
else:
|
|
76
|
+
# Next, check for the original order (not twin).
|
|
77
|
+
intersections = cache.get((self, other))
|
|
78
|
+
|
|
79
|
+
# If intersections not previously computed, compute them now.
|
|
80
|
+
if intersections is None:
|
|
81
|
+
intersections = self.intersect(other)
|
|
82
|
+
if intersections is NotImplemented:
|
|
83
|
+
# Try the other way around in case other knows how to intersect self.
|
|
84
|
+
intersections = other.intersect(self)
|
|
85
|
+
isTwin = True
|
|
86
|
+
# Store intersections in cache.
|
|
87
|
+
if cache is not None:
|
|
88
|
+
if isTwin:
|
|
89
|
+
cache[(other, self)] = intersections
|
|
90
|
+
else:
|
|
91
|
+
cache[(self, other)] = intersections
|
|
92
|
+
|
|
93
|
+
return intersections, isTwin
|
|
94
|
+
|
|
95
|
+
def complete_slice(self, slice, solid):
|
|
96
|
+
"""
|
|
97
|
+
Add any missing inherent (implicit) boundaries of this manifold's domain to the given slice of the
|
|
98
|
+
given solid that are needed to make the slice valid and complete.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
slice : `Solid`
|
|
103
|
+
The slice of the given solid formed by the manifold. The slice may be incomplete, missing some of the
|
|
104
|
+
manifold's inherent domain boundaries. Its dimension must match `self.domain_dimension()`.
|
|
105
|
+
|
|
106
|
+
solid : `Solid`
|
|
107
|
+
The solid being sliced by the manifold. Its dimension must match `self.range_dimension()`.
|
|
108
|
+
|
|
109
|
+
See Also
|
|
110
|
+
--------
|
|
111
|
+
`Solid.slice` : Slice the solid by a manifold.
|
|
112
|
+
|
|
113
|
+
Notes
|
|
114
|
+
-----
|
|
115
|
+
For manifolds without inherent domain boundaries (like hyperplanes), the operation does nothing.
|
|
116
|
+
"""
|
|
117
|
+
assert self.domain_dimension() == slice.dimension
|
|
118
|
+
assert self.range_dimension() == solid.dimension
|
|
119
|
+
|
|
120
|
+
def copy(self):
|
|
121
|
+
"""
|
|
122
|
+
Copy the manifold.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
manifold : `Manifold`
|
|
127
|
+
"""
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def domain_dimension(self):
|
|
131
|
+
"""
|
|
132
|
+
Return the domain dimension.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
dimension : `int`
|
|
137
|
+
"""
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def evaluate(self, domainPoint):
|
|
141
|
+
"""
|
|
142
|
+
Return the value of the manifold (a point on the manifold).
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
domainPoint : `numpy.array`
|
|
147
|
+
The 1D array at which to evaluate the point.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
point : `numpy.array`
|
|
152
|
+
"""
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
def flip_normal(self):
|
|
156
|
+
"""
|
|
157
|
+
Flip the direction of the normal.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
manifold : `Manifold`
|
|
162
|
+
The manifold with flipped normal. The manifold retains the same tangent space.
|
|
163
|
+
|
|
164
|
+
See Also
|
|
165
|
+
--------
|
|
166
|
+
`Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
|
|
167
|
+
"""
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def from_dict(dictionary):
|
|
172
|
+
"""
|
|
173
|
+
Create a `Manifold` from a data in a `dict`.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
dictionary : `dict`
|
|
178
|
+
The `dict` containing `Manifold` data.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
manifold : `Manifold`
|
|
183
|
+
|
|
184
|
+
See Also
|
|
185
|
+
--------
|
|
186
|
+
`to_dict` : Return a `dict` with `Manifold` data.
|
|
187
|
+
"""
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def full_domain(self):
|
|
191
|
+
"""
|
|
192
|
+
Return a solid that represents the full domain of the manifold.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
domain : `Solid`
|
|
197
|
+
The full (untrimmed) domain of the manifold.
|
|
198
|
+
|
|
199
|
+
See Also
|
|
200
|
+
--------
|
|
201
|
+
`Boundary` : A portion of the boundary of a solid.
|
|
202
|
+
"""
|
|
203
|
+
return None
|
|
204
|
+
|
|
20
205
|
def intersect(self, other):
|
|
21
206
|
"""
|
|
22
207
|
Intersect two manifolds (self and other).
|
|
@@ -48,8 +233,8 @@ class Manifold:
|
|
|
48
233
|
|
|
49
234
|
See Also
|
|
50
235
|
--------
|
|
51
|
-
`
|
|
52
|
-
`
|
|
236
|
+
`cached_intersect` : Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
|
|
237
|
+
`Solid.slice` : slice the solid by a manifold.
|
|
53
238
|
|
|
54
239
|
Notes
|
|
55
240
|
-----
|
|
@@ -57,32 +242,150 @@ class Manifold:
|
|
|
57
242
|
"""
|
|
58
243
|
return NotImplemented
|
|
59
244
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
245
|
+
def normal(self, domainPoint, normalize=True, indices=None):
|
|
246
|
+
"""
|
|
247
|
+
Return the normal.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
domainPoint : `numpy.array`
|
|
252
|
+
The 1D array at which to evaluate the normal.
|
|
253
|
+
|
|
254
|
+
normalize : `boolean`, optional
|
|
255
|
+
If True the returned normal will have unit length (the default). Otherwise, the normal's length will
|
|
256
|
+
be the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
|
|
257
|
+
|
|
258
|
+
indices : `iterable`, optional
|
|
259
|
+
An iterable of normal indices to calculate. For example, `indices=(0, 3)` will return a vector of length 2
|
|
260
|
+
with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
normal : `numpy.array`
|
|
265
|
+
"""
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
def range_bounds(self):
|
|
269
|
+
"""
|
|
270
|
+
Return the range bounds for the manifold.
|
|
271
|
+
|
|
272
|
+
Returns
|
|
273
|
+
-------
|
|
274
|
+
rangeBounds : `np.array` or `None`
|
|
275
|
+
The range of the manifold given as lower and upper bounds on each dependent variable.
|
|
276
|
+
If the manifold has an unbounded range, `None` is returned.
|
|
277
|
+
"""
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
def range_dimension(self):
|
|
281
|
+
"""
|
|
282
|
+
Return the range dimension.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
dimension : `int`
|
|
287
|
+
"""
|
|
288
|
+
return 0
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def register(manifold):
|
|
292
|
+
"""
|
|
293
|
+
Class decorator for subclasses of `Manifold` that registers the subclass with the `Manifold` factory.
|
|
294
|
+
"""
|
|
295
|
+
Manifold.factory[manifold.__name__] = manifold
|
|
296
|
+
return manifold
|
|
297
|
+
|
|
298
|
+
def tangent_space(self, domainPoint):
|
|
299
|
+
"""
|
|
300
|
+
Return the tangent space.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
domainPoint : `numpy.array`
|
|
305
|
+
The 1D array at which to evaluate the tangent space.
|
|
80
306
|
|
|
81
|
-
|
|
82
|
-
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
tangentSpace : `numpy.array`
|
|
310
|
+
"""
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
def to_dict(self):
|
|
314
|
+
"""
|
|
315
|
+
Return a `dict` with `Manifold` data.
|
|
83
316
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
dictionary : `dict`
|
|
320
|
+
|
|
321
|
+
See Also
|
|
322
|
+
--------
|
|
323
|
+
`from_dict` : Create a `Manifold` from a data in a `dict`.
|
|
324
|
+
"""
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
def transform(self, matrix, matrixInverseTranspose = None):
|
|
328
|
+
"""
|
|
329
|
+
Transform the range of the manifold.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
matrix : `numpy.array`
|
|
334
|
+
A square matrix transformation.
|
|
335
|
+
|
|
336
|
+
matrixInverseTranspose : `numpy.array`, optional
|
|
337
|
+
The inverse transpose of matrix (computed if not provided).
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
manifold : `Manifold`
|
|
342
|
+
The transformed manifold.
|
|
343
|
+
|
|
344
|
+
See Also
|
|
345
|
+
--------
|
|
346
|
+
`Solid.transform` : transform the range of the solid.
|
|
347
|
+
"""
|
|
348
|
+
assert np.shape(matrix) == (self.range_dimension(), self.range_dimension())
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
def translate(self, delta):
|
|
352
|
+
"""
|
|
353
|
+
Translate the range of the manifold.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
delta : `numpy.array`
|
|
358
|
+
A 1D array translation.
|
|
359
|
+
|
|
360
|
+
Returns
|
|
361
|
+
-------
|
|
362
|
+
manifold : `Manifold`
|
|
363
|
+
The translated manifold.
|
|
364
|
+
|
|
365
|
+
See Also
|
|
366
|
+
--------
|
|
367
|
+
`Solid.translate` : translate the range of the solid.
|
|
368
|
+
"""
|
|
369
|
+
assert len(delta) == self.range_dimension()
|
|
370
|
+
return None
|
|
371
|
+
|
|
372
|
+
def trimmed_range_bounds(self, domainBounds):
|
|
373
|
+
"""
|
|
374
|
+
Return the trimmed range bounds for the manifold.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
domainBounds : array-like
|
|
379
|
+
An array with shape (domain_dimension, 2) of lower and upper and lower bounds on each manifold parameter.
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
trimmedManifold, rangeBounds : `Manifold`, `np.array`
|
|
384
|
+
A manifold trimmed to the given domain bounds, and the range of the trimmed manifold given as
|
|
385
|
+
lower and upper bounds on each dependent variable.
|
|
386
|
+
|
|
387
|
+
Notes
|
|
388
|
+
-----
|
|
389
|
+
The returned trimmed manifold may be the original manifold, depending on the subclass of manifold.
|
|
390
|
+
"""
|
|
391
|
+
return None, None
|