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
« 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."""
24from typing import Any as _Any
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)
44def dump_node(node):
45 """Return the representation of a node in the 4C input file."""
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)}")
63def dump_solid_element(solid_element):
64 """Return a dict with the items representing the given solid element."""
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 )
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 }
90def dump_coupling(coupling):
91 """Return the input file representation of the coupling condition."""
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 )
126 data = element_type.get_coupling_dict(coupling.data)
128 return {"E": coupling.geometry_set.i_global + 1, **data}
131def dump_geometry_set(geometry_set):
132 """Return a list with the data describing this set."""
134 # Sort nodes based on their global index
135 nodes = sorted(geometry_set.get_all_nodes(), key=lambda n: n.i_global)
137 if not nodes:
138 raise ValueError("Writing empty geometry sets is not supported")
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 ]
153def dump_nurbs_patch_knotvectors(input_file, nurbs_patch) -> None:
154 """Set the knot vectors of the NURBS patch in the input file."""
156 patch_data: dict[str, _Any] = {
157 "KNOT_VECTORS": [],
158 }
160 for dir_manifold in range(nurbs_patch.get_nurbs_dimension()):
161 knotvector = nurbs_patch.knot_vectors[dir_manifold]
162 num_knots = len(knotvector)
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"
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
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 )
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 = []
199 patch_data["ID"] = nurbs_patch.i_nurbs_patch + 1
200 patches.append(patch_data)
201 input_file.add({"STRUCTURE KNOTVECTORS": {"PATCHES": patches}})
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."""
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 )
213 # Check the material
214 nurbs_patch._check_material()
216 patch_elements = []
217 j = 0
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)
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
242 return patch_elements
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}")
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)