beamme.mesh_creation_functions.beam_helix

Functions to create beam meshes along helical paths.

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

Generate a helical segment starting at a given start point around a predefined axis (defined by axis_vector and axis_point). The helical segment is defined by a start_point and exactly two of the basic helical quantities [helix_angle, height_helix, turns].

Args

mesh: Mesh Mesh that the helical segment will be added to. beam_class: Beam Class of beam that will be used for this line. material: Material Material for this segment. axis_vector: _np.array, list Vector for the orientation of the helical center axis. axis_point: _np.array, list Point lying on the helical center axis. Does not need to align with bottom plane of helix. start_point: _np.array, list Start point of the helix. Defines the radius. helix_angle: float Angle of the helix (synonyms in literature: twist angle or pitch angle). height_helix: float Height of helix. turns: float Number of turns. warning_straight_line: bool Warn if radius of helix is zero or helix angle is 90 degrees and simple line is returned.

**kwargs (for all of them look into create_beam_mesh_function)

n_el: int Number of equally spaced beam elements along the line. Defaults to 1. Mutually exclusive with l_el. l_el: float Desired length of beam elements. Mutually exclusive with n_el. Be aware, that this length might not be achieved, if the elements are warped after they are created.

Return

return_set: GeometryName Set with the 'start' and 'end' node of the line. Also a 'line' set with all nodes of the line.