Coverage for src/beamme/core/node.py: 100%
39 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-08 11:03 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-08 11:03 +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 module implements the class that represents one node in the Mesh."""
24from typing import Any as _Any
26import numpy as _np
27from numpy.typing import NDArray as _NDArray
29from beamme.core.conf import bme as _bme
30from beamme.core.rotation import Rotation as _Rotation
33class Node:
34 """This object represents one node in the mesh."""
36 node_type = _bme.node_type.node
38 def __init__(self, coordinates, *, is_middle_node=False) -> None:
39 # Global index of this node in a mesh.
40 self.i_global: None | int = None
42 # Coordinates of this node.
43 self.coordinates = _np.array(coordinates, dtype=float)
45 # If this node is at the end of a line or curve (by default only those
46 # nodes are checked for overlapping nodes).
47 self.is_end_node = False
49 # If the node is in the middle of a beam element.
50 self.is_middle_node = is_middle_node
52 # Lists with the objects that this node is linked to.
53 self.element_link: list[_Any] = []
55 # If this node is replaced, store a link to the remaining node.
56 self.target_node: "Node" | None = None
58 def get_target_node(self) -> "Node":
59 """Return the target node of this node.
61 Returns:
62 If this node has a linked target node, then this target node is returned,
63 otherwise this node is returned.
64 """
66 if self.target_node is None:
67 return self
68 else:
69 return self.target_node.get_target_node()
71 def unlink(self) -> None:
72 """Reset the links to elements."""
73 self.element_link = []
76class NodeCosserat(Node):
77 """This object represents a Cosserat node in the mesh, i.e., it contains
78 three positions and three rotations."""
80 node_type = _bme.node_type.cosserat
82 def __init__(
83 self,
84 coordinates,
85 rotation: _Rotation,
86 *,
87 arc_length: float | None = None,
88 **kwargs,
89 ):
90 super().__init__(coordinates, **kwargs)
92 # Rotation of this node.
93 self.rotation = rotation.copy()
95 # Arc length along the filament that this beam is a part of
96 self.arc_length = arc_length
98 def rotate(
99 self,
100 rotation: _Rotation,
101 *,
102 origin: _NDArray | list[float] | None = None,
103 only_rotate_triads: bool = False,
104 ) -> None:
105 """Rotate this node.
107 Args:
108 rotation: Rotation that will be applied to this node.
109 origin: Point around which the node will be rotated. If None, the
110 node will be rotated around the origin (0,0,0).
111 only_rotate_triads: If True, only the rotation of this node will be
112 affected, the position of the node stays the same.
113 """
115 self.rotation = rotation * self.rotation
117 # Rotate the positions (around origin).
118 if not only_rotate_triads:
119 if origin is not None:
120 self.coordinates = self.coordinates - origin
121 self.coordinates = rotation * self.coordinates
122 if origin is not None:
123 self.coordinates = self.coordinates + origin
126class ControlPoint(Node):
127 """This object represents a control point with a weight in the mesh."""
129 node_type = _bme.node_type.control_point
131 def __init__(self, coordinates, weight, **kwargs):
132 super().__init__(coordinates, **kwargs)
134 # Weight of this node
135 self.weight = weight