Coverage for src / beamme / four_c / input_file_dump_item.py: 90%

94 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-23 08:08 +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 defines functions to dump mesh items for 4C.""" 

23 

24from typing import Any as _Any 

25 

26from beamme.core.boundary_condition import BoundaryCondition as _BoundaryCondition 

27from beamme.core.conf import bme as _bme 

28from beamme.core.coupling import Coupling as _Coupling 

29from beamme.core.element_volume import VolumeElement as _VolumeElement 

30from beamme.core.geometry_set import GeometrySet as _GeometrySet 

31from beamme.core.geometry_set import GeometrySetNodes as _GeometrySetNodes 

32from beamme.core.node import ControlPoint as _ControlPoint 

33from beamme.core.node import Node as _Node 

34from beamme.core.nurbs_patch import NURBSPatch as _NURBSPatch 

35from beamme.four_c.four_c_types import ( 

36 BeamKirchhoffParametrizationType as _BeamKirchhoffParametrizationType, 

37) 

38from beamme.four_c.four_c_types import BeamType as _BeamType 

39from beamme.four_c.input_file_mappings import ( 

40 INPUT_FILE_MAPPINGS as _INPUT_FILE_MAPPINGS, 

41) 

42 

43 

44def dump_node(node): 

45 """Return the representation of a node in the 4C input file.""" 

46 

47 if isinstance(node, _ControlPoint): 

48 return { 

49 "id": node.i_global + 1, 

50 "COORD": node.coordinates, 

51 "data": {"type": "CP", "weight": node.weight}, 

52 } 

53 elif isinstance(node, _Node): 

54 return { 

55 "id": node.i_global + 1, 

56 "COORD": node.coordinates, 

57 "data": {"type": "NODE"}, 

58 } 

59 else: 

60 raise TypeError(f"Got unexpected item of type {type(node)}") 

61 

62 

63def dump_solid_element(solid_element): 

64 """Return a dict with the items representing the given solid element.""" 

65 

66 if "MAT" in solid_element.data: 

67 raise ValueError( 

68 f"Element {solid_element.i_global} has a MAT entry in its data, this " 

69 "is not supported, the materials have to be assigned via the material " 

70 "attribute of the element." 

71 ) 

72 

73 return { 

74 "id": solid_element.i_global + 1, 

75 "cell": { 

76 "type": _INPUT_FILE_MAPPINGS["element_type_to_four_c_string"][ 

77 type(solid_element) 

78 ], 

79 "connectivity": solid_element.nodes, 

80 }, 

81 "data": solid_element.data 

82 | ( 

83 {"MAT": solid_element.material} 

84 if solid_element.material is not None 

85 else {} 

86 ), 

87 } 

88 

89 

90def dump_coupling(coupling): 

91 """Return the input file representation of the coupling condition.""" 

92 

93 if isinstance(coupling.data, dict): 

94 data = coupling.data 

95 else: 

96 # In this case we have to check which beams are connected to the node. 

97 # TODO: Coupling also makes sense for different beam types, this can 

98 # be implemented at some point. 

99 nodes = coupling.geometry_set.get_points() 

100 connected_elements = [ 

101 element for node in nodes for element in node.element_link 

102 ] 

103 element_types = {type(element) for element in connected_elements} 

104 if len(element_types) > 1: 

105 raise TypeError( 

106 f"Expected a single connected type of beam elements, got {element_types}" 

107 ) 

108 element_type = element_types.pop() 

109 if element_type.four_c_beam_type is _BeamType.kirchhoff: 

110 unique_parametrization_flags = { 

111 _BeamKirchhoffParametrizationType[ 

112 type(element).four_c_element_data["PARAMETRIZATION"] 

113 ] 

114 for element in connected_elements 

115 } 

116 if ( 

117 len(unique_parametrization_flags) > 1 

118 or not unique_parametrization_flags.pop() 

119 == _BeamKirchhoffParametrizationType.rot 

120 ): 

121 raise TypeError( 

122 "Couplings for Kirchhoff beams and tangent " 

123 "based parametrization not yet implemented." 

124 ) 

125 

126 data = element_type.get_coupling_dict(coupling.data) 

127 

128 return {"E": coupling.geometry_set.i_global + 1, **data} 

129 

130 

131def dump_geometry_set(geometry_set): 

132 """Return a list with the data describing this set.""" 

133 

134 # Sort nodes based on their global index 

135 nodes = sorted(geometry_set.get_all_nodes(), key=lambda n: n.i_global) 

136 

137 if not nodes: 

138 raise ValueError("Writing empty geometry sets is not supported") 

139 

140 return [ 

141 { 

142 "type": "NODE", 

143 "node_id": node.i_global + 1, 

144 "d_type": _INPUT_FILE_MAPPINGS["geometry_sets_geometry_to_entry_name"][ 

145 geometry_set.geometry_type 

146 ], 

147 "d_id": geometry_set.i_global + 1, 

148 } 

149 for node in nodes 

150 ] 

151 

152 

153def dump_nurbs_patch_knotvectors(input_file, nurbs_patch) -> None: 

154 """Set the knot vectors of the NURBS patch in the input file.""" 

155 

156 patch_data: dict[str, _Any] = { 

157 "KNOT_VECTORS": [], 

158 } 

159 

160 for dir_manifold in range(nurbs_patch.get_nurbs_dimension()): 

161 knotvector = nurbs_patch.knot_vectors[dir_manifold] 

162 num_knots = len(knotvector) 

163 

164 # Check the type of knot vector, in case that the multiplicity of the first and last 

165 # knot vectors is not p + 1, then it is a closed (periodic) knot vector, otherwise it 

166 # is an open (interpolated) knot vector. 

167 knotvector_type = "Interpolated" 

168 

169 for i in range(nurbs_patch.polynomial_orders[dir_manifold] - 1): 

170 if (abs(knotvector[i] - knotvector[i + 1]) > _bme.eps_knot_vector) or ( 

171 abs(knotvector[num_knots - 2 - i] - knotvector[num_knots - 1 - i]) 

172 > _bme.eps_knot_vector 

173 ): 

174 knotvector_type = "Periodic" 

175 break 

176 

177 patch_data["KNOT_VECTORS"].append( 

178 { 

179 "DEGREE": nurbs_patch.polynomial_orders[dir_manifold], 

180 "TYPE": knotvector_type, 

181 "KNOTS": [ 

182 knot_vector_val 

183 for knot_vector_val in nurbs_patch.knot_vectors[dir_manifold] 

184 ], 

185 } 

186 ) 

187 

188 if "STRUCTURE KNOTVECTORS" in input_file: 

189 # Get all existing patches in the input file - they will be added to the 

190 # input file again at the end of this function. By doing it this way, the 

191 # FourCIPP type converter will be applied to the current patch. 

192 # This also means that we apply the type converter again already existing 

193 # patches. But, with the usual number of patches and data size, this 

194 # should not lead to a measurable performance impact. 

195 patches = input_file.pop("STRUCTURE KNOTVECTORS")["PATCHES"] 

196 else: 

197 patches = [] 

198 

199 patch_data["ID"] = nurbs_patch.i_nurbs_patch + 1 

200 patches.append(patch_data) 

201 input_file.add({"STRUCTURE KNOTVECTORS": {"PATCHES": patches}}) 

202 

203 

204def dump_nurbs_patch_elements(nurbs_patch: _NURBSPatch) -> list[dict[str, _Any]]: 

205 """Return a list with all the element definitions contained in this 

206 patch.""" 

207 

208 if nurbs_patch.i_global is None: 

209 raise ValueError( 

210 "i_global is not set, make sure that the NURBS patch is added to the mesh" 

211 ) 

212 

213 # Check the material 

214 nurbs_patch._check_material() 

215 

216 patch_elements = [] 

217 j = 0 

218 

219 for knot_span in nurbs_patch.get_knot_span_iterator(): 

220 element_cps_ids = nurbs_patch.get_ids_ctrlpts(*knot_span) 

221 connectivity = [nurbs_patch.nodes[i] for i in element_cps_ids] 

222 num_cp = len(connectivity) 

223 

224 patch_elements.append( 

225 { 

226 "id": nurbs_patch.i_global + j + 1, 

227 "cell": { 

228 "type": f"NURBS{num_cp}", 

229 "connectivity": connectivity, 

230 }, 

231 "data": { 

232 "type": _INPUT_FILE_MAPPINGS["nurbs_type_to_default_four_c_type"][ 

233 type(nurbs_patch) 

234 ], 

235 "MAT": nurbs_patch.material, 

236 **(nurbs_patch.data if nurbs_patch.data else {}), 

237 }, 

238 } 

239 ) 

240 j += 1 

241 

242 return patch_elements 

243 

244 

245def dump_item_to_list(dumped_list, item) -> None: 

246 """General function to dump items to a 4C input file.""" 

247 if hasattr(item, "dump_to_list"): 

248 dumped_list.append(item.dump_to_list()) 

249 elif isinstance(item, _Node): 

250 dumped_list.append(dump_node(item)) 

251 elif isinstance(item, _VolumeElement): 

252 dumped_list.append(dump_solid_element(item)) 

253 elif isinstance(item, _GeometrySet) or isinstance(item, _GeometrySetNodes): 

254 dumped_list.extend(dump_geometry_set(item)) 

255 elif isinstance(item, _NURBSPatch): 

256 dumped_list.extend(dump_nurbs_patch_elements(item)) 

257 elif isinstance(item, _BoundaryCondition): 

258 if item.geometry_set.i_global is None: 

259 raise ValueError("i_global is not set") 

260 dumped_list.append( 

261 { 

262 "E": item.geometry_set.i_global + 1, 

263 **item.data, 

264 } 

265 ) 

266 elif isinstance(item, _Coupling): 

267 dumped_list.append(dump_coupling(item)) 

268 else: 

269 raise TypeError(f"Could not dump {item}") 

270 

271 

272def dump_item_to_section(input_file, item) -> None: 

273 """This function dumps information of mesh items to general input file 

274 sections, e.g., knotvectors for NURBS.""" 

275 if isinstance(item, _NURBSPatch): 

276 dump_nurbs_patch_knotvectors(input_file, item)