Coverage for src/beamme/mesh_creation_functions/beam_helix.py: 84%
44 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 15:20 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 15:20 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2018-2026 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"""Functions to create beam meshes along helical paths."""
24import warnings as _warnings
26import numpy as _np
28from beamme.core.mesh import Mesh as _Mesh
29from beamme.core.rotation import Rotation as _Rotation
31from .beam_line import create_beam_mesh_line as _create_beam_mesh_line
34def create_beam_mesh_helix(
35 mesh,
36 beam_class,
37 material,
38 axis_vector,
39 axis_point,
40 start_point,
41 *,
42 helix_angle=None,
43 height_helix=None,
44 turns=None,
45 warning_straight_line=True,
46 **kwargs,
47):
48 """Generate a helical segment starting at a given start point around a predefined
49 axis (defined by axis_vector and axis_point). The helical segment is defined by a
50 start_point and exactly two of the basic helical quantities [helix_angle,
51 height_helix, turns].
53 Args
54 ----
55 mesh: Mesh
56 Mesh that the helical segment will be added to.
57 beam_class: Beam
58 Class of beam that will be used for this line.
59 material: Material
60 Material for this segment.
61 axis_vector: _np.array, list
62 Vector for the orientation of the helical center axis.
63 axis_point: _np.array, list
64 Point lying on the helical center axis. Does not need to align with
65 bottom plane of helix.
66 start_point: _np.array, list
67 Start point of the helix. Defines the radius.
68 helix_angle: float
69 Angle of the helix (synonyms in literature: twist angle or pitch
70 angle).
71 height_helix: float
72 Height of helix.
73 turns: float
74 Number of turns.
75 warning_straight_line: bool
76 Warn if radius of helix is zero or helix angle is 90 degrees and
77 simple line is returned.
79 **kwargs (for all of them look into create_beam_mesh_function)
80 ----
81 n_el: int
82 Number of equally spaced beam elements along the line. Defaults to 1.
83 Mutually exclusive with l_el.
84 l_el: float
85 Desired length of beam elements. Mutually exclusive with n_el.
86 Be aware, that this length might not be achieved, if the elements are
87 warped after they are created.
89 Return
90 ----
91 return_set: GeometryName
92 Set with the 'start' and 'end' node of the line. Also a 'line' set
93 with all nodes of the line.
94 """
95 if [helix_angle, height_helix, turns].count(None) != 1:
96 raise ValueError(
97 "Exactly two arguments of [helix_angle, height_helix, turns]"
98 " must be provided!"
99 )
101 if helix_angle is not None and _np.isclose(_np.sin(helix_angle), 0.0):
102 raise ValueError(
103 "Helix angle of helix is 0 degrees! "
104 + "Change angle for feasible helix geometry!"
105 )
107 if height_helix is not None and _np.isclose(height_helix, 0.0):
108 raise ValueError(
109 "Height of helix is 0! Change height for feasible helix geometry!"
110 )
112 # determine radius of helix
113 axis_vector = _np.asarray(axis_vector)
114 axis_point = _np.asarray(axis_point)
115 start_point = _np.asarray(start_point)
117 axis_vector = axis_vector / _np.linalg.norm(axis_vector)
118 origin = axis_point + _np.dot(
119 _np.dot(start_point - axis_point, axis_vector), axis_vector
120 )
121 start_point_origin_vec = start_point - origin
122 radius = _np.linalg.norm(start_point_origin_vec)
124 # create temporary mesh to not alter original mesh
125 mesh_temp = _Mesh()
127 # return line if radius of helix is 0, helix angle is pi/2 or turns is 0
128 if (
129 _np.isclose(radius, 0)
130 or (helix_angle is not None and _np.isclose(_np.cos(helix_angle), 0.0))
131 or (turns is not None and _np.isclose(turns, 0.0))
132 ):
133 if height_helix is None:
134 raise ValueError(
135 "Radius of helix is 0, helix angle is 90 degrees or turns is 0! "
136 + "Fallback to simple line geometry but height cannot be "
137 + "determined based on helix angle and turns! Either switch one "
138 + "helix parameter to height of helix or change radius!"
139 )
141 if warning_straight_line:
142 _warnings.warn(
143 "Radius of helix is 0, helix angle is 90 degrees or turns is 0! "
144 + "Simple line geometry is returned!"
145 )
147 if helix_angle is not None and height_helix is not None:
148 end_point = start_point + height_helix * axis_vector * _np.sign(
149 _np.sin(helix_angle)
150 )
151 elif height_helix is not None and turns is not None:
152 end_point = start_point + height_helix * axis_vector
154 line_sets = _create_beam_mesh_line(
155 mesh_temp,
156 beam_class,
157 material,
158 start_point=start_point,
159 end_point=end_point,
160 **kwargs,
161 )
163 # add line to mesh
164 mesh.add_mesh(mesh_temp)
166 return line_sets
168 # generate simple helix
169 if helix_angle and height_helix:
170 end_point = _np.array(
171 [
172 radius,
173 _np.sign(_np.sin(helix_angle)) * height_helix / _np.tan(helix_angle),
174 _np.sign(_np.sin(helix_angle)) * height_helix,
175 ]
176 )
177 elif helix_angle and turns:
178 end_point = _np.array(
179 [
180 radius,
181 _np.sign(_np.cos(helix_angle)) * 2 * _np.pi * radius * turns,
182 _np.sign(_np.cos(helix_angle))
183 * 2
184 * _np.pi
185 * radius
186 * _np.abs(turns)
187 * _np.tan(helix_angle),
188 ]
189 )
190 elif height_helix and turns:
191 end_point = _np.array(
192 [
193 radius,
194 2 * _np.pi * radius * turns,
195 height_helix,
196 ]
197 )
199 helix_sets = _create_beam_mesh_line(
200 mesh_temp,
201 beam_class,
202 material,
203 start_point=[radius, 0, 0],
204 end_point=end_point,
205 **kwargs,
206 )
208 mesh_temp.wrap_around_cylinder()
210 # rotate and translate simple helix to align with necessary axis and starting point
211 mesh_temp.rotate(
212 _Rotation.from_basis(start_point_origin_vec, axis_vector)
213 * _Rotation([1, 0, 0], -_np.pi * 0.5)
214 )
215 mesh_temp.translate(-mesh_temp.nodes[0].coordinates + start_point)
217 # add helix to mesh
218 mesh.add_mesh(mesh_temp)
220 return helix_sets