Coverage for src/beamme/four_c/input_file.py: 83%
98 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 defines the classes that are used to create an input file for
234C."""
25import os as _os
26from datetime import datetime as _datetime
27from pathlib import Path as _Path
28from typing import Any as _Any
29from typing import Callable as _Callable
31from fourcipp.fourc_input import FourCInput as _FourCInput
32from fourcipp.fourc_input import sort_by_section_names as _sort_by_section_names
33from fourcipp.utils.not_set import NOT_SET as _NOT_SET
35from beamme.core.conf import INPUT_FILE_HEADER as _INPUT_FILE_HEADER
36from beamme.core.mesh import Mesh as _Mesh
37from beamme.core.mesh_representation import MeshRepresentation as _MeshRepresentation
38from beamme.four_c.element_data import FourCElementData as _FourCElementData
39from beamme.four_c.input_file_dump_functions import (
40 dump_mesh_representation_to_input_file_legacy as _dump_mesh_representation_to_input_file_legacy,
41)
42from beamme.four_c.input_file_dump_functions import (
43 dump_mesh_to_input_file as _dump_mesh_to_input_file,
44)
45from beamme.utils.environment import cubitpy_is_available as _cubitpy_is_available
46from beamme.utils.environment import get_application_path as _get_application_path
47from beamme.utils.environment import get_git_data as _get_git_data
49if _cubitpy_is_available():
50 import cubitpy as _cubitpy
53class InputFile:
54 """An item that represents a complete 4C input file."""
56 def __init__(self) -> None:
57 """Initialize the input file."""
59 self.fourc_input = _FourCInput()
61 # Register converters to directly convert non-primitive types
62 # to native Python types via the FourCIPP type converter.
63 self.fourc_input.type_converter.register_numpy_types()
65 # Contents of NOX xml file.
66 self.nox_xml_contents = ""
68 # Mesh representation for this input file.
69 self.mesh_representation = _MeshRepresentation()
70 self.element_type_id_to_data: dict[_Any, _FourCElementData] = {}
72 def __contains__(self, key: str) -> bool:
73 """Contains function.
75 Allows to use the `in` operator.
77 Args:
78 key: Section name to check if it is set
80 Returns:
81 True if section is set
82 """
84 return key in self.fourc_input
86 def __setitem__(self, key: str, value: _Any) -> None:
87 """Set section.
89 Args:
90 key: Section name
91 value: Section entry
92 """
94 self.fourc_input[key] = value
96 def __getitem__(self, key: str) -> _Any:
97 """Get section of input file.
99 Allows to use the indexing operator.
101 Args:
102 key: Section name to get
104 Returns:
105 The section content
106 """
108 return self.fourc_input[key]
110 @classmethod
111 def from_4C_yaml(
112 cls, input_file_path: str | _Path, header_only: bool = False
113 ) -> "InputFile":
114 """Load 4C yaml file.
116 Args:
117 input_file_path: Path to yaml file
118 header_only: Only extract header, i.e., all sections except the legacy ones
120 Returns:
121 Initialised object
122 """
124 obj = cls()
125 obj.fourc_input = _FourCInput.from_4C_yaml(input_file_path, header_only)
126 return obj
128 @property
129 def sections(self) -> dict:
130 """All the set sections.
132 Returns:
133 dict: Set sections
134 """
136 return self.fourc_input.sections
138 def pop(self, key: str, default_value: _Any = _NOT_SET) -> _Any:
139 """Pop section of input file.
141 Args:
142 key: Section name to pop
144 Returns:
145 The section content
146 """
148 return self.fourc_input.pop(key, default_value)
150 def add(self, object_to_add, **kwargs):
151 """Add a mesh or a dictionary to the input file.
153 Args:
154 object: The object to be added. This can be a mesh or a dictionary.
155 **kwargs: Additional arguments to be passed to the add method.
156 """
158 if isinstance(object_to_add, _Mesh):
159 _dump_mesh_to_input_file(self, mesh=object_to_add, **kwargs)
161 else:
162 self.fourc_input.combine_sections(object_to_add)
164 def get_fourcipp_input_with_mesh(self) -> _FourCInput:
165 """Return a copy of the FourCIPP input file with the contents of the
166 mesh representation dumped to the legacy sections."""
167 fourc_input = self.fourc_input.copy()
168 _dump_mesh_representation_to_input_file_legacy(
169 fourc_input,
170 self.mesh_representation,
171 self.element_type_id_to_data,
172 )
173 return fourc_input
175 def dump(
176 self,
177 input_file_path: str | _Path,
178 *,
179 nox_xml_file: str | None = None,
180 add_header_default: bool = True,
181 add_header_information: bool = True,
182 add_footer_application_script: bool = True,
183 validate=True,
184 validate_sections_only: bool = False,
185 sort_function: _Callable[[dict], dict] | None = _sort_by_section_names,
186 fourcipp_yaml_style: bool = True,
187 ):
188 """Write the input file to disk.
190 Args:
191 input_file_path:
192 Path to the input file that should be created.
193 nox_xml_file:
194 If this is a string, the NOX xml file will be created with this
195 name. If this is None, the NOX xml file will be created with the
196 name of the input file with the extension ".nox.xml".
197 add_header_default:
198 Prepend the default header comment to the input file.
199 add_header_information:
200 If the information header should be exported to the input file
201 Contains creation date, git details of BeamMe, CubitPy and
202 original application which created the input file if available.
203 add_footer_application_script:
204 Append the application script which creates the input files as a
205 comment at the end of the input file.
206 validate:
207 Validate if the created input file is compatible with 4C with FourCIPP.
208 validate_sections_only:
209 Validate each section independently. Required sections are no longer
210 required, but the sections must be valid.
211 sort_function:
212 A function which sorts the sections of the input file.
213 fourcipp_yaml_style:
214 If True, the input file is written in the fourcipp yaml style.
215 """
217 # Make sure the given input file is a Path instance.
218 input_file_path = _Path(input_file_path)
220 # Create a deep copy of the existing input sections - this function should not alter
221 # the present instance of InputFile
222 fourc_input = self.fourc_input.copy()
224 if self.nox_xml_contents:
225 if nox_xml_file is None:
226 nox_xml_file = input_file_path.name.split(".")[0] + ".nox.xml"
228 fourc_input["STRUCT NOX/Status Test"] = {"XML File": nox_xml_file}
230 # Write the xml file to the disc.
231 with open(input_file_path.parent / nox_xml_file, "w") as xml_file:
232 xml_file.write(self.nox_xml_contents)
234 # Dump the mesh representation.
235 _dump_mesh_representation_to_input_file_legacy(
236 fourc_input,
237 self.mesh_representation,
238 self.element_type_id_to_data,
239 )
241 # Add information header to the input file
242 if add_header_information:
243 fourc_input.combine_sections({"TITLE": self._get_header()})
245 fourc_input.dump(
246 input_file_path=input_file_path,
247 validate=validate,
248 validate_sections_only=validate_sections_only,
249 convert_to_native_types=False, # conversion already happens during add()
250 sort_function=sort_function,
251 use_fourcipp_yaml_style=fourcipp_yaml_style,
252 )
254 if add_header_default or add_footer_application_script:
255 with open(input_file_path, "r") as input_file:
256 lines = input_file.readlines()
258 if add_header_default:
259 lines = ["# " + line + "\n" for line in _INPUT_FILE_HEADER] + lines
261 if add_footer_application_script:
262 application_path = _get_application_path()
263 if application_path is not None:
264 lines += self._get_application_script(application_path)
266 with open(input_file_path, "w") as input_file:
267 input_file.writelines(lines)
269 def _get_header(self) -> dict:
270 """Return the information header for the current BeamMe run.
272 Returns:
273 A dictionary with the header information.
274 """
276 header: dict = {"BeamMe": {}}
278 header["BeamMe"]["creation_date"] = _datetime.now().isoformat(
279 sep=" ", timespec="seconds"
280 )
282 # application which created the input file
283 application_path = _get_application_path()
284 if application_path is not None:
285 header["BeamMe"]["Application"] = {"path": str(application_path)}
287 application_git_sha, application_git_date = _get_git_data(
288 application_path.parent
289 )
290 if application_git_sha is not None and application_git_date is not None:
291 header["BeamMe"]["Application"].update(
292 {
293 "git_sha": application_git_sha,
294 "git_date": application_git_date,
295 }
296 )
298 # BeamMe information
299 beamme_git_sha, beamme_git_date = _get_git_data(
300 _Path(__file__).resolve().parent
301 )
302 if beamme_git_sha is not None and beamme_git_date is not None:
303 header["BeamMe"]["BeamMe"] = {
304 "git_SHA": beamme_git_sha,
305 "git_date": beamme_git_date,
306 }
308 # CubitPy information
309 if _cubitpy_is_available():
310 cubitpy_git_sha, cubitpy_git_date = _get_git_data(
311 _os.path.dirname(_cubitpy.__file__)
312 )
314 if cubitpy_git_sha is not None and cubitpy_git_date is not None:
315 header["BeamMe"]["CubitPy"] = {
316 "git_SHA": cubitpy_git_sha,
317 "git_date": cubitpy_git_date,
318 }
320 return header
322 def _get_application_script(self, application_path: _Path) -> list[str]:
323 """Get the script that created this input file.
325 Args:
326 application_path: Path to the script that created this input file.
327 Returns:
328 A list of strings with the script that created this input file.
329 """
331 application_script_lines = [
332 "# Application script which created this input file:\n"
333 ]
335 with open(application_path) as script_file:
336 application_script_lines.extend("# " + line for line in script_file)
338 return application_script_lines
340 def contains_external_mesh_based_geometry(self) -> bool:
341 """Check if the input file contains external mesh-based geometry.
343 Returns:
344 True if the input file contains external mesh-based geometry, False otherwise.
345 """
347 return False