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
« 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.
24This can for example be used to create fiber reinforced composite plates.
25"""
27import numpy as _np
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
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.
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.
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)
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 ]
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]))
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])
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)
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 )
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.
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")
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!")
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)
173 # Direction and normal vector of the fibers.
174 fiber_direction = _np.array([cos, sin])
175 fiber_normal = _np.array([-sin, cos])
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
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 )
195 if projection_found:
196 # Calculate the length of the line.
197 fiber_length = _np.linalg.norm(end - start)
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
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