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

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.""" 

23 

24import warnings as _warnings 

25 

26import numpy as _np 

27 

28from beamme.core.mesh import Mesh as _Mesh 

29from beamme.core.rotation import Rotation as _Rotation 

30 

31from .beam_line import create_beam_mesh_line as _create_beam_mesh_line 

32 

33 

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]. 

52 

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. 

78 

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. 

88 

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 ) 

100 

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 ) 

106 

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 ) 

111 

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) 

116 

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) 

123 

124 # create temporary mesh to not alter original mesh 

125 mesh_temp = _Mesh() 

126 

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 ) 

140 

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 ) 

146 

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 

153 

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 ) 

162 

163 # add line to mesh 

164 mesh.add_mesh(mesh_temp) 

165 

166 return line_sets 

167 

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 ) 

198 

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 ) 

207 

208 mesh_temp.wrap_around_cylinder() 

209 

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) 

216 

217 # add helix to mesh 

218 mesh.add_mesh(mesh_temp) 

219 

220 return helix_sets