geomdl 5.4.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.
geomdl/BSpline.py ADDED
@@ -0,0 +1,1017 @@
1
+ """
2
+ .. module:: BSpline
3
+ :platform: Unix, Windows
4
+ :synopsis: Provides data storage and evaluation functionality for non-rational spline geometries
5
+
6
+ .. moduleauthor:: Onur R. Bingol <contact@onurbingol.net>
7
+
8
+ """
9
+
10
+ import pickle
11
+ from . import abstract, evaluators, operations, tessellate, utilities
12
+ from . import _utilities as utl
13
+ from .exceptions import GeomdlException
14
+
15
+
16
+ @utl.export
17
+ class Curve(abstract.Curve):
18
+ """Data storage and evaluation class for n-variate B-spline (non-rational) curves.
19
+
20
+ This class provides the following properties:
21
+
22
+ * :py:attr:`type` = spline
23
+ * :py:attr:`id`
24
+ * :py:attr:`order`
25
+ * :py:attr:`degree`
26
+ * :py:attr:`knotvector`
27
+ * :py:attr:`ctrlpts`
28
+ * :py:attr:`delta`
29
+ * :py:attr:`sample_size`
30
+ * :py:attr:`bbox`
31
+ * :py:attr:`vis`
32
+ * :py:attr:`name`
33
+ * :py:attr:`dimension`
34
+ * :py:attr:`evaluator`
35
+ * :py:attr:`rational`
36
+
37
+ The following code segment illustrates the usage of Curve class:
38
+
39
+ .. code-block:: python
40
+
41
+ from geomdl import BSpline
42
+
43
+ # Create a 3-dimensional B-spline Curve
44
+ curve = BSpline.Curve()
45
+
46
+ # Set degree
47
+ curve.degree = 3
48
+
49
+ # Set control points
50
+ curve.ctrlpts = [[10, 5, 10], [10, 20, -30], [40, 10, 25], [-10, 5, 0]]
51
+
52
+ # Set knot vector
53
+ curve.knotvector = [0, 0, 0, 0, 1, 1, 1, 1]
54
+
55
+ # Set evaluation delta (controls the number of curve points)
56
+ curve.delta = 0.05
57
+
58
+ # Get curve points (the curve will be automatically evaluated)
59
+ curve_points = curve.evalpts
60
+
61
+ **Keyword Arguments:**
62
+
63
+ * ``precision``: number of decimal places to round to. *Default: 18*
64
+ * ``normalize_kv``: activates knot vector normalization. *Default: True*
65
+ * ``find_span_func``: sets knot span search implementation. *Default:* :func:`.helpers.find_span_linear`
66
+ * ``insert_knot_func``: sets knot insertion implementation. *Default:* :func:`.operations.insert_knot`
67
+ * ``remove_knot_func``: sets knot removal implementation. *Default:* :func:`.operations.remove_knot`
68
+
69
+ Please refer to the :py:class:`.abstract.Curve()` documentation for more details.
70
+ """
71
+
72
+ # __slots__ = ('_insert_knot_func', '_remove_knot_func')
73
+
74
+ def __init__(self, **kwargs):
75
+ super(Curve, self).__init__(**kwargs)
76
+ self._evaluator = evaluators.CurveEvaluator(find_span_func=self._span_func)
77
+ self._insert_knot_func = kwargs.get("insert_knot_func", operations.insert_knot)
78
+ self._remove_knot_func = kwargs.get("remove_knot_func", operations.remove_knot)
79
+
80
+ def save(self, file_name):
81
+ """Saves the curve as a pickled file.
82
+
83
+ .. deprecated:: 5.2.4
84
+
85
+ Use :func:`.exchange.export_json()` instead.
86
+
87
+ :param file_name: name of the file to be saved
88
+ :type file_name: str
89
+ """
90
+ return None
91
+
92
+ def load(self, file_name):
93
+ """Loads the curve from a pickled file.
94
+
95
+ .. deprecated:: 5.2.4
96
+
97
+ Use :func:`.exchange.import_json()` instead.
98
+
99
+ :param file_name: name of the file to be loaded
100
+ :type file_name: str
101
+ """
102
+ return None
103
+
104
+ def evaluate(self, **kwargs):
105
+ """Evaluates the curve.
106
+
107
+ The evaluated points are stored in :py:attr:`evalpts` property.
108
+
109
+ Keyword arguments:
110
+ * ``start``: start parameter
111
+ * ``stop``: stop parameter
112
+
113
+ The ``start`` and ``stop`` parameters allow evaluation of a curve segment in the range *[start, stop]*, i.e.
114
+ the curve will also be evaluated at the ``stop`` parameter value.
115
+
116
+ The following examples illustrate the usage of the keyword arguments.
117
+
118
+ .. code-block:: python
119
+
120
+ # Start evaluating from u=0.2 to u=1.0
121
+ curve.evaluate(start=0.2)
122
+
123
+ # Start evaluating from u=0.0 to u=0.7
124
+ curve.evaluate(stop=0.7)
125
+
126
+ # Start evaluating from u=0.1 to u=0.5
127
+ curve.evaluate(start=0.1, stop=0.5)
128
+
129
+ # Get the evaluated points
130
+ curve_points = curve.evalpts
131
+ """
132
+ # Call parent method
133
+ super(Curve, self).evaluate(**kwargs)
134
+
135
+ # Find evaluation start and stop parameter values
136
+ start = kwargs.get("start", self.knotvector[self.degree])
137
+ stop = kwargs.get("stop", self.knotvector[-(self.degree + 1)])
138
+
139
+ # Check parameters
140
+ if self._kv_normalize:
141
+ if not utilities.check_params([start, stop]):
142
+ raise GeomdlException("Parameters should be between 0 and 1")
143
+
144
+ # Clean up the curve points
145
+ self.reset(evalpts=True)
146
+
147
+ # Evaluate and cache
148
+ self._eval_points = self._evaluator.evaluate(self.data, start=start, stop=stop)
149
+
150
+ def evaluate_single(self, param):
151
+ """Evaluates the curve at the input parameter.
152
+
153
+ :param param: parameter
154
+ :type param: float
155
+ :return: evaluated surface point at the given parameter
156
+ :rtype: list
157
+ """
158
+ # Call parent method
159
+ super(Curve, self).evaluate_single(param)
160
+
161
+ # Check parameters
162
+ if self._kv_normalize:
163
+ if not utilities.check_params([param]):
164
+ raise GeomdlException("Parameters should be between 0 and 1")
165
+
166
+ # Evaluate the curve point
167
+ pt = self._evaluator.evaluate(self.data, start=param, stop=param)
168
+
169
+ return pt[0]
170
+
171
+ def evaluate_list(self, param_list):
172
+ """Evaluates the curve for an input range of parameters.
173
+
174
+ :param param_list: list of parameters
175
+ :type param_list: list, tuple
176
+ :return: evaluated surface points at the input parameters
177
+ :rtype: list
178
+ """
179
+ # Call parent method
180
+ super(Curve, self).evaluate_list(param_list)
181
+
182
+ # Evaluate parameter list
183
+ res = []
184
+ for prm in param_list:
185
+ if self._kv_normalize:
186
+ if utilities.check_params([prm]):
187
+ res.append(self.evaluate_single(prm))
188
+ else:
189
+ res.append(self.evaluate_single(prm))
190
+ return res
191
+
192
+ def derivatives(self, u, order=0, **kwargs):
193
+ """Evaluates n-th order curve derivatives at the given parameter value.
194
+
195
+ The output of this method is list of n-th order derivatives. If ``order`` is ``0``, then it will only output
196
+ the evaluated point. Similarly, if ``order`` is ``2``, then it will output the evaluated point, 1st derivative
197
+ and the 2nd derivative. For instance;
198
+
199
+ .. code-block:: python
200
+
201
+ # Assuming a curve (crv) is defined on a parametric domain [0.0, 1.0]
202
+ # Let's take the curve derivative at the parametric position u = 0.35
203
+ ders = crv.derivatives(u=0.35, order=2)
204
+ ders[0] # evaluated point, equal to crv.evaluate_single(0.35)
205
+ ders[1] # 1st derivative at u = 0.35
206
+ ders[2] @ 2nd derivative at u = 0.35
207
+
208
+ :param u: parameter value
209
+ :type u: float
210
+ :param order: derivative order
211
+ :type order: int
212
+ :return: a list containing up to {order}-th derivative of the curve
213
+ :rtype: list
214
+ """
215
+ # Call parent method
216
+ super(Curve, self).derivatives(u=u, order=order, **kwargs)
217
+
218
+ # Evaluate and return the derivative at knot u
219
+ return self._evaluator.derivatives(self.data, parpos=u, deriv_order=order)
220
+
221
+ def insert_knot(self, param, **kwargs):
222
+ """Inserts the knot and updates the control points array and the knot vector.
223
+
224
+ Keyword Arguments:
225
+ * ``num``: Number of knot insertions. *Default: 1*
226
+
227
+ :param param: knot to be inserted
228
+ :type param: float
229
+ """
230
+ # Check if all required parameters are set before the evaluation
231
+ self._check_variables()
232
+
233
+ # Check parameters are correct
234
+ if self._kv_normalize:
235
+ if not utilities.check_params([param]):
236
+ raise GeomdlException("Parameters should be between 0 and 1")
237
+
238
+ # Get keyword arguments
239
+ num = kwargs.get("num", 1) # number of knot insertions
240
+ check_num = kwargs.get("check_r", True) # can be set to False when the caller checks number of insertions
241
+
242
+ # Insert knot
243
+ try:
244
+ self._insert_knot_func(self, [param], [num], check_num=check_num)
245
+ except GeomdlException as e:
246
+ print(e)
247
+ return
248
+
249
+ # Evaluate curve again if it has already been evaluated before knot insertion
250
+ if check_num and self._eval_points:
251
+ self.evaluate()
252
+
253
+ def remove_knot(self, param, **kwargs):
254
+ """Removes the knot and updates the control points array and the knot vector.
255
+
256
+ Keyword Arguments:
257
+ * ``num``: Number of knot removals. *Default: 1*
258
+
259
+ :param param: knot to be removed
260
+ :type param: float
261
+ """
262
+ # Check if all required parameters are set before the evaluation
263
+ self._check_variables()
264
+
265
+ # Check param parameters are correct
266
+ if self._kv_normalize:
267
+ if not utilities.check_params([param]):
268
+ raise GeomdlException("Parameters should be between 0 and 1")
269
+
270
+ # Get keyword arguments
271
+ num = kwargs.get("num", 1) # number of knot removals
272
+ check_num = kwargs.get("check_r", True) # can be set to False when the caller checks number of removals
273
+
274
+ # Remove knot
275
+ try:
276
+ self._remove_knot_func(self, [param], [num], check_num=check_num)
277
+ except GeomdlException as e:
278
+ print(e)
279
+ return
280
+
281
+ # Evaluate curve again if it has already been evaluated before knot removal
282
+ if check_num and self._eval_points:
283
+ self.evaluate()
284
+
285
+ def tangent(self, parpos, **kwargs):
286
+ """Evaluates the tangent vector of the curve at the given parametric position(s).
287
+
288
+ .. deprecated: 5.3.0
289
+
290
+ Please use :func:`operations.tangent` instead.
291
+
292
+ :param parpos: parametric position(s) where the evaluation will be executed
293
+ :type parpos: float, list or tuple
294
+ :return: tangent vector as a tuple of the origin point and the vector components
295
+ :rtype: tuple
296
+ """
297
+ return tuple()
298
+
299
+ def normal(self, parpos, **kwargs):
300
+ """Evaluates the normal to the tangent vector of the curve at the given parametric position(s).
301
+
302
+ .. deprecated: 5.3.0
303
+
304
+ :param parpos: parametric position(s) where the evaluation will be executed
305
+ :type parpos: float, list or tuple
306
+ :return: normal vector as a tuple of the origin point and the vector components
307
+ :rtype: tuple
308
+ """
309
+ return tuple()
310
+
311
+ def binormal(self, parpos, **kwargs):
312
+ """Evaluates the binormal vector of the curve at the given parametric position(s).
313
+
314
+ .. deprecated: 5.3.0
315
+
316
+ :param parpos: parametric position(s) where the evaluation will be executed
317
+ :type parpos: float, list or tuple
318
+ :return: binormal vector as a tuple of the origin point and the vector components
319
+ :rtype: tuple
320
+ """
321
+ return tuple()
322
+
323
+
324
+ @utl.export
325
+ class Surface(abstract.Surface):
326
+ """Data storage and evaluation class for B-spline (non-rational) surfaces.
327
+
328
+ This class provides the following properties:
329
+
330
+ * :py:attr:`type` = spline
331
+ * :py:attr:`id`
332
+ * :py:attr:`order_u`
333
+ * :py:attr:`order_v`
334
+ * :py:attr:`degree_u`
335
+ * :py:attr:`degree_v`
336
+ * :py:attr:`knotvector_u`
337
+ * :py:attr:`knotvector_v`
338
+ * :py:attr:`ctrlpts`
339
+ * :py:attr:`ctrlpts_size_u`
340
+ * :py:attr:`ctrlpts_size_v`
341
+ * :py:attr:`ctrlpts2d`
342
+ * :py:attr:`delta`
343
+ * :py:attr:`delta_u`
344
+ * :py:attr:`delta_v`
345
+ * :py:attr:`sample_size`
346
+ * :py:attr:`sample_size_u`
347
+ * :py:attr:`sample_size_v`
348
+ * :py:attr:`bbox`
349
+ * :py:attr:`name`
350
+ * :py:attr:`dimension`
351
+ * :py:attr:`vis`
352
+ * :py:attr:`evaluator`
353
+ * :py:attr:`tessellator`
354
+ * :py:attr:`rational`
355
+ * :py:attr:`trims`
356
+
357
+ The following code segment illustrates the usage of Surface class:
358
+
359
+ .. code-block:: python
360
+ :linenos:
361
+
362
+ from geomdl import BSpline
363
+
364
+ # Create a BSpline surface instance (Bezier surface)
365
+ surf = BSpline.Surface()
366
+
367
+ # Set degrees
368
+ surf.degree_u = 3
369
+ surf.degree_v = 2
370
+
371
+ # Set control points
372
+ control_points = [[0, 0, 0], [0, 4, 0], [0, 8, -3],
373
+ [2, 0, 6], [2, 4, 0], [2, 8, 0],
374
+ [4, 0, 0], [4, 4, 0], [4, 8, 3],
375
+ [6, 0, 0], [6, 4, -3], [6, 8, 0]]
376
+ surf.set_ctrlpts(control_points, 4, 3)
377
+
378
+ # Set knot vectors
379
+ surf.knotvector_u = [0, 0, 0, 0, 1, 1, 1, 1]
380
+ surf.knotvector_v = [0, 0, 0, 1, 1, 1]
381
+
382
+ # Set evaluation delta (control the number of surface points)
383
+ surf.delta = 0.05
384
+
385
+ # Get surface points (the surface will be automatically evaluated)
386
+ surface_points = surf.evalpts
387
+
388
+ **Keyword Arguments:**
389
+
390
+ * ``precision``: number of decimal places to round to. *Default: 18*
391
+ * ``normalize_kv``: activates knot vector normalization. *Default: True*
392
+ * ``find_span_func``: sets knot span search implementation. *Default:* :func:`.helpers.find_span_linear`
393
+ * ``insert_knot_func``: sets knot insertion implementation. *Default:* :func:`.operations.insert_knot`
394
+ * ``remove_knot_func``: sets knot removal implementation. *Default:* :func:`.operations.remove_knot`
395
+
396
+ Please refer to the :py:class:`.abstract.Surface()` documentation for more details.
397
+ """
398
+
399
+ # __slots__ = ('_insert_knot_func', '_remove_knot_func', '_control_points2D')
400
+
401
+ def __init__(self, **kwargs):
402
+ super(Surface, self).__init__(**kwargs)
403
+ self._evaluator = evaluators.SurfaceEvaluator(find_span_func=self._span_func)
404
+ self._tsl_component = tessellate.TriangularTessellate()
405
+ self._control_points2D = self._init_array() # control points, 2-D array [u][v]
406
+ self._insert_knot_func = kwargs.get("insert_knot_func", operations.insert_knot)
407
+ self._remove_knot_func = kwargs.get("remove_knot_func", operations.remove_knot)
408
+
409
+ @property
410
+ def ctrlpts2d(self):
411
+ """2-dimensional array of control points.
412
+
413
+ The getter returns a tuple of 2D control points (weighted control points + weights if NURBS) in *[u][v]* format.
414
+ The rows of the returned tuple correspond to v-direction and the columns correspond to u-direction.
415
+
416
+ The following example can be used to traverse 2D control points:
417
+
418
+ .. code-block:: python
419
+ :linenos:
420
+
421
+ # Create a BSpline surface
422
+ surf_bs = BSpline.Surface()
423
+
424
+ # Do degree, control points and knot vector assignments here
425
+
426
+ # Each u includes a row of v values
427
+ for u in surf_bs.ctrlpts2d:
428
+ # Each row contains the coordinates of the control points
429
+ for v in u:
430
+ print(str(v)) # will be something like (1.0, 2.0, 3.0)
431
+
432
+ # Create a NURBS surface
433
+ surf_nb = NURBS.Surface()
434
+
435
+ # Do degree, weighted control points and knot vector assignments here
436
+
437
+ # Each u includes a row of v values
438
+ for u in surf_nb.ctrlpts2d:
439
+ # Each row contains the coordinates of the weighted control points
440
+ for v in u:
441
+ print(str(v)) # will be something like (0.5, 1.0, 1.5, 0.5)
442
+
443
+
444
+ When using **NURBS.Surface** class, the output of :py:attr:`~ctrlpts2d` property could be confusing since,
445
+ :py:attr:`~ctrlpts` always returns the unweighted control points, i.e. :py:attr:`~ctrlpts` property returns 3D
446
+ control points all divided by the weights and you can use :py:attr:`~weights` property to access the weights
447
+ vector, but :py:attr:`~ctrlpts2d` returns the weighted ones plus weights as the last element.
448
+ This difference is intentionally added for compatibility and interoperability purposes.
449
+
450
+ To explain this situation in a simple way;
451
+
452
+ * If you need the weighted control points directly, use :py:attr:`~ctrlpts2d`
453
+ * If you need the control points and the weights separately, use :py:attr:`~ctrlpts` and :py:attr:`~weights`
454
+
455
+ .. note::
456
+
457
+ Please note that the setter doesn't check for inconsistencies and using the setter is not recommended.
458
+ Instead of the setter property, please use :func:`.set_ctrlpts()` function.
459
+
460
+ Please refer to the `wiki <https://github.com/orbingol/NURBS-Python/wiki/Using-Python-Properties>`_ for details
461
+ on using this class member.
462
+
463
+ :getter: Gets the control points as a 2-dimensional array in [u][v] format
464
+ :setter: Sets the control points as a 2-dimensional array in [u][v] format
465
+ :type: list
466
+ """
467
+ return self._control_points2D
468
+
469
+ @ctrlpts2d.setter
470
+ def ctrlpts2d(self, value):
471
+ if not isinstance(value, (list, tuple)):
472
+ raise ValueError("The input must be a list or tuple")
473
+
474
+ # Clean up the surface and control points
475
+ self.reset(evalpts=True, ctrlpts=True)
476
+
477
+ # Assume that the user has prepared the lists correctly
478
+ size_u = len(value)
479
+ size_v = len(value[0])
480
+
481
+ # Estimate dimension by checking the size of the first element
482
+ self._dimension = len(value[0][0])
483
+
484
+ # Make sure that all numbers are float type
485
+ ctrlpts = [[] for _ in range(size_u * size_v)]
486
+ for u in range(size_u):
487
+ for v in range(size_v):
488
+ idx = v + (size_v * u)
489
+ ctrlpts[idx] = [float(coord) for coord in value[u][v]]
490
+
491
+ # Set control points
492
+ self.set_ctrlpts(ctrlpts, size_u, size_v)
493
+
494
+ def set_ctrlpts(self, ctrlpts, *args, **kwargs):
495
+ """Sets the control points and checks if the data is consistent.
496
+
497
+ This method is designed to provide a consistent way to set control points whether they are weighted or not.
498
+ It directly sets the control points member of the class, and therefore it doesn't return any values.
499
+ The input will be an array of coordinates. If you are working in the 3-dimensional space, then your coordinates
500
+ will be an array of 3 elements representing *(x, y, z)* coordinates.
501
+
502
+ This method also generates 2D control points in *[u][v]* format which can be accessed via :py:attr:`~ctrlpts2d`.
503
+
504
+ .. note::
505
+
506
+ The v index varies first. That is, a row of v control points for the first u value is found first.
507
+ Then, the row of v control points for the next u value.
508
+
509
+ :param ctrlpts: input control points as a list of coordinates
510
+ :type ctrlpts: list
511
+ """
512
+ # Call parent function
513
+ super(Surface, self).set_ctrlpts(ctrlpts, *args, **kwargs)
514
+
515
+ # Generate a 2-dimensional list of control points
516
+ array_init2d = kwargs.get("array_init2d", [[[] for _ in range(args[1])] for _ in range(args[0])])
517
+ ctrlpts_float2d = array_init2d
518
+ for i in range(0, self.ctrlpts_size_u):
519
+ for j in range(0, self.ctrlpts_size_v):
520
+ ctrlpts_float2d[i][j] = self._control_points[j + (i * self.ctrlpts_size_v)]
521
+
522
+ # Set the new 2-dimension control points
523
+ self._control_points2D = ctrlpts_float2d
524
+
525
+ def reset(self, **kwargs):
526
+ """Resets control points and/or evaluated points.
527
+
528
+ Keyword Arguments:
529
+ * ``evalpts``: if True, then resets evaluated points
530
+ * ``ctrlpts`` if True, then resets control points
531
+
532
+ """
533
+ # Call parent function
534
+ super(Surface, self).reset(**kwargs)
535
+
536
+ # Reset ctrlpts2d
537
+ reset_ctrlpts = kwargs.get("ctrlpts", False)
538
+ if reset_ctrlpts:
539
+ self._control_points2D = self._init_array()
540
+
541
+ def save(self, file_name):
542
+ """Saves the surface as a pickled file.
543
+
544
+ .. deprecated:: 5.2.4
545
+
546
+ Use :func:`.exchange.export_json()` instead.
547
+
548
+ :param file_name: name of the file to be saved
549
+ :type file_name: str
550
+ """
551
+ return None
552
+
553
+ def load(self, file_name):
554
+ """Loads the surface from a pickled file.
555
+
556
+ .. deprecated:: 5.2.4
557
+
558
+ Use :func:`.exchange.import_json()` instead.
559
+
560
+ :param file_name: name of the file to be loaded
561
+ :type file_name: str
562
+ """
563
+ return None
564
+
565
+ def transpose(self):
566
+ """Transposes the surface by swapping u and v parametric directions."""
567
+ operations.transpose(self, inplace=True)
568
+ self.reset(evalpts=True)
569
+
570
+ def evaluate(self, **kwargs):
571
+ """Evaluates the surface.
572
+
573
+ The evaluated points are stored in :py:attr:`evalpts` property.
574
+
575
+ Keyword arguments:
576
+ * ``start_u``: start parameter on the u-direction
577
+ * ``stop_u``: stop parameter on the u-direction
578
+ * ``start_v``: start parameter on the v-direction
579
+ * ``stop_v``: stop parameter on the v-direction
580
+
581
+ The ``start_u``, ``start_v`` and ``stop_u`` and ``stop_v`` parameters allow evaluation of a surface segment
582
+ in the range *[start_u, stop_u][start_v, stop_v]* i.e. the surface will also be evaluated at the ``stop_u``
583
+ and ``stop_v`` parameter values.
584
+
585
+ The following examples illustrate the usage of the keyword arguments.
586
+
587
+ .. code-block:: python
588
+ :linenos:
589
+
590
+ # Start evaluating in range u=[0, 0.7] and v=[0.1, 1]
591
+ surf.evaluate(stop_u=0.7, start_v=0.1)
592
+
593
+ # Start evaluating in range u=[0, 1] and v=[0.1, 0.3]
594
+ surf.evaluate(start_v=0.1, stop_v=0.3)
595
+
596
+ # Get the evaluated points
597
+ surface_points = surf.evalpts
598
+
599
+ """
600
+ # Call parent method
601
+ super(Surface, self).evaluate(**kwargs)
602
+
603
+ # Find evaluation start and stop parameter values
604
+ start_u = kwargs.get("start_u", self.knotvector_u[self.degree_u])
605
+ stop_u = kwargs.get("stop_u", self.knotvector_u[-(self.degree_u + 1)])
606
+ start_v = kwargs.get("start_v", self.knotvector_v[self.degree_v])
607
+ stop_v = kwargs.get("stop_v", self.knotvector_v[-(self.degree_v + 1)])
608
+
609
+ # Check parameters
610
+ if self._kv_normalize:
611
+ if not utilities.check_params([start_u, stop_u, start_v, stop_v]):
612
+ raise GeomdlException("Parameters should be between 0 and 1")
613
+
614
+ # Clean up the surface points
615
+ self.reset(evalpts=True)
616
+
617
+ # Evaluate and cache
618
+ self._eval_points = self._evaluator.evaluate(self.data, start=(start_u, start_v), stop=(stop_u, stop_v))
619
+
620
+ def evaluate_single(self, param):
621
+ """Evaluates the surface at the input (u, v) parameter pair.
622
+
623
+ :param param: parameter pair (u, v)
624
+ :type param: list, tuple
625
+ :return: evaluated surface point at the given parameter pair
626
+ :rtype: list
627
+ """
628
+ # Call parent method
629
+ super(Surface, self).evaluate_single(param)
630
+
631
+ # Evaluate the surface point
632
+ pt = self._evaluator.evaluate(self.data, start=param, stop=param)
633
+
634
+ return pt[0]
635
+
636
+ def evaluate_list(self, param_list):
637
+ """Evaluates the surface for a given list of (u, v) parameters.
638
+
639
+ :param param_list: list of parameter pairs (u, v)
640
+ :type param_list: list, tuple
641
+ :return: evaluated surface point at the input parameter pairs
642
+ :rtype: tuple
643
+ """
644
+ # Call parent method
645
+ super(Surface, self).evaluate_list(param_list)
646
+
647
+ # Evaluate (u,v) list
648
+ res = []
649
+ for prm in param_list:
650
+ if self._kv_normalize:
651
+ if utilities.check_params(prm):
652
+ res.append(self.evaluate_single(prm))
653
+ else:
654
+ res.append(self.evaluate_single(prm))
655
+ return res
656
+
657
+ # Evaluates n-th order surface derivatives at the given (u,v) parameter
658
+ def derivatives(self, u, v, order=0, **kwargs):
659
+ """Evaluates n-th order surface derivatives at the given (u, v) parameter pair.
660
+
661
+ * SKL[0][0] will be the surface point itself
662
+ * SKL[0][1] will be the 1st derivative w.r.t. v
663
+ * SKL[2][1] will be the 2nd derivative w.r.t. u and 1st derivative w.r.t. v
664
+
665
+ :param u: parameter on the u-direction
666
+ :type u: float
667
+ :param v: parameter on the v-direction
668
+ :type v: float
669
+ :param order: derivative order
670
+ :type order: integer
671
+ :return: A list SKL, where SKL[k][l] is the derivative of the surface S(u,v) w.r.t. u k times and v l times
672
+ :rtype: list
673
+ """
674
+ # Call parent method
675
+ super(Surface, self).derivatives(u=u, v=v, order=order, **kwargs)
676
+
677
+ # Evaluate and return the derivatives
678
+ return self._evaluator.derivatives(self.data, parpos=(u, v), deriv_order=order)
679
+
680
+ def insert_knot(self, u=None, v=None, **kwargs):
681
+ """Inserts knot(s) on the u- or v-directions
682
+
683
+ Keyword Arguments:
684
+ * ``num_u``: Number of knot insertions on the u-direction. *Default: 1*
685
+ * ``num_v``: Number of knot insertions on the v-direction. *Default: 1*
686
+
687
+ :param u: knot to be inserted on the u-direction
688
+ :type u: float
689
+ :param v: knot to be inserted on the v-direction
690
+ :type v: float
691
+ """
692
+ # Check all parameters are set before the evaluation
693
+ self._check_variables()
694
+
695
+ # Check if the parameter values are correctly defined
696
+ if self._kv_normalize:
697
+ if not utilities.check_params([u, v]):
698
+ raise GeomdlException("Parameters should be between 0 and 1")
699
+
700
+ # Get keyword arguments
701
+ num_u = kwargs.get("num_u", 1) # number of knot insertions on the u-direction
702
+ num_v = kwargs.get("num_v", 1) # number of knot insertions on the v-direction
703
+ check_num = kwargs.get("check_r", True) # Enables/disables number of knot insertions checking
704
+
705
+ # Insert knots
706
+ try:
707
+ self._insert_knot_func(self, [u, v], [num_u, num_v], check_num=check_num)
708
+ except GeomdlException as e:
709
+ print(e)
710
+ return
711
+
712
+ # Evaluate surface again if it has already been evaluated before knot insertion
713
+ if check_num and self._eval_points:
714
+ self.evaluate()
715
+
716
+ def remove_knot(self, u=None, v=None, **kwargs):
717
+ """Inserts knot(s) on the u- or v-directions
718
+
719
+ Keyword Arguments:
720
+ * ``num_u``: Number of knot removals on the u-direction. *Default: 1*
721
+ * ``num_v``: Number of knot removals on the v-direction. *Default: 1*
722
+
723
+ :param u: knot to be removed on the u-direction
724
+ :type u: float
725
+ :param v: knot to be removed on the v-direction
726
+ :type v: float
727
+ """
728
+ # Check all parameters are set before the evaluation
729
+ self._check_variables()
730
+
731
+ # Check if the parameter values are correctly defined
732
+ if self._kv_normalize:
733
+ if not utilities.check_params([u, v]):
734
+ raise GeomdlException("Parameters should be between 0 and 1")
735
+
736
+ # Get keyword arguments
737
+ num_u = kwargs.get("num_u", 1) # number of knot removals on the u-direction
738
+ num_v = kwargs.get("num_v", 1) # number of knot removals on the v-direction
739
+ check_num = kwargs.get("check_r", True) # can be set to False when the caller checks number of removals
740
+
741
+ # Remove knots
742
+ try:
743
+ self._remove_knot_func(self, [u, v], [num_u, num_v], check_num=check_num)
744
+ except GeomdlException as e:
745
+ print(e)
746
+ return
747
+
748
+ # Evaluate curve again if it has already been evaluated before knot removal
749
+ if check_num and self._eval_points:
750
+ self.evaluate()
751
+
752
+ def tangent(self, parpos, **kwargs):
753
+ """Evaluates the tangent vectors of the surface at the given parametric position(s).
754
+
755
+ .. deprecated: 5.3.0
756
+
757
+ Please use :func:`operations.tangent` instead.
758
+
759
+ :param parpos: parametric position(s) where the evaluation will be executed
760
+ :type parpos: list or tuple
761
+ :return: an array containing "point" and "vector"s on u- and v-directions, respectively
762
+ :rtype: tuple
763
+ """
764
+ return tuple()
765
+
766
+ def normal(self, parpos, **kwargs):
767
+ """Evaluates the normal vector of the surface at the given parametric position(s).
768
+
769
+ .. deprecated: 5.3.0
770
+
771
+ Please use :func:`operations.normal` instead.
772
+
773
+ :param parpos: parametric position(s) where the evaluation will be executed
774
+ :type parpos: list or tuple
775
+ :return: an array containing "point" and "vector" pairs
776
+ :rtype: tuple
777
+ """
778
+ return tuple()
779
+
780
+
781
+ @utl.export
782
+ class Volume(abstract.Volume):
783
+ """Data storage and evaluation class for B-spline (non-rational) volumes.
784
+
785
+ This class provides the following properties:
786
+
787
+ * :py:attr:`type` = spline
788
+ * :py:attr:`id`
789
+ * :py:attr:`order_u`
790
+ * :py:attr:`order_v`
791
+ * :py:attr:`order_w`
792
+ * :py:attr:`degree_u`
793
+ * :py:attr:`degree_v`
794
+ * :py:attr:`degree_w`
795
+ * :py:attr:`knotvector_u`
796
+ * :py:attr:`knotvector_v`
797
+ * :py:attr:`knotvector_w`
798
+ * :py:attr:`ctrlpts`
799
+ * :py:attr:`ctrlpts_size_u`
800
+ * :py:attr:`ctrlpts_size_v`
801
+ * :py:attr:`ctrlpts_size_w`
802
+ * :py:attr:`delta`
803
+ * :py:attr:`delta_u`
804
+ * :py:attr:`delta_v`
805
+ * :py:attr:`delta_w`
806
+ * :py:attr:`sample_size`
807
+ * :py:attr:`sample_size_u`
808
+ * :py:attr:`sample_size_v`
809
+ * :py:attr:`sample_size_w`
810
+ * :py:attr:`bbox`
811
+ * :py:attr:`name`
812
+ * :py:attr:`dimension`
813
+ * :py:attr:`vis`
814
+ * :py:attr:`evaluator`
815
+ * :py:attr:`rational`
816
+
817
+ **Keyword Arguments:**
818
+
819
+ * ``precision``: number of decimal places to round to. *Default: 18*
820
+ * ``normalize_kv``: activates knot vector normalization. *Default: True*
821
+ * ``find_span_func``: sets knot span search implementation. *Default:* :func:`.helpers.find_span_linear`
822
+ * ``insert_knot_func``: sets knot insertion implementation. *Default:* :func:`.operations.insert_knot`
823
+ * ``remove_knot_func``: sets knot removal implementation. *Default:* :func:`.operations.remove_knot`
824
+
825
+ Please refer to the :py:class:`.abstract.Volume()` documentation for more details.
826
+ """
827
+
828
+ # __slots__ = ('_insert_knot_func', '_remove_knot_func')
829
+
830
+ def __init__(self, **kwargs):
831
+ super(Volume, self).__init__(**kwargs)
832
+ self._evaluator = evaluators.VolumeEvaluator(find_span_func=self._span_func)
833
+ self._insert_knot_func = kwargs.get("insert_knot_func", operations.insert_knot)
834
+ self._remove_knot_func = kwargs.get("remove_knot_func", operations.remove_knot)
835
+
836
+ def save(self, file_name):
837
+ """Saves the volume as a pickled file.
838
+
839
+ .. deprecated:: 5.2.4
840
+
841
+ Use :func:`.exchange.export_json()` instead.
842
+
843
+ :param file_name: name of the file to be saved
844
+ :type file_name: str
845
+ """
846
+ return None
847
+
848
+ def load(self, file_name):
849
+ """Loads the volume from a pickled file.
850
+
851
+ .. deprecated:: 5.2.4
852
+
853
+ Use :func:`.exchange.import_json()` instead.
854
+
855
+ :param file_name: name of the file to be loaded
856
+ :type file_name: str
857
+ """
858
+ return None
859
+
860
+ def evaluate(self, **kwargs):
861
+ """Evaluates the volume.
862
+
863
+ The evaluated points are stored in :py:attr:`evalpts` property.
864
+
865
+ Keyword arguments:
866
+ * ``start_u``: start parameter on the u-direction
867
+ * ``stop_u``: stop parameter on the u-direction
868
+ * ``start_v``: start parameter on the v-direction
869
+ * ``stop_v``: stop parameter on the v-direction
870
+ * ``start_w``: start parameter on the w-direction
871
+ * ``stop_w``: stop parameter on the w-direction
872
+
873
+ """
874
+ # Call parent method
875
+ super(Volume, self).evaluate(**kwargs)
876
+
877
+ # Find evaluation start and stop parameter values
878
+ start_u = kwargs.get("start_u", self.knotvector_u[self.degree_u])
879
+ stop_u = kwargs.get("stop_u", self.knotvector_u[-(self.degree_u + 1)])
880
+ start_v = kwargs.get("start_v", self.knotvector_v[self.degree_v])
881
+ stop_v = kwargs.get("stop_v", self.knotvector_v[-(self.degree_v + 1)])
882
+ start_w = kwargs.get("start_w", self.knotvector_w[self.degree_w])
883
+ stop_w = kwargs.get("stop_w", self.knotvector_w[-(self.degree_w + 1)])
884
+
885
+ # Check if all the input parameters are in the range
886
+ if self._kv_normalize:
887
+ if not utilities.check_params([start_u, stop_u, start_v, stop_v, start_w, stop_w]):
888
+ raise GeomdlException("Parameters should be between 0 and 1")
889
+
890
+ # Clean up the evaluated points
891
+ self.reset(evalpts=True)
892
+
893
+ # Evaluate and cache
894
+ self._eval_points = self._evaluator.evaluate(
895
+ self.data, start=(start_u, start_v, start_w), stop=(stop_u, stop_v, stop_w)
896
+ )
897
+
898
+ def evaluate_single(self, param):
899
+ """Evaluates the volume at the input (u, v, w) parameter.
900
+
901
+ :param param: parameter (u, v, w)
902
+ :type param: list, tuple
903
+ :return: evaluated surface point at the given parameter pair
904
+ :rtype: list
905
+ """
906
+ # Call parent method
907
+ super(Volume, self).evaluate_single(param)
908
+
909
+ # Check if all parameters are in the range
910
+ if self._kv_normalize:
911
+ if not utilities.check_params(param):
912
+ raise GeomdlException("Parameters should be between 0 and 1")
913
+
914
+ # Evaluate the volume point
915
+ pt = self._evaluator.evaluate(self.data, start=param, stop=param)
916
+ return pt[0]
917
+
918
+ def evaluate_list(self, param_list):
919
+ """Evaluates the volume for a given list of (u, v, w) parameters.
920
+
921
+ :param param_list: list of parameters in format (u, v, w)
922
+ :type param_list: list, tuple
923
+ :return: evaluated surface point at the input parameter pairs
924
+ :rtype: tuple
925
+ """
926
+ # Call parent method
927
+ super(Volume, self).evaluate_list(param_list)
928
+
929
+ # Evaluate (u, v, w) list
930
+ res = []
931
+ for prm in param_list:
932
+ if self._kv_normalize:
933
+ if utilities.check_params(prm):
934
+ res.append(self.evaluate_single(prm))
935
+ else:
936
+ res.append(self.evaluate_single(prm))
937
+ return res
938
+
939
+ def insert_knot(self, u=None, v=None, w=None, **kwargs):
940
+ """Inserts knot(s) on the u-, v- and w-directions
941
+
942
+ Keyword Arguments:
943
+ * ``num_u``: Number of knot insertions on the u-direction. *Default: 1*
944
+ * ``num_v``: Number of knot insertions on the v-direction. *Default: 1*
945
+ * ``num_w``: Number of knot insertions on the w-direction. *Default: 1*
946
+
947
+ :param u: knot to be inserted on the u-direction
948
+ :type u: float
949
+ :param v: knot to be inserted on the v-direction
950
+ :type v: float
951
+ :param w: knot to be inserted on the w-direction
952
+ :type w: float
953
+ """
954
+ # Check all parameters are set before the evaluation
955
+ self._check_variables()
956
+
957
+ # Check if the parameter values are correctly defined
958
+ if self._kv_normalize:
959
+ if not utilities.check_params([u, v, w]):
960
+ raise GeomdlException("Parameters should be between 0 and 1")
961
+
962
+ # Get keyword arguments
963
+ num_u = kwargs.get("num_u", 1) # number of knot insertions on the u-direction
964
+ num_v = kwargs.get("num_v", 1) # number of knot insertions on the v-direction
965
+ num_w = kwargs.get("num_w", 1) # number of knot insertions on the w-direction
966
+ check_num = kwargs.get("check_r", True) # Enables/disables number of knot insertions checking
967
+
968
+ # Insert knots
969
+ try:
970
+ self._insert_knot_func(self, [u, v, w], [num_u, num_v, num_w], check_num=check_num)
971
+ except GeomdlException as e:
972
+ print(e)
973
+ return
974
+
975
+ # Evaluate surface again if it has already been evaluated before knot insertion
976
+ if check_num and self._eval_points:
977
+ self.evaluate()
978
+
979
+ def remove_knot(self, u=None, v=None, w=None, **kwargs):
980
+ """Inserts knot(s) on the u-, v- and w-directions
981
+
982
+ Keyword Arguments:
983
+ * ``num_u``: Number of knot removals on the u-direction. *Default: 1*
984
+ * ``num_v``: Number of knot removals on the v-direction. *Default: 1*
985
+ * ``num_w``: Number of knot removals on the w-direction. *Default: 1*
986
+
987
+ :param u: knot to be removed on the u-direction
988
+ :type u: float
989
+ :param v: knot to be removed on the v-direction
990
+ :type v: float
991
+ :param w: knot to be removed on the w-direction
992
+ :type w: float
993
+ """
994
+ # Check all parameters are set before the evaluation
995
+ self._check_variables()
996
+
997
+ # Check if the parameter values are correctly defined
998
+ if self._kv_normalize:
999
+ if not utilities.check_params([u, v, w]):
1000
+ raise GeomdlException("Parameters should be between 0 and 1")
1001
+
1002
+ # Get keyword arguments
1003
+ num_u = kwargs.get("num_u", 1) # number of knot removals on the u-direction
1004
+ num_v = kwargs.get("num_v", 1) # number of knot removals on the v-direction
1005
+ num_w = kwargs.get("num_w", 1) # number of knot insertions on the w-direction
1006
+ check_num = kwargs.get("check_r", True) # can be set to False when the caller checks number of removals
1007
+
1008
+ # Remove knots
1009
+ try:
1010
+ self._remove_knot_func(self, [u, v, w], [num_u, num_v, num_w], check_num=check_num)
1011
+ except GeomdlException as e:
1012
+ print(e)
1013
+ return
1014
+
1015
+ # Evaluate curve again if it has already been evaluated before knot removal
1016
+ if check_num and self._eval_points:
1017
+ self.evaluate()