Coverage for src/beamme/mesh_creation_functions/nurbs_generic.py: 96%
105 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:17 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-11 12:17 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2018-2025 BeamMe 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."""
24import itertools as _itertools
25from typing import Type as _Type
27import numpy as _np
29from beamme.core.conf import bme as _bme
30from beamme.core.geometry_set import GeometryName as _GeometryName
31from beamme.core.geometry_set import GeometrySetNodes as _GeometrySetNodes
32from beamme.core.mesh import Mesh as _Mesh
33from beamme.core.node import ControlPoint as _ControlPoint
34from beamme.core.nurbs_patch import NURBSSurface as _NURBSSurface
35from beamme.core.nurbs_patch import NURBSVolume as _NURBSVolume
38def add_splinepy_nurbs_to_mesh(
39 mesh: _Mesh,
40 splinepy_obj,
41 *,
42 material=None,
43 data: dict | None = None,
44) -> _GeometryName:
45 """Add a splinepy NURBS to the mesh.
47 Args:
48 mesh: Mesh that the created NURBS geometry will be added to.
49 splinepy_obj (splinepy object): NURBS geometry created using splinepy.
50 material (Material): Material for this geometry.
51 data: General element data, e.g., material, formulation, ...
53 Returns:
54 GeometryName:
55 Set with the control points that form the topology of the mesh.
57 For a surface, the following information is stored:
58 Vertices: 'vertex_u_min_v_min', 'vertex_u_max_v_min', 'vertex_u_min_v_max', 'vertex_u_max_v_max'
59 Edges: 'line_v_min', 'line_u_max', 'line_v_max', 'line_u_min'
60 Surface: 'surf'
62 For a volume, the following information is stored:
63 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',
64 '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'
65 Edges: 'line_v_min_w_min', 'line_u_max_w_min', 'line_v_max_w_min', 'line_u_min_w_min',
66 'line_u_min_v_min', 'line_u_max_v_min', 'line_u_min_v_max', 'line_u_max_v_max'
67 'line_v_min_w_max', 'line_u_max_w_max', 'line_v_max_w_max', 'line_u_min_w_max'
68 Surfaces: 'surf_w_min', 'surf_w_max', 'surf_v_min', 'surf_v_max', 'surf_v_max', 'surf_u_min'
69 Volume: 'vol'
70 """
72 # Make sure the material is in the mesh
73 mesh.add_material(material)
75 # Fill control points
76 control_points = [
77 _ControlPoint(coord, weight[0])
78 for coord, weight in zip(splinepy_obj.control_points, splinepy_obj.weights)
79 ]
81 # Fill element
82 manifold_dim = len(splinepy_obj.knot_vectors)
83 nurbs_object: _Type[_NURBSSurface] | _Type[_NURBSVolume]
84 if manifold_dim == 2:
85 nurbs_object = _NURBSSurface
86 elif manifold_dim == 3:
87 nurbs_object = _NURBSVolume
88 else:
89 raise NotImplementedError(
90 "Error, not implemented for a NURBS {}!".format(type(splinepy_obj))
91 )
93 element = nurbs_object(
94 splinepy_obj.knot_vectors,
95 splinepy_obj.degrees,
96 nodes=control_points,
97 material=material,
98 data=data,
99 )
101 # Add element and control points to the mesh
102 mesh.elements.append(element)
103 mesh.nodes.extend(control_points)
105 # Create geometry sets that will be returned
106 return_set = create_geometry_sets(element)
108 return return_set
111def add_geomdl_nurbs_to_mesh(
112 mesh: _Mesh,
113 geomdl_obj,
114 *,
115 material=None,
116 data: dict | None = None,
117) -> _GeometryName:
118 """Add a geomdl NURBS to the mesh.
120 Args:
121 mesh: Mesh that the created NURBS geometry will be added to.
122 geomdl_obj (geomdl object): NURBS geometry created using geomdl.
123 material (Material): Material for this geometry.
124 data: General element data, e.g., material, formulation, ...
126 Returns:
127 GeometryName:
128 Set with the control points that form the topology of the mesh.
130 For a surface, the following information is stored:
131 Vertices: 'vertex_u_min_v_min', 'vertex_u_max_v_min', 'vertex_u_min_v_max', 'vertex_u_max_v_max'
132 Edges: 'line_v_min', 'line_u_max', 'line_v_max', 'line_u_min'
133 Surface: 'surf'
135 For a volume, the following information is stored:
136 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',
137 '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'
138 Edges: 'line_v_min_w_min', 'line_u_max_w_min', 'line_v_max_w_min', 'line_u_min_w_min',
139 'line_u_min_v_min', 'line_u_max_v_min', 'line_u_min_v_max', 'line_u_max_v_max'
140 'line_v_min_w_max', 'line_u_max_w_max', 'line_v_max_w_max', 'line_u_min_w_max'
141 Surfaces: 'surf_w_min', 'surf_w_max', 'surf_v_min', 'surf_v_max', 'surf_v_max', 'surf_u_min'
142 Volume: 'vol'
143 """
145 # Make sure the material is in the mesh
146 mesh.add_material(material)
148 # Fill control points
149 control_points = []
150 nurbs_dimension = len(geomdl_obj.knotvector)
151 if nurbs_dimension == 2:
152 control_points = create_control_points_surface(geomdl_obj)
153 elif nurbs_dimension == 3:
154 control_points = create_control_points_volume(geomdl_obj)
155 else:
156 raise NotImplementedError(
157 "Error, not implemented for NURBS with dimension {}!".format(
158 nurbs_dimension
159 )
160 )
162 # Fill element
163 manifold_dim = len(geomdl_obj.knotvector)
164 nurbs_object: _Type[_NURBSSurface] | _Type[_NURBSVolume]
165 if manifold_dim == 2:
166 nurbs_object = _NURBSSurface
167 elif manifold_dim == 3:
168 nurbs_object = _NURBSVolume
169 else:
170 raise NotImplementedError(
171 "Error, not implemented for a NURBS {}!".format(type(geomdl_obj))
172 )
174 element = nurbs_object(
175 geomdl_obj.knotvector,
176 geomdl_obj.degree,
177 nodes=control_points,
178 material=material,
179 data=data,
180 )
182 # Add element and control points to the mesh
183 mesh.elements.append(element)
184 mesh.nodes.extend(control_points)
186 # Create geometry sets that will be returned
187 return_set = create_geometry_sets(element)
189 return return_set
192def create_control_points_surface(geomdl_obj):
193 """Creates a list with the ControlPoint objects of a surface created with
194 geomdl."""
195 control_points = []
196 for dir_v in range(geomdl_obj.ctrlpts_size_v):
197 for dir_u in range(geomdl_obj.ctrlpts_size_u):
198 weight = geomdl_obj.ctrlpts2d[dir_u][dir_v][3]
200 # As the control points are scaled with their weight, divide them to get
201 # their coordinates
202 coord = [
203 geomdl_obj.ctrlpts2d[dir_u][dir_v][0] / weight,
204 geomdl_obj.ctrlpts2d[dir_u][dir_v][1] / weight,
205 geomdl_obj.ctrlpts2d[dir_u][dir_v][2] / weight,
206 ]
208 control_points.append(_ControlPoint(coord, weight))
210 return control_points
213def create_control_points_volume(geomdl_obj):
214 """Creates a list with the ControlPoint objects of a volume created with
215 geomdl."""
216 control_points = []
217 for dir_w in range(geomdl_obj.ctrlpts_size_w):
218 for dir_v in range(geomdl_obj.ctrlpts_size_v):
219 for dir_u in range(geomdl_obj.ctrlpts_size_u):
220 # Obtain the id of the control point
221 cp_id = (
222 dir_v
223 + geomdl_obj.ctrlpts_size_v * dir_u
224 + geomdl_obj.ctrlpts_size_u * geomdl_obj.ctrlpts_size_v * dir_w
225 )
227 weight = geomdl_obj.ctrlptsw[cp_id][3]
229 # As the control points are scaled with their weight, divide them to get
230 # their coordinates
231 coord = [
232 geomdl_obj.ctrlptsw[cp_id][0] / weight,
233 geomdl_obj.ctrlptsw[cp_id][1] / weight,
234 geomdl_obj.ctrlptsw[cp_id][2] / weight,
235 ]
237 control_points.append(_ControlPoint(coord, weight))
239 return control_points
242def create_geometry_sets(element: _NURBSSurface | _NURBSVolume) -> _GeometryName:
243 """Create the geometry sets for NURBS patches of all dimensions.
245 Args:
246 element: The NURBS patch for which the geometry sets should be created.
248 Returns:
249 The geometry set container for the given NURBS patch.
250 """
252 # Create return set
253 return_set = _GeometryName()
255 # Get general data needed for the set creation
256 num_cps_uvw = element.get_number_of_control_points_per_dir()
257 nurbs_dimension = len(element.knot_vectors)
258 n_cp = _np.prod(num_cps_uvw)
259 axes = ["u", "v", "w"][:nurbs_dimension]
260 name_map = {0: "min", -1: "max", 1: "min_next", -2: "max_next"}
262 # This is a tensor array that contains the CP indices
263 cp_indices_dim = _np.arange(n_cp, dtype=int).reshape(*num_cps_uvw[::-1]).transpose()
265 if nurbs_dimension >= 0:
266 # Add point sets
267 directions = [0, -1]
268 for corner in _itertools.product(*([directions] * nurbs_dimension)):
269 name = "vertex_" + "_".join(
270 f"{axis}_{name_map[coord]}" for axis, coord in zip(axes, corner)
271 )
272 index = cp_indices_dim[corner]
273 return_set[name] = _GeometrySetNodes(
274 _bme.geo.point, nodes=element.nodes[index]
275 )
277 if nurbs_dimension > 0:
278 # Add edge sets
280 if nurbs_dimension == 2:
281 directions = [0, 1, -2, -1]
282 elif nurbs_dimension == 3:
283 directions = [0, -1]
284 else:
285 raise ValueError("NURBS dimension 1 not implemented")
287 # Iterate over each axis (the axis that varies — the "edge" direction)
288 for edge_axis in range(nurbs_dimension):
289 # The other axes will be fixed
290 fixed_axes = [i for i in range(nurbs_dimension) if i != edge_axis]
291 for fixed_dir in _itertools.product(*([directions] * len(fixed_axes))):
292 # Build slicing tuple for indexing cp_indices_dim
293 slicer: list[slice | int] = [slice(None)] * nurbs_dimension
294 name_parts = []
295 for axis_idx, dir_val in zip(fixed_axes, fixed_dir):
296 slicer[axis_idx] = dir_val
297 name_parts.append(f"{axes[axis_idx]}_{name_map[dir_val]}")
298 name = "line_" + "_".join(name_parts)
300 # Get node indices along the edge
301 edge_indices = cp_indices_dim[tuple(slicer)].flatten()
302 return_set[name] = _GeometrySetNodes(
303 _bme.geo.line, nodes=[element.nodes[i] for i in edge_indices]
304 )
306 if nurbs_dimension == 2:
307 # Add surface sets for surface NURBS
308 return_set["surf"] = _GeometrySetNodes(_bme.geo.surface, element.nodes)
310 if nurbs_dimension == 3:
311 # Add surface sets for volume NURBS
312 for fixed_axis in range(nurbs_dimension):
313 for dir_val in directions:
314 # Build slice and name
315 slicer = [slice(None)] * nurbs_dimension
316 slicer[fixed_axis] = dir_val
317 surface_name = f"surf_{axes[fixed_axis]}_{name_map[dir_val]}"
319 surface_indices = cp_indices_dim[tuple(slicer)].flatten()
320 return_set[surface_name] = _GeometrySetNodes(
321 _bme.geo.surface,
322 nodes=[element.nodes[i] for i in surface_indices],
323 )
325 # Add volume sets
326 return_set["vol"] = _GeometrySetNodes(_bme.geo.volume, element.nodes)
328 return return_set