bsplyne 1.0.0__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.
- bsplyne/__init__.py +55 -0
- bsplyne/b_spline.py +2464 -0
- bsplyne/b_spline_basis.py +1000 -0
- bsplyne/geometries_in_3D.py +1193 -0
- bsplyne/multi_patch_b_spline.py +1731 -0
- bsplyne/my_wide_product.py +209 -0
- bsplyne/parallel_utils.py +378 -0
- bsplyne/save_utils.py +141 -0
- bsplyne-1.0.0.dist-info/METADATA +91 -0
- bsplyne-1.0.0.dist-info/RECORD +13 -0
- bsplyne-1.0.0.dist-info/WHEEL +5 -0
- bsplyne-1.0.0.dist-info/licenses/LICENSE.txt +70 -0
- bsplyne-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1193 @@
|
|
|
1
|
+
# %% Imports
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from .b_spline import BSpline
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# %% 3D transformations
|
|
8
|
+
def _rotation_matrix(axis, angle):
|
|
9
|
+
P = np.expand_dims(axis, axis=1) @ np.expand_dims(axis, axis=0)
|
|
10
|
+
I = np.eye(3)
|
|
11
|
+
Q = np.cross(I, axis)
|
|
12
|
+
R = P + np.cos(angle) * (I - P) + np.sin(angle) * Q
|
|
13
|
+
return R
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _scale_matrix(scale_vector):
|
|
17
|
+
S = np.diag(scale_vector)
|
|
18
|
+
return S
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def scale_rotate_translate(pts, scale_vector, axis, angle, translation_vector):
|
|
22
|
+
"""
|
|
23
|
+
Applies a scale, rotation and translation to a set of points.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
pts : array_like
|
|
28
|
+
The points to be transformed.
|
|
29
|
+
scale_vector : array_like
|
|
30
|
+
The vector to scale by.
|
|
31
|
+
axis : array_like
|
|
32
|
+
The axis to rotate around.
|
|
33
|
+
angle : float
|
|
34
|
+
The angle to rotate by in radians.
|
|
35
|
+
translation_vector : array_like
|
|
36
|
+
The vector to translate by.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
new_pts : array_like
|
|
41
|
+
The transformed points.
|
|
42
|
+
"""
|
|
43
|
+
S = _scale_matrix(scale_vector)
|
|
44
|
+
R = _rotation_matrix(axis, angle)
|
|
45
|
+
new_pts = (np.tensordot(R @ S, pts, 1).T + translation_vector).T
|
|
46
|
+
return new_pts
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# %% inner points computation
|
|
50
|
+
def _IDW(known_points, unknown_points, data, exposant=2):
|
|
51
|
+
dim = known_points.shape[0]
|
|
52
|
+
distances = np.empty((dim, unknown_points.shape[1], known_points.shape[1]))
|
|
53
|
+
for d in range(dim):
|
|
54
|
+
distances[d] = np.expand_dims(unknown_points[d], axis=-1) - known_points[d]
|
|
55
|
+
distances = np.linalg.norm(distances, axis=0)
|
|
56
|
+
ID = distances ** (-exposant)
|
|
57
|
+
ID = (ID.T / ID.sum(1)).T
|
|
58
|
+
data_mean = np.mean(data, axis=-1)
|
|
59
|
+
return (np.tensordot(ID, data.T - data_mean, 1) + data_mean).T
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _find_inner_ctrlPts(degrees, knotVects, ctrlPts, exposant=2):
|
|
63
|
+
greville = []
|
|
64
|
+
for idx in range(len(degrees)):
|
|
65
|
+
p = degrees[idx]
|
|
66
|
+
knot = knotVects[idx]
|
|
67
|
+
n = ctrlPts.shape[1 + idx]
|
|
68
|
+
greville.append(
|
|
69
|
+
np.array(
|
|
70
|
+
[
|
|
71
|
+
sum([knot[i + k] for k in range(p)]) / p
|
|
72
|
+
for i in range(p - 1, n - 1 + p)
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
parametricPts = np.array(np.meshgrid(*greville, indexing="ij"))
|
|
77
|
+
notFound = np.zeros(parametricPts.shape[1:], dtype="bool")
|
|
78
|
+
notFound[tuple([slice(1, -1) for i in range(notFound.ndim)])] = True
|
|
79
|
+
found = np.logical_not(notFound)
|
|
80
|
+
known_points = parametricPts[:, found]
|
|
81
|
+
unknown_points = parametricPts[:, notFound]
|
|
82
|
+
data = ctrlPts[:, found]
|
|
83
|
+
ctrlPts[:, notFound] = _IDW(known_points, unknown_points, data, exposant=exposant)
|
|
84
|
+
return ctrlPts
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# %% Constants for B-Spline circles
|
|
88
|
+
_p = 2
|
|
89
|
+
_knot = np.array([0, 0, 0, 1 / 4, 1 / 2, 3 / 4, 1, 1, 1], dtype="float")
|
|
90
|
+
_C = np.array(
|
|
91
|
+
[
|
|
92
|
+
-41687040 / 36473 * np.sqrt(2) / np.pi**3
|
|
93
|
+
- (4884480 / 36473) / np.pi**3
|
|
94
|
+
- 221 / 72946 * np.pi
|
|
95
|
+
+ (14069760 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
96
|
+
+ (50933760 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3,
|
|
97
|
+
-41687040 / 36473 * np.sqrt(2) / np.pi**3
|
|
98
|
+
- (4884480 / 36473) / np.pi**3
|
|
99
|
+
- 221 / 72946 * np.pi
|
|
100
|
+
+ (14069760 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
101
|
+
+ (50933760 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3,
|
|
102
|
+
-50557440 / 36473 * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
103
|
+
- 92505600 / 36473 * np.sqrt(2 - np.sqrt(2)) / np.pi**3
|
|
104
|
+
+ (26527 / 2334272) * np.pi
|
|
105
|
+
+ (18163200 / 36473) / np.pi**3
|
|
106
|
+
+ (103925760 / 36473) * np.sqrt(2) / np.pi**3,
|
|
107
|
+
-91238400 / 36473 * np.sqrt(2) / np.pi**3
|
|
108
|
+
- (41034240 / 36473) / np.pi**3
|
|
109
|
+
- 65605 / 2334272 * np.pi
|
|
110
|
+
+ (52646400 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3
|
|
111
|
+
+ (70632960 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3,
|
|
112
|
+
(1 / 16) * np.pi,
|
|
113
|
+
0,
|
|
114
|
+
],
|
|
115
|
+
dtype="float",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# %% 1D parametric space
|
|
120
|
+
def _base_quarter_circle():
|
|
121
|
+
degrees = np.array([_p], dtype="int")
|
|
122
|
+
knots = [_knot]
|
|
123
|
+
Z = np.zeros_like(_C)
|
|
124
|
+
ctrlPts = np.array([_C, _C[::-1], Z])
|
|
125
|
+
quarter_circle = BSpline(degrees, knots)
|
|
126
|
+
return quarter_circle, ctrlPts
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def new_quarter_circle(center, normal, radius):
|
|
130
|
+
"""
|
|
131
|
+
Creates a B-spline quarter circle from a given center, normal and radius.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
center : array_like
|
|
136
|
+
The center of the quarter circle.
|
|
137
|
+
normal : array_like
|
|
138
|
+
The normal vector of the quarter circle.
|
|
139
|
+
radius : float
|
|
140
|
+
The radius of the quarter circle.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
quarter_circle : BSpline
|
|
145
|
+
The quarter circle.
|
|
146
|
+
ctrlPts : array_like
|
|
147
|
+
The control points of the quarter circle.
|
|
148
|
+
"""
|
|
149
|
+
quarter_circle, ctrlPts = _base_quarter_circle()
|
|
150
|
+
|
|
151
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
152
|
+
|
|
153
|
+
# The unit vector pointing upwards
|
|
154
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
155
|
+
|
|
156
|
+
# The normal vector of the quarter circle
|
|
157
|
+
normal = np.array(normal, dtype="float")
|
|
158
|
+
normal /= np.linalg.norm(normal)
|
|
159
|
+
axis = np.cross(e_z, normal)
|
|
160
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
161
|
+
|
|
162
|
+
# The vector to translate by
|
|
163
|
+
translation_vector = np.array(center, dtype="float")
|
|
164
|
+
|
|
165
|
+
# Apply the transformations to the control points
|
|
166
|
+
ctrlPts = scale_rotate_translate(
|
|
167
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return quarter_circle, ctrlPts
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _base_circle():
|
|
174
|
+
degrees = np.array([_p], dtype="int")
|
|
175
|
+
knots = [
|
|
176
|
+
np.array(
|
|
177
|
+
[0 / 4 + float(k) / 4 for k in _knot[:-_p]]
|
|
178
|
+
+ [1 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
179
|
+
+ [2 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
180
|
+
+ [3 / 4 + float(k) / 4 for k in _knot[_p:]],
|
|
181
|
+
dtype="float",
|
|
182
|
+
)
|
|
183
|
+
]
|
|
184
|
+
Z = np.zeros_like(_C)
|
|
185
|
+
ctrlPts = np.concatenate(
|
|
186
|
+
(
|
|
187
|
+
np.array([_C[:-1], _C[::-1][:-1], Z[:-1]]),
|
|
188
|
+
np.array([-_C[::-1][:-1], _C[:-1], Z[:-1]]),
|
|
189
|
+
np.array([-_C[:-1], -_C[::-1][:-1], Z[:-1]]),
|
|
190
|
+
np.array([_C[::-1], -_C, Z]),
|
|
191
|
+
),
|
|
192
|
+
axis=1,
|
|
193
|
+
)
|
|
194
|
+
circle = BSpline(degrees, knots)
|
|
195
|
+
return circle, ctrlPts
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def new_circle(center, normal, radius):
|
|
199
|
+
"""
|
|
200
|
+
Create a B-spline circle in 3D space.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
center : array_like
|
|
205
|
+
The center of the circle.
|
|
206
|
+
normal : array_like
|
|
207
|
+
The normal vector of the circle.
|
|
208
|
+
radius : float
|
|
209
|
+
The radius of the circle.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
circle : BSpline
|
|
214
|
+
The circle.
|
|
215
|
+
ctrlPts : array_like
|
|
216
|
+
The control points of the circle.
|
|
217
|
+
"""
|
|
218
|
+
circle, ctrlPts = _base_circle()
|
|
219
|
+
|
|
220
|
+
# The vector to scale by
|
|
221
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
222
|
+
|
|
223
|
+
# The unit vector pointing upwards
|
|
224
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
225
|
+
|
|
226
|
+
# The normal vector of the circle
|
|
227
|
+
normal = np.array(normal, dtype="float")
|
|
228
|
+
normal /= np.linalg.norm(normal)
|
|
229
|
+
axis = np.cross(e_z, normal)
|
|
230
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
231
|
+
|
|
232
|
+
# The vector to translate by
|
|
233
|
+
translation_vector = np.array(center, dtype="float")
|
|
234
|
+
|
|
235
|
+
# Apply the transformations to the control points
|
|
236
|
+
ctrlPts = scale_rotate_translate(
|
|
237
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return circle, ctrlPts
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# %% 2D parametric space
|
|
244
|
+
def _base_disk():
|
|
245
|
+
q = 1
|
|
246
|
+
degrees = np.array([_p, q], dtype="int")
|
|
247
|
+
knots = [
|
|
248
|
+
np.array(
|
|
249
|
+
[0 / 4 + float(k) / 4 for k in _knot[:-_p]]
|
|
250
|
+
+ [1 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
251
|
+
+ [2 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
252
|
+
+ [3 / 4 + float(k) / 4 for k in _knot[_p:]],
|
|
253
|
+
dtype="float",
|
|
254
|
+
),
|
|
255
|
+
np.array([0] * (q + 1) + [1] * (q + 1), dtype="float"),
|
|
256
|
+
]
|
|
257
|
+
Z = np.zeros_like(_C)
|
|
258
|
+
ctrlPts = np.concatenate(
|
|
259
|
+
(
|
|
260
|
+
np.array([_C[:-1], _C[::-1][:-1], Z[:-1]]),
|
|
261
|
+
np.array([-_C[::-1][:-1], _C[:-1], Z[:-1]]),
|
|
262
|
+
np.array([-_C[:-1], -_C[::-1][:-1], Z[:-1]]),
|
|
263
|
+
np.array([_C[::-1], -_C, Z]),
|
|
264
|
+
),
|
|
265
|
+
axis=1,
|
|
266
|
+
)
|
|
267
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
268
|
+
ctrlPts = np.concatenate((ctrlPts, np.zeros_like(ctrlPts)), axis=-1)
|
|
269
|
+
disk = BSpline(degrees, knots)
|
|
270
|
+
return disk, ctrlPts
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def new_disk(center, normal, radius):
|
|
274
|
+
"""
|
|
275
|
+
Creates a B-spline disk from a given center, normal and radius.
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
center : array_like
|
|
280
|
+
The center of the disk.
|
|
281
|
+
normal : array_like
|
|
282
|
+
The normal vector of the disk.
|
|
283
|
+
radius : float
|
|
284
|
+
The radius of the disk.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
disk : BSpline
|
|
289
|
+
The disk.
|
|
290
|
+
ctrlPts : array_like
|
|
291
|
+
The control points of the disk.
|
|
292
|
+
"""
|
|
293
|
+
disk, ctrlPts = _base_disk()
|
|
294
|
+
|
|
295
|
+
# The vector to scale by
|
|
296
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
297
|
+
|
|
298
|
+
# The unit vector pointing upwards
|
|
299
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
300
|
+
|
|
301
|
+
# The normal vector of the disk
|
|
302
|
+
normal = np.array(normal, dtype="float")
|
|
303
|
+
normal /= np.linalg.norm(normal)
|
|
304
|
+
axis = np.cross(e_z, normal)
|
|
305
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
306
|
+
|
|
307
|
+
# The vector to translate by
|
|
308
|
+
translation_vector = np.array(center, dtype="float")
|
|
309
|
+
|
|
310
|
+
# Apply the transformations to the control points
|
|
311
|
+
ctrlPts = scale_rotate_translate(
|
|
312
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return disk, ctrlPts
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _base_degenerated_disk():
|
|
319
|
+
degrees = np.array([_p, _p], dtype="int")
|
|
320
|
+
knots = [_knot, _knot]
|
|
321
|
+
Z = np.zeros_like(_C)
|
|
322
|
+
n = _C.size
|
|
323
|
+
ctrlPts = np.empty((3, n, n), dtype="float")
|
|
324
|
+
ctrlPts[:, 0, :] = [+_C[::1], +_C[::-1], Z]
|
|
325
|
+
ctrlPts[:, -1, :] = [-_C[::-1], -_C[::1], Z]
|
|
326
|
+
ctrlPts[:, :, 0] = [+_C[::1], -_C[::-1], Z]
|
|
327
|
+
ctrlPts[:, :, -1] = [-_C[::-1], +_C[::1], Z]
|
|
328
|
+
ctrlPts = _find_inner_ctrlPts(degrees, knots, ctrlPts)
|
|
329
|
+
disk = BSpline(degrees, knots)
|
|
330
|
+
return disk, ctrlPts
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def new_degenerated_disk(center, normal, radius):
|
|
334
|
+
"""
|
|
335
|
+
Creates a B-spline degenerated disk from a given center, normal and radius.
|
|
336
|
+
The disk is degenerated as it is created by "blowing" a square into a circle.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
center : array_like
|
|
341
|
+
The center of the degenerated disk.
|
|
342
|
+
normal : array_like
|
|
343
|
+
The normal vector of the degenerated disk.
|
|
344
|
+
radius : float
|
|
345
|
+
The radius of the degenerated disk.
|
|
346
|
+
|
|
347
|
+
Returns
|
|
348
|
+
-------
|
|
349
|
+
disk : BSpline
|
|
350
|
+
The degenerated disk.
|
|
351
|
+
ctrlPts : array_like
|
|
352
|
+
The control points of the degenerated disk.
|
|
353
|
+
"""
|
|
354
|
+
disk, ctrlPts = _base_degenerated_disk()
|
|
355
|
+
|
|
356
|
+
# The vector to scale by
|
|
357
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
358
|
+
|
|
359
|
+
# The unit vector pointing upwards
|
|
360
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
361
|
+
|
|
362
|
+
# The normal vector of the degenerated disk
|
|
363
|
+
normal = np.array(normal, dtype="float")
|
|
364
|
+
normal /= np.linalg.norm(normal)
|
|
365
|
+
axis = np.cross(e_z, normal)
|
|
366
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
367
|
+
|
|
368
|
+
# The vector to translate by
|
|
369
|
+
translation_vector = np.array(center, dtype="float")
|
|
370
|
+
|
|
371
|
+
# Apply the transformations to the control points
|
|
372
|
+
ctrlPts = scale_rotate_translate(
|
|
373
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return disk, ctrlPts
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _base_quarter_pipe():
|
|
380
|
+
q = 1
|
|
381
|
+
degrees = np.array([_p, q], dtype="int")
|
|
382
|
+
knots = [_knot, np.array([0] * (q + 1) + [1] * (q + 1), dtype="float")]
|
|
383
|
+
Z = np.zeros_like(_C)
|
|
384
|
+
ctrlPts = np.array([_C, _C[::-1], Z])
|
|
385
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
386
|
+
ctrlPts_m = ctrlPts.copy()
|
|
387
|
+
ctrlPts_m[2] = 0
|
|
388
|
+
ctrlPts_p = ctrlPts.copy()
|
|
389
|
+
ctrlPts_p[2] = 1
|
|
390
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
391
|
+
quarter_pipe = BSpline(degrees, knots)
|
|
392
|
+
return quarter_pipe, ctrlPts
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def new_quarter_pipe(center_front, orientation, radius, length):
|
|
396
|
+
"""
|
|
397
|
+
Creates a B-spline quarter pipe from a given center, orientation, radius and length.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
center_front : array_like
|
|
402
|
+
The center of the front of the quarter pipe.
|
|
403
|
+
orientation : array_like
|
|
404
|
+
The normal vector of the quarter pipe.
|
|
405
|
+
radius : float
|
|
406
|
+
The radius of the quarter pipe.
|
|
407
|
+
length : float
|
|
408
|
+
The length of the quarter pipe.
|
|
409
|
+
|
|
410
|
+
Returns
|
|
411
|
+
-------
|
|
412
|
+
quarter_pipe : BSpline
|
|
413
|
+
The quarter pipe.
|
|
414
|
+
ctrlPts : array_like
|
|
415
|
+
The control points of the quarter pipe.
|
|
416
|
+
"""
|
|
417
|
+
quarter_pipe, ctrlPts = _base_quarter_pipe()
|
|
418
|
+
|
|
419
|
+
# The vector to scale by
|
|
420
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
421
|
+
|
|
422
|
+
# The unit vector pointing upwards
|
|
423
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
424
|
+
|
|
425
|
+
# The normal vector of the quarter pipe
|
|
426
|
+
orientation = np.array(orientation, dtype="float")
|
|
427
|
+
orientation /= np.linalg.norm(orientation)
|
|
428
|
+
axis = np.cross(e_z, orientation)
|
|
429
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
430
|
+
|
|
431
|
+
# The vector to translate by
|
|
432
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
433
|
+
|
|
434
|
+
# Apply the transformations to the control points
|
|
435
|
+
ctrlPts = scale_rotate_translate(
|
|
436
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
return quarter_pipe, ctrlPts
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _base_pipe():
|
|
443
|
+
q = 1
|
|
444
|
+
degrees = np.array([_p, q], dtype="int")
|
|
445
|
+
knots = [
|
|
446
|
+
np.array(
|
|
447
|
+
[0 / 4 + float(k) / 4 for k in _knot[:-_p]]
|
|
448
|
+
+ [1 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
449
|
+
+ [2 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
450
|
+
+ [3 / 4 + float(k) / 4 for k in _knot[_p:]],
|
|
451
|
+
dtype="float",
|
|
452
|
+
),
|
|
453
|
+
np.array([0] * (q + 1) + [1] * (q + 1), dtype="float"),
|
|
454
|
+
]
|
|
455
|
+
Z = np.zeros_like(_C)
|
|
456
|
+
ctrlPts = np.concatenate(
|
|
457
|
+
(
|
|
458
|
+
np.array([_C[:-1], _C[::-1][:-1], Z[:-1]]),
|
|
459
|
+
np.array([-_C[::-1][:-1], _C[:-1], Z[:-1]]),
|
|
460
|
+
np.array([-_C[:-1], -_C[::-1][:-1], Z[:-1]]),
|
|
461
|
+
np.array([_C[::-1], -_C, Z]),
|
|
462
|
+
),
|
|
463
|
+
axis=1,
|
|
464
|
+
)
|
|
465
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
466
|
+
ctrlPts_m = ctrlPts.copy()
|
|
467
|
+
ctrlPts_m[2] = 0
|
|
468
|
+
ctrlPts_p = ctrlPts.copy()
|
|
469
|
+
ctrlPts_p[2] = 1
|
|
470
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
471
|
+
pipe = BSpline(degrees, knots)
|
|
472
|
+
return pipe, ctrlPts
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def new_pipe(center_front, orientation, radius, length):
|
|
476
|
+
"""
|
|
477
|
+
Creates a B-spline pipe from a given center, orientation, radius and length.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
center_front : array_like
|
|
482
|
+
The center of the front of the pipe.
|
|
483
|
+
orientation : array_like
|
|
484
|
+
The normal vector of the pipe.
|
|
485
|
+
radius : float
|
|
486
|
+
The radius of the pipe.
|
|
487
|
+
length : float
|
|
488
|
+
The length of the pipe.
|
|
489
|
+
|
|
490
|
+
Returns
|
|
491
|
+
-------
|
|
492
|
+
pipe : BSpline
|
|
493
|
+
The pipe.
|
|
494
|
+
ctrlPts : array_like
|
|
495
|
+
The control points of the pipe.
|
|
496
|
+
"""
|
|
497
|
+
pipe, ctrlPts = _base_pipe()
|
|
498
|
+
|
|
499
|
+
# The vector to scale by
|
|
500
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
501
|
+
|
|
502
|
+
# The unit vector pointing upwards
|
|
503
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
504
|
+
|
|
505
|
+
# The normal vector of the pipe
|
|
506
|
+
orientation = np.array(orientation, dtype="float")
|
|
507
|
+
orientation /= np.linalg.norm(orientation)
|
|
508
|
+
axis = np.cross(e_z, orientation)
|
|
509
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
510
|
+
|
|
511
|
+
# The vector to translate by
|
|
512
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
513
|
+
|
|
514
|
+
# Apply the transformations to the control points
|
|
515
|
+
ctrlPts = scale_rotate_translate(
|
|
516
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
return pipe, ctrlPts
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
# %% 3D parametric space
|
|
523
|
+
def _base_quarter_cylinder():
|
|
524
|
+
q = 1
|
|
525
|
+
r = 1
|
|
526
|
+
degrees = np.array([_p, q, r], dtype="int")
|
|
527
|
+
knots = [
|
|
528
|
+
_knot,
|
|
529
|
+
np.array([0] * (q + 1) + [1] * (q + 1), dtype="float"),
|
|
530
|
+
np.array([0] * (r + 1) + [1] * (r + 1), dtype="float"),
|
|
531
|
+
]
|
|
532
|
+
Z = np.zeros_like(_C)
|
|
533
|
+
ctrlPts = np.array([_C, _C[::-1], Z])
|
|
534
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
535
|
+
ctrlPts = np.concatenate((ctrlPts, np.zeros_like(ctrlPts)), axis=-1)
|
|
536
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
537
|
+
ctrlPts_m = ctrlPts.copy()
|
|
538
|
+
ctrlPts_m[2] = 0
|
|
539
|
+
ctrlPts_p = ctrlPts.copy()
|
|
540
|
+
ctrlPts_p[2] = 1
|
|
541
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
542
|
+
quarter_cylinder = BSpline(degrees, knots)
|
|
543
|
+
return quarter_cylinder, ctrlPts
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def new_quarter_cylinder(center_front, orientation, radius, length):
|
|
547
|
+
"""
|
|
548
|
+
Creates a B-spline quarter cylinder from a given center, orientation, radius and length.
|
|
549
|
+
|
|
550
|
+
Parameters
|
|
551
|
+
----------
|
|
552
|
+
center_front : array_like
|
|
553
|
+
The center of the front of the quarter cylinder.
|
|
554
|
+
orientation : array_like
|
|
555
|
+
The normal vector of the quarter cylinder.
|
|
556
|
+
radius : float
|
|
557
|
+
The radius of the quarter cylinder.
|
|
558
|
+
length : float
|
|
559
|
+
The length of the quarter cylinder.
|
|
560
|
+
|
|
561
|
+
Returns
|
|
562
|
+
-------
|
|
563
|
+
quarter_cylinder : BSpline
|
|
564
|
+
The quarter cylinder.
|
|
565
|
+
ctrlPts : array_like
|
|
566
|
+
The control points of the quarter cylinder.
|
|
567
|
+
"""
|
|
568
|
+
quarter_cylinder, ctrlPts = _base_quarter_cylinder()
|
|
569
|
+
|
|
570
|
+
# The vector to scale by
|
|
571
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
572
|
+
|
|
573
|
+
# The unit vector pointing upwards
|
|
574
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
575
|
+
|
|
576
|
+
# The normal vector of the quarter cylinder
|
|
577
|
+
orientation = np.array(orientation, dtype="float")
|
|
578
|
+
orientation /= np.linalg.norm(orientation)
|
|
579
|
+
axis = np.cross(e_z, orientation)
|
|
580
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
581
|
+
|
|
582
|
+
# The vector to translate by
|
|
583
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
584
|
+
|
|
585
|
+
# Apply the transformations to the control points
|
|
586
|
+
ctrlPts = scale_rotate_translate(
|
|
587
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
return quarter_cylinder, ctrlPts
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _base_cylinder():
|
|
594
|
+
q = 1
|
|
595
|
+
r = 1
|
|
596
|
+
degrees = np.array([_p, q, r], dtype="int")
|
|
597
|
+
knots = [
|
|
598
|
+
np.array(
|
|
599
|
+
[0 / 4 + float(k) / 4 for k in _knot[:-_p]]
|
|
600
|
+
+ [1 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
601
|
+
+ [2 / 4 + float(k) / 4 for k in _knot[_p:-_p]]
|
|
602
|
+
+ [3 / 4 + float(k) / 4 for k in _knot[_p:]],
|
|
603
|
+
dtype="float",
|
|
604
|
+
),
|
|
605
|
+
np.array([0] * (q + 1) + [1] * (q + 1), dtype="float"),
|
|
606
|
+
np.array([0] * (r + 1) + [1] * (r + 1), dtype="float"),
|
|
607
|
+
]
|
|
608
|
+
Z = np.zeros_like(_C)
|
|
609
|
+
ctrlPts = np.concatenate(
|
|
610
|
+
(
|
|
611
|
+
np.array([_C[:-1], _C[::-1][:-1], Z[:-1]]),
|
|
612
|
+
np.array([-_C[::-1][:-1], _C[:-1], Z[:-1]]),
|
|
613
|
+
np.array([-_C[:-1], -_C[::-1][:-1], Z[:-1]]),
|
|
614
|
+
np.array([_C[::-1], -_C, Z]),
|
|
615
|
+
),
|
|
616
|
+
axis=1,
|
|
617
|
+
)
|
|
618
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
619
|
+
ctrlPts = np.concatenate((ctrlPts, np.zeros_like(ctrlPts)), axis=-1)
|
|
620
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
621
|
+
ctrlPts_m = ctrlPts.copy()
|
|
622
|
+
ctrlPts_m[2] = 0
|
|
623
|
+
ctrlPts_p = ctrlPts.copy()
|
|
624
|
+
ctrlPts_p[2] = 1
|
|
625
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
626
|
+
cylinder = BSpline(degrees, knots)
|
|
627
|
+
return cylinder, ctrlPts
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def new_cylinder(center_front, orientation, radius, length):
|
|
631
|
+
"""
|
|
632
|
+
Creates a B-spline cylinder from a given center, orientation, radius and length.
|
|
633
|
+
|
|
634
|
+
Parameters
|
|
635
|
+
----------
|
|
636
|
+
center_front : array_like
|
|
637
|
+
The center of the front of the cylinder.
|
|
638
|
+
orientation : array_like
|
|
639
|
+
The normal vector of the cylinder.
|
|
640
|
+
radius : float
|
|
641
|
+
The radius of the cylinder.
|
|
642
|
+
length : float
|
|
643
|
+
The length of the cylinder.
|
|
644
|
+
|
|
645
|
+
Returns
|
|
646
|
+
-------
|
|
647
|
+
cylinder : BSpline
|
|
648
|
+
The cylinder.
|
|
649
|
+
ctrlPts : array_like
|
|
650
|
+
The control points of the cylinder.
|
|
651
|
+
"""
|
|
652
|
+
cylinder, ctrlPts = _base_cylinder()
|
|
653
|
+
|
|
654
|
+
# The vector to scale by
|
|
655
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
656
|
+
|
|
657
|
+
# The unit vector pointing upwards
|
|
658
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
659
|
+
|
|
660
|
+
# The normal vector of the cylinder
|
|
661
|
+
orientation = np.array(orientation, dtype="float")
|
|
662
|
+
orientation /= np.linalg.norm(orientation)
|
|
663
|
+
axis = np.cross(e_z, orientation)
|
|
664
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
665
|
+
|
|
666
|
+
# The vector to translate by
|
|
667
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
668
|
+
|
|
669
|
+
# Apply the transformations to the control points
|
|
670
|
+
ctrlPts = scale_rotate_translate(
|
|
671
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
return cylinder, ctrlPts
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _base_degenerated_cylinder():
|
|
678
|
+
q = 1
|
|
679
|
+
degrees = np.array([_p, _p, q], dtype="int")
|
|
680
|
+
knots = [_knot, _knot, np.array([0] * (q + 1) + [1] * (q + 1), dtype="float")]
|
|
681
|
+
Z = np.zeros_like(_C)
|
|
682
|
+
n = _C.size
|
|
683
|
+
ctrlPts = np.empty((3, n, n), dtype="float")
|
|
684
|
+
ctrlPts[:, 0, :] = [+_C[::1], +_C[::-1], Z]
|
|
685
|
+
ctrlPts[:, -1, :] = [-_C[::-1], -_C[::1], Z]
|
|
686
|
+
ctrlPts[:, :, 0] = [+_C[::1], -_C[::-1], Z]
|
|
687
|
+
ctrlPts[:, :, -1] = [-_C[::-1], +_C[::1], Z]
|
|
688
|
+
ctrlPts = _find_inner_ctrlPts(degrees[:-1], knots[:-1], ctrlPts)
|
|
689
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
690
|
+
ctrlPts = np.concatenate((ctrlPts, ctrlPts), axis=-1)
|
|
691
|
+
ctrlPts[2, :, :, 1] = 1
|
|
692
|
+
cylinder = BSpline(degrees, knots)
|
|
693
|
+
return cylinder, ctrlPts
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def new_degenerated_cylinder(center_front, orientation, radius, length):
|
|
697
|
+
"""
|
|
698
|
+
Creates a B-spline cylinder from a given center, orientation, radius and length.
|
|
699
|
+
The cylinder is degenerated as it is created by "blowing" a square into a circle
|
|
700
|
+
before extruding it into a cylinder.
|
|
701
|
+
|
|
702
|
+
Parameters
|
|
703
|
+
----------
|
|
704
|
+
center_front : array_like
|
|
705
|
+
The center of the front of the cylinder.
|
|
706
|
+
orientation : array_like
|
|
707
|
+
The normal vector of the cylinder.
|
|
708
|
+
radius : float
|
|
709
|
+
The radius of the cylinder.
|
|
710
|
+
length : float
|
|
711
|
+
The length of the cylinder.
|
|
712
|
+
|
|
713
|
+
Returns
|
|
714
|
+
-------
|
|
715
|
+
cylinder : BSpline
|
|
716
|
+
The cylinder.
|
|
717
|
+
ctrlPts : array_like
|
|
718
|
+
The control points of the cylinder.
|
|
719
|
+
"""
|
|
720
|
+
cylinder, ctrlPts = _base_degenerated_cylinder()
|
|
721
|
+
|
|
722
|
+
# Scale the control points
|
|
723
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
724
|
+
|
|
725
|
+
# Rotate the control points
|
|
726
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
727
|
+
orientation = np.array(orientation, dtype="float")
|
|
728
|
+
orientation /= np.linalg.norm(orientation)
|
|
729
|
+
axis = np.cross(e_z, orientation)
|
|
730
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
731
|
+
|
|
732
|
+
# Translate the control points
|
|
733
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
734
|
+
|
|
735
|
+
# Apply the transformations to the control points
|
|
736
|
+
ctrlPts = scale_rotate_translate(
|
|
737
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
return cylinder, ctrlPts
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
# %% Constants for closed knot vector B-Spline circles
|
|
744
|
+
_p_closed = 2
|
|
745
|
+
_knot_closed = (
|
|
746
|
+
1 / 8 * np.array([-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype="float")
|
|
747
|
+
)
|
|
748
|
+
_a = -(226560 / 751) / np.pi**3 + (176640 / 751) * np.sqrt(2) / np.pi**3
|
|
749
|
+
_b = -(579840 / 751) / np.pi**3 + (403200 / 751) * np.sqrt(2) / np.pi**3
|
|
750
|
+
_C_closed = np.array(
|
|
751
|
+
[
|
|
752
|
+
[_a, _b],
|
|
753
|
+
[_a, -_b],
|
|
754
|
+
[-_b, _a],
|
|
755
|
+
[_b, _a],
|
|
756
|
+
[-_a, -_b],
|
|
757
|
+
[-_a, _b],
|
|
758
|
+
[_b, -_a],
|
|
759
|
+
[-_b, -_a],
|
|
760
|
+
[_a, _b],
|
|
761
|
+
[_a, -_b],
|
|
762
|
+
],
|
|
763
|
+
dtype="float",
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
# %% 1D parametric space
|
|
768
|
+
def _base_closed_circle():
|
|
769
|
+
degrees = np.array([_p_closed], dtype="int")
|
|
770
|
+
knots = [_knot_closed]
|
|
771
|
+
Z = np.zeros_like(_C_closed)
|
|
772
|
+
ctrlPts = _C_closed
|
|
773
|
+
circle = BSpline(degrees, knots)
|
|
774
|
+
return circle, ctrlPts
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def new_closed_circle(center, normal, radius):
|
|
778
|
+
"""
|
|
779
|
+
Creates a B-spline circle from a given center, normal and radius.
|
|
780
|
+
The circle is closed as it the junction between the start and the end of the
|
|
781
|
+
parametric space spans enough elements to conserve the C^(p-1) continuity.
|
|
782
|
+
|
|
783
|
+
Parameters
|
|
784
|
+
----------
|
|
785
|
+
center : array_like
|
|
786
|
+
The center of the circle.
|
|
787
|
+
normal : array_like
|
|
788
|
+
The normal vector of the circle.
|
|
789
|
+
radius : float
|
|
790
|
+
The radius of the circle.
|
|
791
|
+
|
|
792
|
+
Returns
|
|
793
|
+
-------
|
|
794
|
+
circle : BSpline
|
|
795
|
+
The circle.
|
|
796
|
+
ctrlPts : array_like
|
|
797
|
+
The control points of the circle.
|
|
798
|
+
"""
|
|
799
|
+
# Get the base circle
|
|
800
|
+
circle, ctrlPts = _base_closed_circle()
|
|
801
|
+
|
|
802
|
+
# Scale the control points by the radius
|
|
803
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
804
|
+
|
|
805
|
+
# Rotate the control points by the angle between the normal vector and the up vector
|
|
806
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
807
|
+
normal = np.array(normal, dtype="float")
|
|
808
|
+
normal /= np.linalg.norm(normal)
|
|
809
|
+
axis = np.cross(e_z, normal)
|
|
810
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
811
|
+
|
|
812
|
+
# Translate the control points by the center vector
|
|
813
|
+
translation_vector = np.array(center, dtype="float")
|
|
814
|
+
|
|
815
|
+
# Apply the transformations to the control points
|
|
816
|
+
ctrlPts = scale_rotate_translate(
|
|
817
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
return circle, ctrlPts
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
# %% 2D parametric space
|
|
824
|
+
def _base_closed_disk():
|
|
825
|
+
q = 1
|
|
826
|
+
degrees = np.array([_p_closed, q], dtype="int")
|
|
827
|
+
knots = [_knot_closed, np.array([0] * (q + 1) + [1] * (q + 1), dtype="float")]
|
|
828
|
+
ctrlPts = np.expand_dims(_C_closed, axis=-1)
|
|
829
|
+
ctrlPts = np.concatenate((ctrlPts, np.zeros_like(ctrlPts)), axis=-1)
|
|
830
|
+
disk = BSpline(degrees, knots)
|
|
831
|
+
return disk, ctrlPts
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def new_closed_disk(center, normal, radius):
|
|
835
|
+
"""
|
|
836
|
+
Creates a B-spline disk from a given center, normal and radius.
|
|
837
|
+
The disk is closed as it the junction between the start and the end of the
|
|
838
|
+
parametric space spans enough elements to conserve the C^(p-1) continuity.
|
|
839
|
+
|
|
840
|
+
Parameters
|
|
841
|
+
----------
|
|
842
|
+
center : array_like
|
|
843
|
+
The center of the disk.
|
|
844
|
+
normal : array_like
|
|
845
|
+
The normal vector of the disk.
|
|
846
|
+
radius : float
|
|
847
|
+
The radius of the disk.
|
|
848
|
+
|
|
849
|
+
Returns
|
|
850
|
+
-------
|
|
851
|
+
disk : BSpline
|
|
852
|
+
The disk.
|
|
853
|
+
ctrlPts : array_like
|
|
854
|
+
The control points of the disk.
|
|
855
|
+
"""
|
|
856
|
+
disk, ctrlPts = _base_closed_disk()
|
|
857
|
+
|
|
858
|
+
# Scale the control points by the radius
|
|
859
|
+
scale_vector = np.array([radius, radius, 1], dtype="float")
|
|
860
|
+
|
|
861
|
+
# Rotate the control points by the angle between the normal vector and the up vector
|
|
862
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
863
|
+
normal = np.array(normal, dtype="float")
|
|
864
|
+
normal /= np.linalg.norm(normal)
|
|
865
|
+
axis = np.cross(e_z, normal)
|
|
866
|
+
angle = np.arccos(np.dot(e_z, normal))
|
|
867
|
+
|
|
868
|
+
# Translate the control points by the center vector
|
|
869
|
+
translation_vector = np.array(center, dtype="float")
|
|
870
|
+
|
|
871
|
+
# Apply the transformations to the control points
|
|
872
|
+
ctrlPts = scale_rotate_translate(
|
|
873
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
return disk, ctrlPts
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _base_closed_pipe():
|
|
880
|
+
q = 1
|
|
881
|
+
degrees = np.array([_p_closed, q], dtype="int")
|
|
882
|
+
knots = [_knot_closed, np.array([0] * (q + 1) + [1] * (q + 1), dtype="float")]
|
|
883
|
+
ctrlPts = np.expand_dims(_C_closed, axis=-1)
|
|
884
|
+
ctrlPts_m = ctrlPts.copy()
|
|
885
|
+
ctrlPts_m[2] = 0
|
|
886
|
+
ctrlPts_p = ctrlPts.copy()
|
|
887
|
+
ctrlPts_p[2] = 1
|
|
888
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
889
|
+
pipe = BSpline(degrees, knots)
|
|
890
|
+
return pipe, ctrlPts
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def new_closed_pipe(center_front, orientation, radius, length):
|
|
894
|
+
"""
|
|
895
|
+
Creates a B-spline closed pipe from a given center, orientation, radius and length.
|
|
896
|
+
The pipe is closed as the junction between the start and the end of the
|
|
897
|
+
parametric space along the circular direction spans enough elements to conserve the
|
|
898
|
+
C^(p-1) continuity.
|
|
899
|
+
|
|
900
|
+
Parameters
|
|
901
|
+
----------
|
|
902
|
+
center_front : array_like
|
|
903
|
+
The center of the front of the pipe.
|
|
904
|
+
orientation : array_like
|
|
905
|
+
The normal vector of the pipe.
|
|
906
|
+
radius : float
|
|
907
|
+
The radius of the pipe.
|
|
908
|
+
length : float
|
|
909
|
+
The length of the pipe.
|
|
910
|
+
|
|
911
|
+
Returns
|
|
912
|
+
-------
|
|
913
|
+
pipe : BSpline
|
|
914
|
+
The pipe.
|
|
915
|
+
ctrlPts : array_like
|
|
916
|
+
The control points of the pipe.
|
|
917
|
+
"""
|
|
918
|
+
pipe, ctrlPts = _base_closed_pipe()
|
|
919
|
+
|
|
920
|
+
# Scale the control points by the radius and length
|
|
921
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
922
|
+
|
|
923
|
+
# Rotate the control points by the angle between the normal vector and the up vector
|
|
924
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
925
|
+
orientation = np.array(orientation, dtype="float")
|
|
926
|
+
orientation /= np.linalg.norm(orientation)
|
|
927
|
+
axis = np.cross(e_z, orientation)
|
|
928
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
929
|
+
|
|
930
|
+
# Translate the control points by the center vector
|
|
931
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
932
|
+
|
|
933
|
+
# Apply the transformations to the control points
|
|
934
|
+
ctrlPts = scale_rotate_translate(
|
|
935
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
return pipe, ctrlPts
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
# %% 3D parametric space
|
|
942
|
+
def _base_closed_cylinder():
|
|
943
|
+
q = 1
|
|
944
|
+
r = 1
|
|
945
|
+
degrees = np.array([_p_closed, q, r], dtype="int")
|
|
946
|
+
knots = [
|
|
947
|
+
_knot_closed,
|
|
948
|
+
np.array([0] * (q + 1) + [1] * (q + 1), dtype="float"),
|
|
949
|
+
np.array([0] * (r + 1) + [1] * (r + 1), dtype="float"),
|
|
950
|
+
]
|
|
951
|
+
ctrlPts = np.expand_dims(_C_closed, axis=-1)
|
|
952
|
+
ctrlPts = np.concatenate((ctrlPts, np.zeros_like(ctrlPts)), axis=-1)
|
|
953
|
+
ctrlPts = np.expand_dims(ctrlPts, axis=-1)
|
|
954
|
+
ctrlPts_m = ctrlPts.copy()
|
|
955
|
+
ctrlPts_m[2] = 0
|
|
956
|
+
ctrlPts_p = ctrlPts.copy()
|
|
957
|
+
ctrlPts_p[2] = 1
|
|
958
|
+
ctrlPts = np.concatenate((ctrlPts_m, ctrlPts_p), axis=-1)
|
|
959
|
+
cylinder = BSpline(degrees, knots)
|
|
960
|
+
return cylinder, ctrlPts
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def new_closed_cylinder(center_front, orientation, radius, length):
|
|
964
|
+
"""
|
|
965
|
+
Creates a B-spline closed cylinder from a given center, orientation, radius and length.
|
|
966
|
+
The cylinder is closed as the junction between the start and the end of the
|
|
967
|
+
parametric space along the circular direction spans enough elements to conserve the
|
|
968
|
+
C^(p-1) continuity.
|
|
969
|
+
|
|
970
|
+
Parameters
|
|
971
|
+
----------
|
|
972
|
+
center_front : array_like
|
|
973
|
+
The center of the front of the closed cylinder.
|
|
974
|
+
orientation : array_like
|
|
975
|
+
The normal vector of the closed cylinder.
|
|
976
|
+
radius : float
|
|
977
|
+
The radius of the closed cylinder.
|
|
978
|
+
length : float
|
|
979
|
+
The length of the closed cylinder.
|
|
980
|
+
|
|
981
|
+
Returns
|
|
982
|
+
-------
|
|
983
|
+
closed_cylinder : BSpline
|
|
984
|
+
The closed cylinder.
|
|
985
|
+
ctrlPts : array_like
|
|
986
|
+
The control points of the closed cylinder.
|
|
987
|
+
"""
|
|
988
|
+
cylinder, ctrlPts = _base_closed_cylinder()
|
|
989
|
+
|
|
990
|
+
# Scale the control points by the radius and length
|
|
991
|
+
scale_vector = np.array([radius, radius, length], dtype="float")
|
|
992
|
+
|
|
993
|
+
# Rotate the control points by the angle between the normal vector and the up vector
|
|
994
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
995
|
+
orientation = np.array(orientation, dtype="float")
|
|
996
|
+
orientation /= np.linalg.norm(orientation)
|
|
997
|
+
axis = np.cross(e_z, orientation)
|
|
998
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
999
|
+
|
|
1000
|
+
# Translate the control points by the center vector
|
|
1001
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
1002
|
+
|
|
1003
|
+
# Apply the transformations to the control points
|
|
1004
|
+
ctrlPts = scale_rotate_translate(
|
|
1005
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
return cylinder, ctrlPts
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
# %% strut elements
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
def new_quarter_strut(center_front, orientation, radius, length):
|
|
1015
|
+
"""
|
|
1016
|
+
Creates a B-spline quarter strut from a given center, orientation, radius and length.
|
|
1017
|
+
|
|
1018
|
+
Parameters
|
|
1019
|
+
----------
|
|
1020
|
+
center_front : array_like
|
|
1021
|
+
The center of the front of the quarter strut.
|
|
1022
|
+
orientation : array_like
|
|
1023
|
+
The normal vector of the quarter strut.
|
|
1024
|
+
radius : float
|
|
1025
|
+
The radius of the quarter strut.
|
|
1026
|
+
length : float
|
|
1027
|
+
The length of the quarter strut.
|
|
1028
|
+
|
|
1029
|
+
Returns
|
|
1030
|
+
-------
|
|
1031
|
+
quarter_strut : BSpline
|
|
1032
|
+
The quarter strut.
|
|
1033
|
+
ctrlPts : array_like
|
|
1034
|
+
The control points of the quarter strut.
|
|
1035
|
+
"""
|
|
1036
|
+
degrees = np.array([2, 1, 1], dtype="int")
|
|
1037
|
+
knots = [
|
|
1038
|
+
np.array([0, 0, 0, 1 / 4, 1 / 2, 3 / 4, 1, 1, 1], dtype="float"),
|
|
1039
|
+
np.array([0, 0, 1, 1], dtype="float"),
|
|
1040
|
+
np.array([0, 0, 1, 1], dtype="float"),
|
|
1041
|
+
]
|
|
1042
|
+
C = (
|
|
1043
|
+
np.array(
|
|
1044
|
+
[
|
|
1045
|
+
-41687040 / 36473 * np.sqrt(2) / np.pi**3
|
|
1046
|
+
- (4884480 / 36473) / np.pi**3
|
|
1047
|
+
- 221 / 72946 * np.pi
|
|
1048
|
+
+ (14069760 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
1049
|
+
+ (50933760 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3,
|
|
1050
|
+
-41687040 / 36473 * np.sqrt(2) / np.pi**3
|
|
1051
|
+
- (4884480 / 36473) / np.pi**3
|
|
1052
|
+
- 221 / 72946 * np.pi
|
|
1053
|
+
+ (14069760 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
1054
|
+
+ (50933760 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3,
|
|
1055
|
+
-50557440 / 36473 * np.sqrt(np.sqrt(2) + 2) / np.pi**3
|
|
1056
|
+
- 92505600 / 36473 * np.sqrt(2 - np.sqrt(2)) / np.pi**3
|
|
1057
|
+
+ (26527 / 2334272) * np.pi
|
|
1058
|
+
+ (18163200 / 36473) / np.pi**3
|
|
1059
|
+
+ (103925760 / 36473) * np.sqrt(2) / np.pi**3,
|
|
1060
|
+
-91238400 / 36473 * np.sqrt(2) / np.pi**3
|
|
1061
|
+
- (41034240 / 36473) / np.pi**3
|
|
1062
|
+
- 65605 / 2334272 * np.pi
|
|
1063
|
+
+ (52646400 / 36473) * np.sqrt(2 - np.sqrt(2)) / np.pi**3
|
|
1064
|
+
+ (70632960 / 36473) * np.sqrt(np.sqrt(2) + 2) / np.pi**3,
|
|
1065
|
+
(1 / 16) * np.pi,
|
|
1066
|
+
0,
|
|
1067
|
+
],
|
|
1068
|
+
dtype="float",
|
|
1069
|
+
)
|
|
1070
|
+
* radius
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
tmp = radius * np.ones_like(C)
|
|
1074
|
+
front = np.array([C, C[::-1], tmp])
|
|
1075
|
+
tmp = np.zeros_like(front)
|
|
1076
|
+
front = np.concatenate((front[..., None], tmp[..., None]), axis=-1)
|
|
1077
|
+
|
|
1078
|
+
tmp = length - radius * np.ones_like(C)
|
|
1079
|
+
back = np.array([C, C[::-1], tmp])
|
|
1080
|
+
tmp = np.zeros_like(back)
|
|
1081
|
+
tmp[-1] = length
|
|
1082
|
+
back = np.concatenate((back[..., None], tmp[..., None]), axis=-1)
|
|
1083
|
+
|
|
1084
|
+
ctrlPts = np.concatenate((front[..., None], back[..., None]), axis=-1)
|
|
1085
|
+
|
|
1086
|
+
spline = BSpline(degrees, knots)
|
|
1087
|
+
ctrlPts = spline.orderElevation(ctrlPts, [0, 0, 1])
|
|
1088
|
+
ctrlPts = spline.knotInsertion(
|
|
1089
|
+
ctrlPts,
|
|
1090
|
+
[np.array([]), np.array([]), np.array([0.25, 0.5, 0.75], dtype="float")],
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
p = np.arctan(3 - 2 * np.sqrt(2))
|
|
1094
|
+
k = np.linspace(p, np.pi / 2 - p, C.size)
|
|
1095
|
+
x = (
|
|
1096
|
+
radius
|
|
1097
|
+
* np.sqrt(2)
|
|
1098
|
+
* (
|
|
1099
|
+
(0.5 + 0.25 * np.sqrt(2)) * np.cos(k)
|
|
1100
|
+
+ (0.25 * np.sqrt(2) - 0.5) * np.sin(k)
|
|
1101
|
+
)
|
|
1102
|
+
)
|
|
1103
|
+
y = (
|
|
1104
|
+
radius
|
|
1105
|
+
* np.sqrt(2)
|
|
1106
|
+
* (
|
|
1107
|
+
(0.5 + 0.25 * np.sqrt(2)) * np.sin(k)
|
|
1108
|
+
+ (0.25 * np.sqrt(2) - 0.5) * np.cos(k)
|
|
1109
|
+
)
|
|
1110
|
+
)
|
|
1111
|
+
z = radius * (0.5 * np.cos(k) + 0.5 * np.sin(k))
|
|
1112
|
+
|
|
1113
|
+
p = 0
|
|
1114
|
+
k = np.linspace(p, np.pi / 2 - p, C.size)
|
|
1115
|
+
x = radius * np.cos(k)
|
|
1116
|
+
y = radius * np.sin(k)
|
|
1117
|
+
z = radius / np.sqrt(2) * (np.cos(k) + np.sin(k))
|
|
1118
|
+
|
|
1119
|
+
# x = radius*np.linspace(np.sqrt(2), 0, C.size)
|
|
1120
|
+
# y = radius*np.linspace(0, np.sqrt(2), C.size)
|
|
1121
|
+
# ctrlPts[0, :, 0, 0] = x
|
|
1122
|
+
# ctrlPts[1, :, 0, 0] = y
|
|
1123
|
+
ctrlPts[2, :, 0, 0] = z
|
|
1124
|
+
# ctrlPts[0, :, 0, -1] = x
|
|
1125
|
+
# ctrlPts[1, :, 0, -1] = y
|
|
1126
|
+
ctrlPts[2, :, 0, -1] = length - z
|
|
1127
|
+
|
|
1128
|
+
scale_vector = np.array([1, 1, 1], dtype="float")
|
|
1129
|
+
|
|
1130
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
1131
|
+
orientation = np.array(orientation, dtype="float")
|
|
1132
|
+
orientation /= np.linalg.norm(orientation)
|
|
1133
|
+
axis = np.cross(e_z, orientation)
|
|
1134
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
1135
|
+
|
|
1136
|
+
translation_vector = np.array(center_front, dtype="float")
|
|
1137
|
+
|
|
1138
|
+
ctrlPts = scale_rotate_translate(
|
|
1139
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
return spline, ctrlPts
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
# %% cube
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
def new_cube(center, orientation, side_length):
|
|
1149
|
+
"""
|
|
1150
|
+
Creates a B-spline cube from a given center, orientation and side length.
|
|
1151
|
+
|
|
1152
|
+
Parameters
|
|
1153
|
+
----------
|
|
1154
|
+
center : array_like
|
|
1155
|
+
The center of the cube.
|
|
1156
|
+
orientation : array_like
|
|
1157
|
+
The normal vector of the cube.
|
|
1158
|
+
side_length : float
|
|
1159
|
+
The side length of the cube.
|
|
1160
|
+
|
|
1161
|
+
Returns
|
|
1162
|
+
-------
|
|
1163
|
+
cube : BSpline
|
|
1164
|
+
The cube.
|
|
1165
|
+
ctrlPts : array_like
|
|
1166
|
+
The control points of the cube.
|
|
1167
|
+
"""
|
|
1168
|
+
degrees = np.array([1] * 3, dtype="int")
|
|
1169
|
+
knots = [np.array([0, 0, 1, 1], dtype="float")] * 3
|
|
1170
|
+
# Create a 3D grid of control points
|
|
1171
|
+
ctrlPts = np.array(np.meshgrid(*([np.array([-0.5, 0.5])] * 3), indexing="ij"))
|
|
1172
|
+
|
|
1173
|
+
spline = BSpline(degrees, knots)
|
|
1174
|
+
|
|
1175
|
+
# Scale the control points by the side length
|
|
1176
|
+
scale_vector = np.array([side_length] * 3, dtype="float")
|
|
1177
|
+
|
|
1178
|
+
# Rotate the control points by the angle between the normal vector and the up vector
|
|
1179
|
+
e_z = np.array([0, 0, 1], dtype="float")
|
|
1180
|
+
orientation = np.array(orientation, dtype="float")
|
|
1181
|
+
orientation /= np.linalg.norm(orientation)
|
|
1182
|
+
axis = np.cross(e_z, orientation)
|
|
1183
|
+
angle = np.arccos(np.dot(e_z, orientation))
|
|
1184
|
+
|
|
1185
|
+
# Translate the control points by the center vector
|
|
1186
|
+
translation_vector = np.array(center, dtype="float")
|
|
1187
|
+
|
|
1188
|
+
# Apply the transformations to the control points
|
|
1189
|
+
ctrlPts = scale_rotate_translate(
|
|
1190
|
+
ctrlPts, scale_vector, axis, angle, translation_vector
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
return spline, ctrlPts
|