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.
@@ -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