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

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

23 

24from typing import Any as _Any 

25 

26import numpy as _np 

27from numpy.typing import NDArray as _NDArray 

28 

29from beamme.core.conf import bme as _bme 

30from beamme.core.rotation import Rotation as _Rotation 

31 

32 

33class Node: 

34 """This object represents one node in the mesh.""" 

35 

36 node_type = _bme.node_type.node 

37 

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 

41 

42 # Coordinates of this node. 

43 self.coordinates = _np.array(coordinates, dtype=float) 

44 

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 

48 

49 # If the node is in the middle of a beam element. 

50 self.is_middle_node = is_middle_node 

51 

52 # Lists with the objects that this node is linked to. 

53 self.element_link: list[_Any] = [] 

54 

55 # If this node is replaced, store a link to the remaining node. 

56 self.target_node: "Node" | None = None 

57 

58 def get_target_node(self) -> "Node": 

59 """Return the target node of this node. 

60 

61 Returns: 

62 If this node has a linked target node, then this target node is returned, 

63 otherwise this node is returned. 

64 """ 

65 

66 if self.target_node is None: 

67 return self 

68 else: 

69 return self.target_node.get_target_node() 

70 

71 def unlink(self) -> None: 

72 """Reset the links to elements.""" 

73 self.element_link = [] 

74 

75 

76class NodeCosserat(Node): 

77 """This object represents a Cosserat node in the mesh, i.e., it contains 

78 three positions and three rotations.""" 

79 

80 node_type = _bme.node_type.cosserat 

81 

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) 

91 

92 # Rotation of this node. 

93 self.rotation = rotation.copy() 

94 

95 # Arc length along the filament that this beam is a part of 

96 self.arc_length = arc_length 

97 

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. 

106 

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

114 

115 self.rotation = rotation * self.rotation 

116 

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 

124 

125 

126class ControlPoint(Node): 

127 """This object represents a control point with a weight in the mesh.""" 

128 

129 node_type = _bme.node_type.control_point 

130 

131 def __init__(self, coordinates, weight, **kwargs): 

132 super().__init__(coordinates, **kwargs) 

133 

134 # Weight of this node 

135 self.weight = weight