Coverage for src/meshpy/core/nurbs_patch.py: 93%
117 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"""This module implements NURBS patches for the mesh."""
24import numpy as _np
26from meshpy.core.conf import mpy as _mpy
27from meshpy.core.element import Element as _Element
28from meshpy.four_c.material import (
29 MaterialStVenantKirchhoff as _MaterialStVenantKirchhoff,
30)
31from meshpy.utils.environment import fourcipp_is_available as _fourcipp_is_available
34class NURBSPatch(_Element):
35 """A base class for a NURBS patch."""
37 # A list of valid material types for this element
38 valid_material = [_MaterialStVenantKirchhoff]
40 def __init__(
41 self,
42 knot_vectors,
43 polynomial_orders,
44 element_string,
45 material=None,
46 nodes=None,
47 element_description=None,
48 ):
49 super().__init__(nodes=nodes, material=material)
51 # Knot vectors
52 self.knot_vectors = knot_vectors
54 # Polynomial degrees
55 self.polynomial_orders = polynomial_orders
57 # Set numbers for elements
58 self.n_nurbs_patch = None
60 # Set the element definitions
61 self.element_string = element_string
62 self.element_description = element_description
64 def get_nurbs_dimension(self):
65 """Return the number of dimensions of the NURBS structure."""
66 n_knots = len(self.knot_vectors)
67 n_polynomial = len(self.polynomial_orders)
68 if not n_knots == n_polynomial:
69 raise ValueError(
70 "The variables n_knots and polynomial_orders should have "
71 f"the same length. Got {n_knots} and {n_polynomial}"
72 )
73 return n_knots
75 def dump_element_specific_section(self, yaml_dict):
76 """Set the knot vectors of the NURBS patch in the input file."""
78 if _fourcipp_is_available():
79 raise ValueError("Port this functionality to not use the legacy format.")
81 knotvectors_section = "STRUCTURE KNOTVECTORS"
83 if knotvectors_section not in yaml_dict.keys():
84 yaml_dict[knotvectors_section] = []
86 section = yaml_dict[knotvectors_section]
87 section.append(f"NURBS_DIMENSION {self.get_nurbs_dimension()}")
88 section.append("BEGIN NURBSPATCH")
89 section.append(f"ID {self.n_nurbs_patch}")
91 for dir_manifold in range(len(self.knot_vectors)):
92 num_knotvectors = len(self.knot_vectors[dir_manifold])
93 section.append(f"NUMKNOTS {num_knotvectors}")
94 section.append(f"DEGREE {self.polynomial_orders[dir_manifold]}")
96 # Check the type of knot vector, in case that the multiplicity of the first and last
97 # knot vectors is not p + 1, then it is a closed (periodic) knot vector, otherwise it
98 # is an open (interpolated) knot vector.
99 knotvector_type = "Interpolated"
101 for i in range(self.polynomial_orders[dir_manifold] - 1):
102 if (
103 abs(
104 self.knot_vectors[dir_manifold][i]
105 - self.knot_vectors[dir_manifold][i + 1]
106 )
107 > _mpy.eps_knot_vector
108 ) or (
109 abs(
110 self.knot_vectors[dir_manifold][num_knotvectors - 2 - i]
111 - self.knot_vectors[dir_manifold][num_knotvectors - 1 - i]
112 )
113 > _mpy.eps_knot_vector
114 ):
115 knotvector_type = "Periodic"
116 break
118 section.append(f"TYPE {knotvector_type}")
120 for knot_vector_val in self.knot_vectors[dir_manifold]:
121 section.append(knot_vector_val)
123 section.append("END NURBSPATCH")
125 def get_number_elements(self):
126 """Get the number of elements in this patch by checking the amount of
127 nonzero knot spans in the knot vector."""
129 num_elements_dir = _np.zeros(len(self.knot_vectors), dtype=int)
131 for i_dir in range(len(self.knot_vectors)):
132 for i_knot in range(len(self.knot_vectors[i_dir]) - 1):
133 if (
134 abs(
135 self.knot_vectors[i_dir][i_knot]
136 - self.knot_vectors[i_dir][i_knot + 1]
137 )
138 > _mpy.eps_knot_vector
139 ):
140 num_elements_dir[i_dir] += 1
142 total_num_elements = _np.prod(num_elements_dir)
144 return total_num_elements
146 def _check_material(self):
147 """Check if the linked material is valid for this type of NURBS solid
148 element."""
149 for material_type in self.valid_material:
150 if isinstance(self.material, material_type):
151 return
152 raise TypeError(
153 f"NURBS solid of type {type(self)} can not have a material of t"
154 f"ype {type(self.material)}!"
155 )
158class NURBSSurface(NURBSPatch):
159 """A patch of a NURBS surface."""
161 def __init__(self, *args, element_string=None, **kwargs):
162 if element_string is None:
163 element_string = "WALLNURBS"
164 super().__init__(*args, element_string, **kwargs)
166 def dump_to_list(self):
167 """Return a list with all the element definitions contained in this
168 patch."""
170 if _fourcipp_is_available():
171 raise ValueError("Port this functionality to not use the legacy format.")
173 # Check the material
174 self._check_material()
176 # Calculate how many control points are on the u direction
177 ctrlpts_size_u = len(self.knot_vectors[0]) - self.polynomial_orders[0] - 1
179 def get_ids_ctrlpts_surface(knot_span_u, knot_span_v):
180 """For an interpolated patch, calculate control points involved in
181 evaluation of the surface point at the knot span (knot_span_u,
182 knot_span_v)"""
184 id_u = knot_span_u - self.polynomial_orders[0]
185 id_v = knot_span_v - self.polynomial_orders[1]
187 element_ctrlpts_ids = []
188 for j in range(self.polynomial_orders[1] + 1):
189 for i in range(self.polynomial_orders[0] + 1):
190 # Calculating the global index of the control point, based on the book
191 # "Isogeometric Analysis: toward Integration of CAD and FEA" of J. Austin
192 # Cottrell, p. 314.
193 index_global = ctrlpts_size_u * (id_v + j) + id_u + i
194 element_ctrlpts_ids.append(index_global)
196 return element_ctrlpts_ids
198 patch_elements = []
200 # Adding an increment j to self.global to obtain the ID of an element in the patch
201 j = 0
203 # Loop over the knot spans to obtain the elements inside the patch
204 for knot_span_v in range(
205 self.polynomial_orders[1],
206 len(self.knot_vectors[1]) - self.polynomial_orders[1] - 1,
207 ):
208 for knot_span_u in range(
209 self.polynomial_orders[0],
210 len(self.knot_vectors[0]) - self.polynomial_orders[0] - 1,
211 ):
212 element_cps_ids = get_ids_ctrlpts_surface(knot_span_u, knot_span_v)
214 string_cps = " ".join(
215 [str(self.nodes[i].i_global) for i in element_cps_ids]
216 )
218 patch_elements.append(
219 "{} {} NURBS{} {} MAT {} {}".format(
220 self.i_global + j,
221 self.element_string,
222 (self.polynomial_orders[0] + 1)
223 * (self.polynomial_orders[1] + 1),
224 string_cps,
225 self.material.i_global,
226 self.element_description,
227 )
228 )
229 j += 1
231 return patch_elements
234class NURBSVolume(NURBSPatch):
235 """A patch of a NURBS volume."""
237 def __init__(self, *args, element_string=None, **kwargs):
238 if element_string is not None:
239 raise ValueError("element_string is not yet implemented for NURBS volumes")
240 super().__init__(*args, element_string, **kwargs)
242 def dump_to_list(self):
243 """Return a list with all the element definitions contained in this
244 patch."""
246 if _fourcipp_is_available():
247 raise ValueError("Port this functionality to not use the legacy format.")
249 # Check the material
250 self._check_material()
252 # Calculate how many control points are on the u and v directions
253 ctrlpts_size_u = len(self.knot_vectors[0]) - self.polynomial_orders[0] - 1
254 ctrlpts_size_v = len(self.knot_vectors[1]) - self.polynomial_orders[1] - 1
256 def get_ids_ctrlpts_volume(knot_span_u, knot_span_v, knot_span_w):
257 """For an interpolated patch, calculate control points involved in
258 evaluation of the surface point at the knot span (knot_span_u,
259 knot_span_v, knot_span_w)"""
261 id_u = knot_span_u - self.polynomial_orders[0]
262 id_v = knot_span_v - self.polynomial_orders[1]
263 id_w = knot_span_w - self.polynomial_orders[2]
265 element_ctrlpts_ids = []
267 for k in range(self.polynomial_orders[2] + 1):
268 for j in range(self.polynomial_orders[1] + 1):
269 for i in range(self.polynomial_orders[0] + 1):
270 # Calculating the global index of the control point, based on the paper
271 # "Isogeometric analysis: an overview and computer implementation aspects"
272 # of Vinh-Phu Nguyen, Mathematics and Computers in Simulation, Jun-2015.
273 index_global = (
274 ctrlpts_size_u * ctrlpts_size_v * (id_w + k)
275 + ctrlpts_size_u * (id_v + j)
276 + id_u
277 + i
278 )
279 element_ctrlpts_ids.append(index_global)
281 return element_ctrlpts_ids
283 patch_elements = []
285 # Adding an increment to self.global to obtain the ID of an element in the patch
286 increment_ele = 0
288 # Loop over the knot spans to obtain the elements inside the patch
289 for knot_span_w in range(
290 self.polynomial_orders[2],
291 len(self.knot_vectors[2]) - self.polynomial_orders[2] - 1,
292 ):
293 for knot_span_v in range(
294 self.polynomial_orders[1],
295 len(self.knot_vectors[1]) - self.polynomial_orders[1] - 1,
296 ):
297 for knot_span_u in range(
298 self.polynomial_orders[0],
299 len(self.knot_vectors[0]) - self.polynomial_orders[0] - 1,
300 ):
301 element_cps_ids = get_ids_ctrlpts_volume(
302 knot_span_u, knot_span_v, knot_span_w
303 )
305 string_cps = " ".join(
306 [str(self.nodes[i].i_global) for i in element_cps_ids]
307 )
309 num_cp_in_element = (
310 (self.polynomial_orders[0] + 1)
311 * (self.polynomial_orders[1] + 1)
312 * (self.polynomial_orders[2] + 1)
313 )
315 patch_elements.append(
316 "{} SOLID NURBS{} {} MAT {} {}".format(
317 self.i_global + increment_ele,
318 num_cp_in_element,
319 string_cps,
320 self.material.i_global,
321 self.element_description,
322 )
323 )
324 increment_ele += 1
326 return patch_elements