Coverage for src/meshpy/mesh_creation_functions/nurbs_generic.py: 97%
186 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-28 04:21 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2018-2025 MeshPy Authors
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22"""Generic function used to create NURBS meshes within meshpy."""
24import numpy as _np
26from meshpy.core.conf import mpy as _mpy
27from meshpy.core.geometry_set import GeometryName as _GeometryName
28from meshpy.core.geometry_set import GeometrySetNodes as _GeometrySetNodes
29from meshpy.core.node import ControlPoint as _ControlPoint
30from meshpy.core.nurbs_patch import NURBSSurface as _NURBSSurface
31from meshpy.core.nurbs_patch import NURBSVolume as _NURBSVolume
34def add_geomdl_nurbs_to_mesh(
35 mesh,
36 geomdl_obj,
37 *,
38 material=None,
39 element_string=None,
40 element_description=None,
41):
42 """Generic NURBS mesh creation function.
44 Args
45 ----
46 mesh: Mesh
47 Mesh that the created NURBS geometry should be added to.
48 geomdl_obj: Geomdl object
49 NURBS geometry created using Geomdl.
50 material: Material
51 Material for this geometry.
52 element_description:
53 Information that will be written after the information of
54 the elements.
56 Return
57 ----
58 return_set: GeometryName
59 Set with the control points that form the topology of the mesh.
61 For a surface, the following information is stored:
62 Vertices: 'vertex_u_min_v_min', 'vertex_u_max_v_min', 'vertex_u_min_v_max', 'vertex_u_max_v_max'
63 Edges: 'line_v_min', 'line_u_max', 'line_v_max', 'line_u_min'
64 Surface: 'surf'
66 For a volume, the following information is stored:
67 Vertices: 'vertex_u_min_v_min_w_min', 'vertex_u_max_v_min_w_min', 'vertex_u_min_v_max_w_min', 'vertex_u_max_v_max_w_min',
68 'vertex_u_min_v_min_w_max', 'vertex_u_max_v_min_w_max', 'vertex_u_min_v_max_w_max', 'vertex_u_max_v_max_w_max'
69 Edges: 'line_v_min_w_min', 'line_u_max_w_min', 'line_v_max_w_min', 'line_u_min_w_min',
70 'line_u_min_v_min', 'line_u_max_v_min', 'line_u_min_v_max', 'line_u_max_v_max'
71 'line_v_min_w_max', 'line_u_max_w_max', 'line_v_max_w_max', 'line_u_min_w_max'
72 Surfaces: 'surf_w_min', 'surf_w_max', 'surf_v_min', 'surf_v_max', 'surf_v_max', 'surf_u_min'
73 Volume: 'vol'
74 """
76 # Make sure the material is in the mesh
77 mesh.add_material(material)
79 # Fill control points
80 control_points = []
81 nurbs_dimension = len(geomdl_obj.knotvector)
82 if nurbs_dimension == 2:
83 control_points = create_control_points_surface(geomdl_obj)
84 elif nurbs_dimension == 3:
85 control_points = create_control_points_volume(geomdl_obj)
86 else:
87 raise NotImplementedError(
88 "Error, not implemented for NURBS with dimension {}!".format(
89 nurbs_dimension
90 )
91 )
93 # Fill element
94 manifold_dim = len(geomdl_obj.knotvector)
96 if manifold_dim == 2:
97 nurbs_object = _NURBSSurface
98 elif manifold_dim == 3:
99 nurbs_object = _NURBSVolume
100 else:
101 raise NotImplementedError(
102 "Error, not implemented for a NURBS {}!".format(type(geomdl_obj))
103 )
105 element = nurbs_object(
106 geomdl_obj.knotvector,
107 geomdl_obj.degree,
108 nodes=control_points,
109 material=material,
110 element_string=element_string,
111 element_description=element_description,
112 )
114 # Add element and control points to the mesh
115 mesh.elements.append(element)
116 mesh.nodes.extend(control_points)
118 # Create geometry sets that will be returned
119 return_set = create_geometry_sets(element)
121 return return_set
124def create_control_points_surface(geomdl_obj):
125 """Creates a list with the ControlPoint objects of a surface created with
126 geomdl."""
127 control_points = []
128 for dir_v in range(geomdl_obj.ctrlpts_size_v):
129 for dir_u in range(geomdl_obj.ctrlpts_size_u):
130 weight = geomdl_obj.ctrlpts2d[dir_u][dir_v][3]
132 # As the control points are scaled with their weight, divide them to get
133 # their coordinates
134 coord = [
135 geomdl_obj.ctrlpts2d[dir_u][dir_v][0] / weight,
136 geomdl_obj.ctrlpts2d[dir_u][dir_v][1] / weight,
137 geomdl_obj.ctrlpts2d[dir_u][dir_v][2] / weight,
138 ]
140 control_points.append(_ControlPoint(coord, weight))
142 return control_points
145def create_control_points_volume(geomdl_obj):
146 """Creates a list with the ControlPoint objects of a volume created with
147 geomdl."""
148 control_points = []
149 for dir_w in range(geomdl_obj.ctrlpts_size_w):
150 for dir_v in range(geomdl_obj.ctrlpts_size_v):
151 for dir_u in range(geomdl_obj.ctrlpts_size_u):
152 # Obtain the id of the control point
153 cp_id = (
154 dir_v
155 + geomdl_obj.ctrlpts_size_v * dir_u
156 + geomdl_obj.ctrlpts_size_u * geomdl_obj.ctrlpts_size_v * dir_w
157 )
159 weight = geomdl_obj.ctrlptsw[cp_id][3]
161 # As the control points are scaled with their weight, divide them to get
162 # their coordinates
163 coord = [
164 geomdl_obj.ctrlptsw[cp_id][0] / weight,
165 geomdl_obj.ctrlptsw[cp_id][1] / weight,
166 geomdl_obj.ctrlptsw[cp_id][2] / weight,
167 ]
169 control_points.append(_ControlPoint(coord, weight))
171 return control_points
174def create_geometry_sets(element):
175 """Function that returns a GeometryName object.
177 For more information of the return item, look into
178 add_geomdl_nurbs_to_mesh.
179 """
181 def get_num_cps_uvw(knot_vectors):
182 """Obtain the number of control points on each parametric direction of
183 a patch."""
184 num_cps_uvw = _np.zeros(len(knot_vectors), dtype=int)
186 for direction in range(len(knot_vectors)):
187 knotvector_size_dir = len(element.knot_vectors[direction])
188 p_dir = element.polynomial_orders[direction]
189 cp_size_dir = knotvector_size_dir - p_dir - 1
191 num_cps_uvw[direction] = cp_size_dir
193 return num_cps_uvw
195 def get_patch_vertices(return_set, num_cps_uvw, nurbs_dimension, element):
196 """Get the control points positioned over the vertices of a patch."""
198 if nurbs_dimension == 2:
199 # Vertex 1 is positioned on u = 0, v = 0
200 return_set["vertex_u_min_v_min"] = _GeometrySetNodes(
201 _mpy.geo.point, nodes=element.nodes[0]
202 )
204 # Vertex 2 is positioned on u = 1, v = 0
205 return_set["vertex_u_max_v_min"] = _GeometrySetNodes(
206 _mpy.geo.point, nodes=element.nodes[num_cps_uvw[0] - 1]
207 )
209 # Vertex 3 is positioned on u = 0, v = 1
210 return_set["vertex_u_min_v_max"] = _GeometrySetNodes(
211 _mpy.geo.point,
212 nodes=element.nodes[num_cps_uvw[0] * (num_cps_uvw[1] - 1)],
213 )
215 # Vertex 4 is positioned on u = 1, v = 1
216 return_set["vertex_u_max_v_max"] = _GeometrySetNodes(
217 _mpy.geo.point, nodes=element.nodes[num_cps_uvw[0] * num_cps_uvw[1] - 1]
218 )
220 elif nurbs_dimension == 3:
221 # Vertex 1 is positioned on u = 0, v = 0, w =
222 return_set["vertex_u_min_v_min_w_min"] = _GeometrySetNodes(
223 _mpy.geo.point, nodes=element.nodes[0]
224 )
226 # Vertex 2 is positioned on u = 1, v = 0, w = 0
227 return_set["vertex_u_max_v_min_w_min"] = _GeometrySetNodes(
228 _mpy.geo.point, nodes=element.nodes[num_cps_uvw[0] - 1]
229 )
231 # Vertex 3 is positioned on u = 0, v = 1, w = 0
232 return_set["vertex_u_min_v_max_w_min"] = _GeometrySetNodes(
233 _mpy.geo.point,
234 nodes=element.nodes[num_cps_uvw[0] * (num_cps_uvw[1] - 1)],
235 )
237 # Vertex 4 is positioned on u = 1, v = 1, w = 0
238 return_set["vertex_u_max_v_max_w_min"] = _GeometrySetNodes(
239 _mpy.geo.point, nodes=element.nodes[num_cps_uvw[0] * num_cps_uvw[1] - 1]
240 )
242 # Vertex 5 is positioned on u = 0, v = 0, w = 1
243 return_set["vertex_u_min_v_min_w_max"] = _GeometrySetNodes(
244 _mpy.geo.point,
245 nodes=element.nodes[
246 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
247 ],
248 )
250 # Vertex 6 is positioned on u = 1, v = 0, w = 1
251 return_set["vertex_u_max_v_min_w_max"] = _GeometrySetNodes(
252 _mpy.geo.point,
253 nodes=element.nodes[
254 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
255 + (num_cps_uvw[0] - 1)
256 ],
257 )
259 # Vertex 7 is positioned on u = 0, v = 1, w = 1
260 return_set["vertex_u_min_v_max_w_max"] = _GeometrySetNodes(
261 _mpy.geo.point,
262 nodes=element.nodes[
263 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
264 + num_cps_uvw[0] * (num_cps_uvw[1] - 1)
265 ],
266 )
268 # Vertex 8 is positioned on u = 1, v = 1, w = 1
269 return_set["vertex_u_max_v_max_w_max"] = _GeometrySetNodes(
270 _mpy.geo.point,
271 nodes=element.nodes[
272 num_cps_uvw[0] * num_cps_uvw[1] * num_cps_uvw[2] - 1
273 ],
274 )
276 else:
277 raise NotImplementedError(
278 "Error, not implemented for NURBS with dimension {}!".format(
279 nurbs_dimension
280 )
281 )
283 def get_patch_lines(return_set, num_cps_uvw, nurbs_dimension, element):
284 """Get the control points positioned over the lines of a patch."""
286 if nurbs_dimension == 2:
287 name_dir = ["v", "u"]
288 name_other_dir = ["min", "min_next", "max_next", "max"]
290 for i_dir, i_other_dir in ((0, 1), (1, 0)):
291 n_cp_dir = num_cps_uvw[i_dir]
292 n_cp_other_dir = num_cps_uvw[i_other_dir]
293 factor_dir = 1 if i_dir == 0 else n_cp_other_dir
294 factor_other_dir = n_cp_dir if i_dir == 0 else 1
296 for index, i_along_other_dir in enumerate(
297 (
298 0,
299 1,
300 n_cp_other_dir - 2,
301 n_cp_other_dir - 1,
302 )
303 ):
304 cp_indices = []
305 for i_along_dir in range(n_cp_dir):
306 cp_indices.append(
307 i_along_other_dir * factor_other_dir
308 + i_along_dir * factor_dir
309 )
310 set_name = f"line_{name_dir[i_dir]}_{name_other_dir[index]}"
311 return_set[set_name] = _GeometrySetNodes(
312 _mpy.geo.line,
313 nodes=[element.nodes[i_node] for i_node in cp_indices],
314 )
316 elif nurbs_dimension == 3:
317 # Define the rest of the lines to define a volume
318 control_points_line_1 = []
319 control_points_line_2 = []
320 control_points_line_3 = []
321 control_points_line_4 = []
322 control_points_line_5 = []
323 control_points_line_6 = []
324 control_points_line_7 = []
325 control_points_line_8 = []
326 control_points_line_9 = []
327 control_points_line_10 = []
328 control_points_line_11 = []
329 control_points_line_12 = []
331 # Fill line 1, 3, 9 and 11 with their control points
332 for i in range(num_cps_uvw[0]):
333 # Line 1 has the control points on u = [0,1], v = 0, w = 0
334 cpgid_l1 = num_cps_uvw[0] * 0 + i
335 control_points_line_1.append(element.nodes[cpgid_l1])
337 # Line 3 has the control points on u = [0,1], v = 1, w = 0
338 cpgid_l3 = num_cps_uvw[0] * (num_cps_uvw[1] - 1) + i
339 control_points_line_3.append(element.nodes[cpgid_l3])
341 # Line 9 has the control points on u = [0,1], v = 0, w = 1
342 cpgid_l9 = num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1) + i
343 control_points_line_9.append(element.nodes[cpgid_l9])
345 # Line 11 has the control points on u = [0,1], v = 1, w = 1
346 cpgid_l11 = (
347 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
348 + num_cps_uvw[0] * (num_cps_uvw[1] - 1)
349 + i
350 )
351 control_points_line_11.append(element.nodes[cpgid_l11])
353 # Fill line 2, 4, 10 and 12 with their control points
354 for j in range(num_cps_uvw[1]):
355 # Line 2 has the control points on u = 1, v = [0,1] , w = 0
356 cpgid_l2 = num_cps_uvw[0] * j + (num_cps_uvw[0] - 1)
357 control_points_line_2.append(element.nodes[cpgid_l2])
359 # Line 4 has the control points on u = 0, v = [0,1] , w = 0
360 cpgid_l4 = num_cps_uvw[0] * j + 0
361 control_points_line_4.append(element.nodes[cpgid_l4])
363 # Line 10 has the control points on u = 1, v = [0,1] , w = 1
364 cpgid_l10 = (
365 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
366 + num_cps_uvw[0] * j
367 + (num_cps_uvw[0] - 1)
368 )
369 control_points_line_10.append(element.nodes[cpgid_l10])
371 # Line 12 has the control points on u = 0, v = [0,1] , w = 1
372 cpgid_l12 = (
373 num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)
374 + num_cps_uvw[0] * j
375 )
376 control_points_line_12.append(element.nodes[cpgid_l12])
378 # Fill line 5, 6, 7 and 8 with their control points
379 for k in range(num_cps_uvw[2]):
380 # Line 5 has the control points on u = 0, v = 0 , w = [0,1]
381 cpgid_l5 = num_cps_uvw[0] * num_cps_uvw[1] * k
382 control_points_line_5.append(element.nodes[cpgid_l5])
384 # Line 6 has the control points on u = 1, v = 0 , w = [0,1]
385 cpgid_l6 = num_cps_uvw[0] * num_cps_uvw[1] * k + num_cps_uvw[0] - 1
386 control_points_line_6.append(element.nodes[cpgid_l6])
388 # Line 7 has the control points on u = 0, v = 1 , w = [0,1]
389 cpgid_l7 = num_cps_uvw[0] * num_cps_uvw[1] * k + num_cps_uvw[0] * (
390 num_cps_uvw[1] - 1
391 )
392 control_points_line_7.append(element.nodes[cpgid_l7])
394 # Line 8 has the control points on u = 1, v = 1 , w = [0,1]
395 cpgid_l8 = (
396 num_cps_uvw[0] * num_cps_uvw[1] * k
397 + num_cps_uvw[0] * num_cps_uvw[1]
398 - 1
399 )
400 control_points_line_8.append(element.nodes[cpgid_l8])
402 # Create geometric sets for lines
403 return_set["line_v_min_w_min"] = _GeometrySetNodes(
404 _mpy.geo.line, nodes=control_points_line_1
405 )
406 return_set["line_u_max_w_min"] = _GeometrySetNodes(
407 _mpy.geo.line, nodes=control_points_line_2
408 )
409 return_set["line_v_max_w_min"] = _GeometrySetNodes(
410 _mpy.geo.line, nodes=control_points_line_3
411 )
412 return_set["line_u_min_w_min"] = _GeometrySetNodes(
413 _mpy.geo.line, nodes=control_points_line_4
414 )
415 return_set["line_u_min_v_min"] = _GeometrySetNodes(
416 _mpy.geo.line, nodes=control_points_line_5
417 )
418 return_set["line_u_max_v_min"] = _GeometrySetNodes(
419 _mpy.geo.line, nodes=control_points_line_6
420 )
421 return_set["line_u_min_v_max"] = _GeometrySetNodes(
422 _mpy.geo.line, nodes=control_points_line_7
423 )
424 return_set["line_u_max_v_max"] = _GeometrySetNodes(
425 _mpy.geo.line, nodes=control_points_line_8
426 )
427 return_set["line_v_min_w_max"] = _GeometrySetNodes(
428 _mpy.geo.line, nodes=control_points_line_9
429 )
430 return_set["line_u_max_w_max"] = _GeometrySetNodes(
431 _mpy.geo.line, nodes=control_points_line_10
432 )
433 return_set["line_v_max_w_max"] = _GeometrySetNodes(
434 _mpy.geo.line, nodes=control_points_line_11
435 )
436 return_set["line_u_min_w_max"] = _GeometrySetNodes(
437 _mpy.geo.line, nodes=control_points_line_12
438 )
440 else:
441 raise NotImplementedError(
442 "Error, not implemented for NURBS with dimension {}!".format(
443 nurbs_dimension
444 )
445 )
447 def get_patch_surfaces(return_set, num_cps_uvw, nurbs_dimension, element):
448 """Get the control points positioned over the surfaces of a patch."""
450 control_points_surface_1 = []
452 if nurbs_dimension == 2:
453 # As there is only one surface, it collects all the control points
454 control_points_surface_1.extend(
455 element.nodes[: (num_cps_uvw[0] * num_cps_uvw[1])]
456 )
458 # Create geometric sets for surfaces
459 return_set["surf"] = _GeometrySetNodes(
460 _mpy.geo.surface, nodes=control_points_surface_1
461 )
463 elif nurbs_dimension == 3:
464 control_points_surface_2 = []
465 control_points_surface_3 = []
466 control_points_surface_4 = []
467 control_points_surface_5 = []
468 control_points_surface_6 = []
470 # Surface defined on w = 0
471 control_points_surface_1.extend(
472 element.nodes[: (num_cps_uvw[0] * num_cps_uvw[1])]
473 )
475 # Surface defined on w = 1
476 control_points_surface_2.extend(
477 element.nodes[
478 (num_cps_uvw[0] * num_cps_uvw[1] * (num_cps_uvw[2] - 1)) : (
479 num_cps_uvw[0] * num_cps_uvw[1] * num_cps_uvw[2]
480 )
481 ]
482 )
484 for layers_w_dir in range(num_cps_uvw[2]):
485 # Calculate the number of control points on the w-plane
486 num_cps_plane_w = num_cps_uvw[0] * num_cps_uvw[1]
488 for layers_u_dir in range(num_cps_uvw[0]):
489 # Surface defined on v = 0
490 cpgid_l1 = num_cps_uvw[0] * 0 + layers_u_dir
491 control_points_surface_3.append(
492 element.nodes[cpgid_l1 + num_cps_plane_w * layers_w_dir]
493 )
495 # Surface defined on v = 1
496 cpgid_l3 = num_cps_uvw[0] * (num_cps_uvw[1] - 1) + layers_u_dir
497 control_points_surface_5.append(
498 element.nodes[cpgid_l3 + num_cps_plane_w * layers_w_dir]
499 )
501 for layers_v_dir in range(num_cps_uvw[1]):
502 # Surface defined on u = 1
503 cpgid_l2 = num_cps_uvw[0] * layers_v_dir + (num_cps_uvw[0] - 1)
504 control_points_surface_4.append(
505 element.nodes[cpgid_l2 + num_cps_plane_w * layers_w_dir]
506 )
508 # Surface defined on u = 0
509 cpgid_l4 = num_cps_uvw[0] * layers_v_dir + 0
510 control_points_surface_6.append(
511 element.nodes[cpgid_l4 + num_cps_plane_w * layers_w_dir]
512 )
514 # Create geometric sets for surfaces
515 return_set["surf_w_min"] = _GeometrySetNodes(
516 _mpy.geo.surface, nodes=control_points_surface_1
517 )
518 return_set["surf_w_max"] = _GeometrySetNodes(
519 _mpy.geo.surface, nodes=control_points_surface_2
520 )
521 return_set["surf_v_min"] = _GeometrySetNodes(
522 _mpy.geo.surface, nodes=control_points_surface_3
523 )
524 return_set["surf_u_max"] = _GeometrySetNodes(
525 _mpy.geo.surface, nodes=control_points_surface_4
526 )
527 return_set["surf_v_max"] = _GeometrySetNodes(
528 _mpy.geo.surface, nodes=control_points_surface_5
529 )
530 return_set["surf_u_min"] = _GeometrySetNodes(
531 _mpy.geo.surface, nodes=control_points_surface_6
532 )
534 else:
535 raise NotImplementedError(
536 "Error, not implemented for NURBS with dimension {}!".format(
537 nurbs_dimension
538 )
539 )
541 def get_patch_volume(return_set, num_cps_uvw, nurbs_dimension, element):
542 """Get the control points positioned in the volume of a patch."""
544 control_points_volume_1 = []
546 if nurbs_dimension == 2:
547 # As this is a surface, it's not necessary to get a volume GeometrySet
548 pass
550 elif nurbs_dimension == 3:
551 # As there is only one volume, it collects all the control points
552 control_points_volume_1.extend(
553 element.nodes[: (num_cps_uvw[0] * num_cps_uvw[1] * num_cps_uvw[2])]
554 )
556 # Create geometric sets for surfaces
557 return_set["vol"] = _GeometrySetNodes(
558 _mpy.geo.volume, nodes=control_points_volume_1
559 )
561 else:
562 raise NotImplementedError(
563 "Error, not implemented for NURBS with dimension {}".format(
564 nurbs_dimension
565 )
566 )
568 # Create return set
569 return_set = _GeometryName()
571 # Get the number of control points on each parametric direction that define the patch
572 num_cps_uvw = get_num_cps_uvw(element.knot_vectors)
574 # Get the NURBS dimension
575 nurbs_dimension = len(element.knot_vectors)
577 # Obtain the vertices of the patch
578 get_patch_vertices(return_set, num_cps_uvw, nurbs_dimension, element)
580 # Obtain the lines of the patch
581 get_patch_lines(return_set, num_cps_uvw, nurbs_dimension, element)
583 # Obtain the surfaces of the patch
584 get_patch_surfaces(return_set, num_cps_uvw, nurbs_dimension, element)
586 # Obtain the volume of the patch
587 get_patch_volume(return_set, num_cps_uvw, nurbs_dimension, element)
589 return return_set