Coverage for src/beamme/mesh_creation_functions/applications/beam_fibers_in_rectangle.py: 95%

55 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"""This file has functions to generate multiple parallel fibers within a rectangle. 

23 

24This can for example be used to create fiber reinforced composite plates. 

25""" 

26 

27import numpy as _np 

28 

29from beamme.core.geometry_set import GeometryName as _GeometryName 

30from beamme.core.geometry_set import GeometrySet as _GeometrySet 

31from beamme.mesh_creation_functions.beam_line import ( 

32 create_beam_mesh_line as _create_beam_mesh_line, 

33) 

34from beamme.utils.nodes import check_node_by_coordinate as _check_node_by_coordinate 

35 

36 

37def _intersect_line_with_rectangle( 

38 length, width, start_line, direction_line, fail_if_no_intersection=True 

39): 

40 """Calculate the intersection points between a line and a rectangle. 

41 

42 Args 

43 ---- 

44 length: scalar 

45 Rectangle length in x direction. 

46 width: scalar 

47 Rectangle width in y direction. 

48 start_line: 2d-list 

49 Point on the line. 

50 direction_line: 2d-list 

51 Direction of the line. 

52 fail_if_no_intersection: bool 

53 If this is true and no intersections are found, an error will be 

54 thrown. 

55 

56 Return 

57 ---- 

58 (start, end, projection_found) 

59 start: 2D vector 

60 Start point of intersected line. 

61 end: 2D vector 

62 End point of intersected line. 

63 projection_found: bool 

64 True if intersection is valid. 

65 """ 

66 # Convert the input values to np.arrays. 

67 start_line = _np.asarray(start_line) 

68 direction_line = _np.asarray(direction_line) 

69 

70 # Set definition for the boundary lines of the rectangle. The director is 

71 # chosen in a way, that the values [0, 1] for the line parameters alpha are 

72 # valid. 

73 # boundary_lines = [..., [start, dir], ...] 

74 boundary_lines = [ 

75 [[0, 0], [length, 0]], 

76 [[0, 0], [0, width]], 

77 [[0, width], [length, 0]], 

78 [[length, 0], [0, width]], 

79 ] 

80 # Convert to numpy arrays. 

81 boundary_lines = [ 

82 [_np.array(item) for item in boundary] for boundary in boundary_lines 

83 ] 

84 

85 # Loop over the boundaries. 

86 alpha_list = [] 

87 for start_boundary, direction_boundary in boundary_lines: 

88 # Set up the linear system to solve the intersection problem. 

89 A = _np.transpose(_np.array([direction_line, -direction_boundary])) 

90 

91 # Check if the system is solvable. 

92 if _np.abs(_np.linalg.det(A)) > 1e-10: 

93 alpha = _np.linalg.solve(A, start_boundary - start_line) 

94 if 0 <= alpha[1] and alpha[1] <= 1: 

95 alpha_list.append(alpha[0]) 

96 

97 # Check that intersections were found. 

98 if len(alpha_list) < 2: 

99 if fail_if_no_intersection: 

100 raise ValueError("No intersections found!") 

101 return (None, None, False) 

102 

103 # Return the start and end point on the line. 

104 return ( 

105 start_line + min(alpha_list) * direction_line, 

106 start_line + max(alpha_list) * direction_line, 

107 True, 

108 ) 

109 

110 

111def create_fibers_in_rectangle( 

112 mesh, 

113 beam_class, 

114 material, 

115 length, 

116 width, 

117 angle, 

118 fiber_normal_distance, 

119 fiber_element_length, 

120 *, 

121 reference_point=None, 

122 fiber_element_length_min=None, 

123): 

124 """Create multiple fibers in a rectangle. 

125 

126 Args 

127 ---- 

128 mesh: Mesh 

129 Mesh that the fibers will be added to. 

130 beam_class: Beam 

131 Class that will be used to create the beam elements. 

132 material: Material 

133 Material for the beam. 

134 length: float 

135 Length of the rectangle in x direction (starting at x=0) 

136 width: float 

137 Width of the rectangle in y direction (starting at y=0) 

138 angle: float 

139 Angle of the fibers in degree. 

140 fiber_normal_distance: float 

141 Normal distance between the parallel fibers. 

142 fiber_element_length: float 

143 Length of a single beam element. In general it will not be possible to 

144 exactly achieve this length. 

145 reference_point: [float, float] 

146 Specify a single point inside the rectangle that one of the fibers will pass through. 

147 Per default the reference point is in the middle of the rectangle. 

148 fiber_element_length_min: float 

149 Minimum fiber length. If a fiber is shorter than this value, it will not be created. 

150 The default value is half of fiber_element_length. 

151 """ 

152 if reference_point is None: 

153 reference_point = 0.5 * _np.array([length, width]) 

154 else: 

155 if ( 

156 reference_point[0] < 0.0 

157 or reference_point[0] > length 

158 or reference_point[1] < 0.0 

159 or reference_point[1] > width 

160 ): 

161 raise ValueError("The reference point has to lie within the rectangle") 

162 

163 if fiber_element_length_min is None: 

164 fiber_element_length_min = 0.5 * fiber_element_length 

165 elif fiber_element_length_min < 0.0: 

166 raise ValueError("fiber_element_length_min must be positive!") 

167 

168 # Get the fiber angle in rad. 

169 fiber_angle = angle * _np.pi / 180.0 

170 sin = _np.sin(fiber_angle) 

171 cos = _np.cos(fiber_angle) 

172 

173 # Direction and normal vector of the fibers. 

174 fiber_direction = _np.array([cos, sin]) 

175 fiber_normal = _np.array([-sin, cos]) 

176 

177 # Get an upper bound of the number of fibers in this layer. 

178 diagonal = _np.sqrt(length**2 + width**2) 

179 fiber_n_max = int(_np.ceil(diagonal / fiber_normal_distance)) + 1 

180 

181 # Go in both directions from the start point. 

182 for direction_sign, n_start in [[-1, 1], [1, 0]]: 

183 # Create a fiber as long as an intersection is found. 

184 for i_fiber in range(n_start, fiber_n_max): 

185 # Get the start and end point of the line. 

186 start, end, projection_found = _intersect_line_with_rectangle( 

187 length, 

188 width, 

189 reference_point 

190 + fiber_normal * i_fiber * fiber_normal_distance * direction_sign, 

191 fiber_direction, 

192 fail_if_no_intersection=False, 

193 ) 

194 

195 if projection_found: 

196 # Calculate the length of the line. 

197 fiber_length = _np.linalg.norm(end - start) 

198 

199 # Create the beams if the length is not smaller than the fiber 

200 # distance. 

201 if fiber_length >= fiber_element_length_min: 

202 # Calculate the number of elements in this fiber. 

203 fiber_nel = int(_np.round(fiber_length / fiber_element_length)) 

204 fiber_nel = _np.max([fiber_nel, 1]) 

205 _create_beam_mesh_line( 

206 mesh, 

207 beam_class, 

208 material, 

209 _np.append(start, 0.0), 

210 _np.append(end, 0.0), 

211 n_el=fiber_nel, 

212 ) 

213 else: 

214 # The current search position is already outside of the rectangle, no need to continue. 

215 break 

216 

217 return_set = _GeometryName() 

218 return_set["north"] = _GeometrySet( 

219 mesh.get_nodes_by_function(_check_node_by_coordinate, 1, width), 

220 ) 

221 return_set["east"] = _GeometrySet( 

222 mesh.get_nodes_by_function(_check_node_by_coordinate, 0, length), 

223 ) 

224 return_set["south"] = _GeometrySet( 

225 mesh.get_nodes_by_function(_check_node_by_coordinate, 1, 0) 

226 ) 

227 return_set["west"] = _GeometrySet( 

228 mesh.get_nodes_by_function(_check_node_by_coordinate, 0, 0) 

229 ) 

230 return_set["all"] = _GeometrySet(mesh.elements) 

231 return return_set