Coverage for src/beamme/four_c/model_importer.py: 93%
261 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 15:20 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 15:20 +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 contains functions to load and parse existing 4C input files."""
24import tempfile as _tempfile
25from collections import defaultdict as _defaultdict
26from pathlib import Path as _Path
28import numpy as _np
30from beamme.core.boundary_condition import BoundaryCondition as _BoundaryCondition
31from beamme.core.boundary_condition import (
32 BoundaryConditionBase as _BoundaryConditionBase,
33)
34from beamme.core.conf import Geometry as _Geometry
35from beamme.core.conf import bme as _bme
36from beamme.core.coupling import Coupling as _Coupling
37from beamme.core.element import Element as _Element
38from beamme.core.geometry_set import GeometrySetNodes as _GeometrySetNodes
39from beamme.core.mesh import Mesh as _Mesh
40from beamme.core.mesh_representation import (
41 MESH_REPRESENTATION_MAPPINGS as _MESH_REPRESENTATION_MAPPINGS,
42)
43from beamme.core.mesh_representation import GeometrySetInfo as _GeometrySetInfo
44from beamme.core.mesh_representation import MeshRepresentation as _MeshRepresentation
45from beamme.core.mesh_representation import (
46 string_to_geometry_set_info as _string_to_geometry_set_info,
47)
48from beamme.core.node import Node as _Node
49from beamme.four_c.element_data import FourCElementData as _FourCElementData
50from beamme.four_c.element_data import (
51 four_c_element_data_from_exo_dict as _four_c_element_data_from_exo_dict,
52)
53from beamme.four_c.element_data import (
54 four_c_element_data_from_yaml_dict as _four_c_element_data_from_yaml_dict,
55)
56from beamme.four_c.element_solid import get_four_c_solid as _get_four_c_solid
57from beamme.four_c.input_file import InputFile as _InputFile
58from beamme.four_c.input_file_mappings import (
59 INPUT_FILE_MAPPINGS as _INPUT_FILE_MAPPINGS,
60)
61from beamme.four_c.material import MaterialSolid as _MaterialSolid
62from beamme.utils.data_structures import (
63 create_inverse_mapping as _create_inverse_mapping,
64)
65from beamme.utils.environment import cubitpy_is_available as _cubitpy_is_available
67if _cubitpy_is_available():
68 import netCDF4 as _netCDF4
69 from cubitpy.conf import cupy as _cupy
70 from cubitpy.cubit_utility import (
71 string_to_node_set_info as _string_to_node_set_info,
72 )
73 from cubitpy.exodus_utility import get_exo_info as _get_exo_info
76class UniqueDataTracker:
77 """Helper class to track unique data dictionaries and assign IDs to them.
79 When importing input files, we need to identify elements of the same type. The type
80 information is given in dictionaries. This class provides a tracker that can be
81 queried with a given element data and return an already matching element type ID or
82 create a new one.
83 """
85 def __init__(self) -> None:
86 self.unique_id_to_data: dict[int, _FourCElementData] = {}
88 def get_unique_id(self, data: _FourCElementData) -> int:
89 """Get the unique ID for the given data.
91 If the data has not been seen before, a new ID will be assigned to it.
93 Args:
94 data: The data dictionary to get the ID for.
96 Returns:
97 The unique ID for the given data.
98 """
99 for unique_id, seen_data in self.unique_id_to_data.items():
100 if data == seen_data:
101 return unique_id
103 # If we reach this point, the data has not been seen before. We assign a new ID to it.
104 new_unique_id = len(self.unique_id_to_data)
105 self.unique_id_to_data[new_unique_id] = data
106 return new_unique_id
109def import_cubitpy_model(
110 cubit, convert_input_to_mesh: bool = False
111) -> tuple[_InputFile, _Mesh]:
112 """Convert a CubitPy instance to a BeamMe InputFile.
114 Args:
115 cubit (CubitPy): An instance of a cubit model.
116 convert_input_to_mesh: If this is false, the cubit model will be
117 converted to plain FourCIPP input data. If this is true, an input
118 file with all the parameters will be returned and a mesh which
119 contains the mesh information from cubit converted to BeamMe
120 objects.
122 Returns:
123 A tuple with the input file and the mesh. If convert_input_to_mesh is
124 False, the mesh will be empty. Note that the input sections which are
125 converted to a BeamMe mesh are removed from the input file object.
126 """
127 temp_dir: str | _Path
128 with _tempfile.TemporaryDirectory() as temp_dir:
129 temp_dir = _Path(temp_dir)
130 input_file_path = temp_dir / "temp_cubit_input_file.4C.yaml"
131 cubit.dump(
132 input_file_path, mesh_in_exo=True, mesh_in_exo_add_node_set_info=True
133 )
134 return import_four_c_model(
135 input_file_path, convert_input_to_mesh=convert_input_to_mesh
136 )
139def import_four_c_model(
140 input_file_path: _Path, convert_input_to_mesh: bool = False
141) -> tuple[_InputFile, _Mesh]:
142 """Import an existing 4C input file and optionally convert it into a BeamMe mesh.
144 Args:
145 input_file_path: A file path to an existing 4C input file that will be
146 imported.
147 convert_input_to_mesh: If True, the input file will be converted to a
148 BeamMe mesh.
150 Returns:
151 A tuple with the input file and the mesh. If convert_input_to_mesh is
152 False, the mesh will be empty. Note that the input sections which are
153 converted to a BeamMe mesh are removed from the input file object.
154 """
155 input_file = _InputFile().from_4C_yaml(input_file_path=input_file_path)
156 base_path = input_file_path.parent
158 if input_file.contains_mesh_based_geometry_exodus():
159 (
160 mesh_representation,
161 element_type_id_to_data,
162 node_set_id_mesh_representation_to_input_file,
163 ) = _extract_mesh_representation_from_exo(input_file, base_path)
165 elif convert_input_to_mesh:
166 (
167 mesh_representation,
168 element_type_id_to_data,
169 node_set_id_mesh_representation_to_input_file,
170 ) = _extract_mesh_representation(input_file)
172 else:
173 mesh_representation = None
174 element_type_id_to_data = None
176 if convert_input_to_mesh:
177 return _create_mesh_from_mesh_representation(
178 input_file,
179 mesh_representation,
180 element_type_id_to_data,
181 node_set_id_mesh_representation_to_input_file,
182 )
183 else:
184 if mesh_representation is not None:
185 input_file.mesh_representation = mesh_representation
186 if element_type_id_to_data is not None:
187 input_file.element_type_id_to_data = element_type_id_to_data
188 return input_file, _Mesh()
191def _extract_mesh_representation(
192 input_file: _InputFile,
193) -> tuple[_MeshRepresentation, dict[int, _FourCElementData], dict[int, int]]:
194 """Extract the mesh representation from mesh data directly contained in the input
195 file.
197 This will do an inplace removal of the mesh data from the provided input file.
199 Args:
200 input_file: The input file containing the mesh data, will be modified in place.
202 Returns:
203 A tuple containing:
204 - `mesh_representation`: Contains the mesh data extracted from the input file.
205 - `element_type_id_to_data`: A mapping between the element type ID and the
206 element data.
207 - `node_set_id_mesh_representation_to_input_file`: A mapping that can be used
208 to map the IDs in the mesh representation to the IDs in the input file.
209 """
210 # extract nodes
211 nodes = input_file.pop("NODE COORDS", [])
212 n_nodes = len(nodes)
213 points = _np.zeros((n_nodes, 3))
214 point_types = _np.full(n_nodes, -1)
215 control_point_weights = _np.full(n_nodes, -1.0)
216 for i, node in enumerate(nodes):
217 four_c_node_type = node["data"]["type"]
218 node_id = node["id"]
219 try:
220 node_type = _INPUT_FILE_MAPPINGS["four_c_node_type_to_beamme_node_type"][
221 four_c_node_type
222 ]
223 except KeyError:
224 raise ValueError(
225 f"Unknown node type `{four_c_node_type}` for node {node_id}."
226 )
227 points[i] = node["COORD"]
228 point_types[i] = node_type.value
229 if node_type == _bme.node_type.control_point:
230 control_point_weights[i] = node["data"]["weight"]
232 # extract elements
233 element_type_tracker = UniqueDataTracker()
234 elements = input_file.pop("STRUCTURE ELEMENTS", [])
235 n_elements = len(elements)
236 cell_connectivity = []
237 cell_types = _np.full(n_elements, -1)
238 cell_element_type_ids = _np.full(n_elements, -1)
239 cell_material_ids = _np.full(n_elements, -1)
240 for i_element, input_element in enumerate(elements):
241 four_c_element_data, element_id, connectivity, material_id = (
242 _four_c_element_data_from_yaml_dict(input_element)
243 )
244 element_type_id = element_type_tracker.get_unique_id(four_c_element_data)
246 # Check if connectivity has to be reordered
247 reorder_indices = _INPUT_FILE_MAPPINGS[
248 "four_c_cell_to_vtk_connectivity_mapping"
249 ].get(four_c_element_data.four_c_cell, None)
250 if reorder_indices is not None:
251 connectivity = connectivity[reorder_indices]
253 cell_connectivity.extend([len(connectivity), *connectivity.tolist()])
255 try:
256 vtk_cell_type = _INPUT_FILE_MAPPINGS["four_c_cell_to_vtk_cell_type"][
257 four_c_element_data.four_c_cell
258 ]
259 except KeyError:
260 raise ValueError(
261 f"Unknown cell type `{four_c_element_data.four_c_cell}` for element {element_id}."
262 )
264 cell_types[i_element] = vtk_cell_type
265 cell_element_type_ids[i_element] = element_type_id
266 cell_material_ids[i_element] = material_id
268 # extract geometry sets
269 node_sets: list[_GeometrySetInfo] = []
270 node_set_id_mesh_representation_to_input_file: dict[int, int] = {}
271 for section_name in input_file.sections:
272 if not section_name.endswith("TOPOLOGY"):
273 continue
275 items = input_file.pop(section_name, [])
276 if not items:
277 continue
279 # Find geometry type for this section
280 try:
281 geometry_type = _INPUT_FILE_MAPPINGS[
282 "geometry_sets_condition_to_geometry_name"
283 ][section_name]
284 except KeyError as e:
285 raise ValueError(f"Unknown geometry section: {section_name}") from e
287 # Extract geometry set indices
288 geom_dict: dict[int, set[int]] = _defaultdict(set)
289 for entry in items:
290 geom_dict[entry["d_id"]].add(entry["node_id"] - 1)
292 for input_file_node_set_id, node_ids in geom_dict.items():
293 node_set_id = len(node_sets)
295 node_set_flag = _np.zeros(n_nodes, dtype=int)
296 node_set_flag[list(node_ids)] = 1
298 node_sets.append(
299 _GeometrySetInfo(
300 geometry_type=geometry_type,
301 i_global=node_set_id,
302 point_flag_vector=node_set_flag,
303 )
304 )
306 node_set_id_mesh_representation_to_input_file[node_set_id] = (
307 input_file_node_set_id
308 )
310 # Create the mesh representation and add the extracted data to it.
311 mesh_representation = _MeshRepresentation(
312 cell_connectivity=cell_connectivity,
313 cell_types=cell_types,
314 points=points,
315 geometry_sets=node_sets,
316 point_data={
317 "point_type": point_types,
318 "control_point_weight": control_point_weights,
319 },
320 cell_data={
321 "element_type_id": cell_element_type_ids,
322 "material_id": cell_material_ids,
323 },
324 )
326 return (
327 mesh_representation,
328 element_type_tracker.unique_id_to_data,
329 node_set_id_mesh_representation_to_input_file,
330 )
333def _get_exodus_path_from_input_file(input_file: _InputFile, base_path: _Path) -> _Path:
334 """Returns the path to the exodus file linked in the input file.
336 Args:
337 input_file: The input file to extract the exodus file path from.
338 base_path: The base path for loading the exodus file.
340 Returns:
341 The path to the exodus file linked in the input file.
342 """
343 if "STRUCTURE GEOMETRY" in input_file.fourc_input:
344 structure_geometry_section = input_file.fourc_input["STRUCTURE GEOMETRY"]
345 if "FILE" in structure_geometry_section:
346 exodus_file_name = _Path(structure_geometry_section["FILE"])
347 if exodus_file_name.suffix.lower() in [".exo", ".e"]:
348 exodus_file_path = base_path / exodus_file_name
349 if not exodus_file_path.is_file():
350 raise FileNotFoundError(
351 "The input file contains a link to an external mesh file "
352 f"{exodus_file_name}, but this file does not exist at the expected "
353 f"location {exodus_file_path}."
354 )
355 return exodus_file_path
356 else:
357 raise ValueError(
358 "The input file contains a link to an external file "
359 f"{exodus_file_name}, with the extension {exodus_file_name.suffix}, but only "
360 ".exo and .e files are supported."
361 )
362 else:
363 raise ValueError(
364 "The input file contains a STRUCTURE GEOMETRY section, but no "
365 "FILE entry."
366 )
367 else:
368 raise ValueError("The input file does not contain a STRUCTURE GEOMETRY section")
371def _extract_mesh_representation_from_exo(
372 input_file: _InputFile, base_path: _Path
373) -> tuple[_MeshRepresentation, dict[int, _FourCElementData], dict[int, int]]:
374 """Extract the mesh representation from mesh data in exodus format.
376 This will do an inplace removal of the extracted sections in the provided input file.
378 Args:
379 input_file: The input file containing the mesh data, will be modified in place.
381 Returns:
382 A tuple (mesh_representation, element_type_id_to_data, node_set_id_mesh_representation_to_input_file).
383 - `mesh_representation`: Contains the mesh data extracted from the input file.
384 - `element_type_id_to_data`: A mapping between the element type ID and the element data.
385 - `node_set_id_mesh_representation_to_input_file`: A mapping that can be used to map the
386 geometry set IDs in the mesh representation to the ones in the input file.
387 """
388 # Load the exodus file.
389 with _netCDF4.Dataset(
390 _get_exodus_path_from_input_file(input_file, base_path)
391 ) as exo:
392 # Read the coordinate array.
393 if "coordz" not in exo.variables:
394 raise ValueError(
395 "The exodus file provides only 2D coordinates, this is not supported."
396 )
397 coordinates = _np.array(
398 [exo.variables["coord" + dim][:] for dim in ["x", "y", "z"]],
399 ).transpose()
400 n_points = coordinates.shape[0]
401 point_types = _np.full(n_points, _bme.node_type.node.value)
403 # Remove the structure geometry section from the input file and extract the
404 # element blocks.
405 element_blocks = input_file.pop("STRUCTURE GEOMETRY")["ELEMENT_BLOCKS"]
406 cubit_id_to_element_blocks = {block["ID"]: block for block in element_blocks}
408 # Add the element connectivity
409 element_type_tracker = UniqueDataTracker()
410 block_connectivity_list = []
411 cell_types = []
412 cell_element_type_ids = []
413 cell_material_ids = []
414 _, exo_block_id_to_info = _get_exo_info(exo, "block")
415 for exo_id in sorted(exo_block_id_to_info.keys()):
416 # First, get the element block ID in beamme and the corresponding element data.
417 info = exo_block_id_to_info[exo_id]
418 element_data = cubit_id_to_element_blocks[info["cubit_id"]]
419 four_c_element_data, material_id = _four_c_element_data_from_exo_dict(
420 element_data
421 )
422 element_type_id = element_type_tracker.get_unique_id(four_c_element_data)
424 # Get the connectivity information for this block and if necessary
425 # reorder it to match the VTK node ordering.
426 connectivity = exo.variables[f"connect{exo_id + 1}"][:] - 1
427 n_elements_in_block = connectivity.shape[0]
428 n_nodes_per_element = connectivity.shape[1]
429 if (
430 not n_nodes_per_element
431 == _INPUT_FILE_MAPPINGS["four_c_cell_to_element_type_and_n_nodes"][
432 four_c_element_data.four_c_cell
433 ][1]
434 ):
435 raise ValueError(
436 f"Number of nodes per element {n_nodes_per_element} in block with "
437 f"ID {info['cubit_id']} does not match expected number of nodes for "
438 f"cell type {four_c_element_data.four_c_cell}."
439 )
440 reordering = _MESH_REPRESENTATION_MAPPINGS[
441 "connectivity_mapping_exodus_to_vtk"
442 ].get(n_nodes_per_element, None)
443 if reordering is not None:
444 connectivity = connectivity[:, reordering]
446 # Add the data for this element block to the cell data lists.
447 cell_material_ids.extend([material_id] * n_elements_in_block)
448 cell_element_type_ids.extend([element_type_id] * n_elements_in_block)
449 cell_types.extend(
450 [
451 _INPUT_FILE_MAPPINGS["four_c_cell_to_vtk_cell_type"][
452 four_c_element_data.four_c_cell
453 ]
454 ]
455 * n_elements_in_block
456 )
457 block_connectivity = _np.empty(
458 (n_elements_in_block, n_nodes_per_element + 1), dtype=int
459 )
460 block_connectivity[:, 0] = n_nodes_per_element
461 block_connectivity[:, 1:] = connectivity
462 block_connectivity_list.append(block_connectivity.ravel())
463 cell_connectivity = _np.concatenate(block_connectivity_list)
465 # Extract the node sets.
466 cubitpy_geometry_type_to_beamme = {
467 _cupy.geometry.vertex: _bme.geo.point,
468 _cupy.geometry.curve: _bme.geo.line,
469 _cupy.geometry.surface: _bme.geo.surface,
470 _cupy.geometry.volume: _bme.geo.volume,
471 }
472 node_set_id_mesh_representation_to_input_file = {}
473 geometry_sets: list[_GeometrySetInfo] = []
474 _, exo_node_set_id_to_info = _get_exo_info(exo, "nodeset")
475 for exo_id in sorted(exo_node_set_id_to_info.keys()):
476 exo_name = exo_node_set_id_to_info[exo_id]["name"]
477 cubit_id, geometry_type_cubitpy, name = _string_to_node_set_info(exo_name)
479 node_set_flag = _np.zeros(n_points, dtype=int)
480 node_set_flag[exo.variables[f"node_ns{exo_id + 1}"][:] - 1] = 1
482 mesh_representation_id = len(geometry_sets)
483 node_set_id_mesh_representation_to_input_file[mesh_representation_id] = (
484 cubit_id
485 )
486 geometry_sets.append(
487 _GeometrySetInfo(
488 geometry_type=cubitpy_geometry_type_to_beamme[
489 geometry_type_cubitpy
490 ],
491 i_global=mesh_representation_id,
492 point_flag_vector=node_set_flag,
493 name=name,
494 )
495 )
497 # Remove the entries in the boundary condition definitions in the input file that
498 # are exodus specific. Also, set the geometry set IDs in the boundary conditions to
499 # the ones in the mesh representation.
500 node_set_id_input_file_to_mesh_representation = _create_inverse_mapping(
501 node_set_id_mesh_representation_to_input_file
502 )
503 for section_name in input_file.sections:
504 if section_name in _INPUT_FILE_MAPPINGS["boundary_conditions"].values():
505 items = input_file.pop(section_name)
506 for bc in items:
507 bc.pop("ENTITY_TYPE")
508 bc["E"] = node_set_id_input_file_to_mesh_representation[bc["E"]] + 1
509 input_file.add({section_name: items})
511 # Create the mesh representation
512 mesh_representation = _MeshRepresentation(
513 cell_connectivity=cell_connectivity,
514 cell_types=cell_types,
515 points=coordinates,
516 geometry_sets=geometry_sets,
517 point_data={"point_type": point_types},
518 cell_data={
519 "element_type_id": cell_element_type_ids,
520 "material_id": cell_material_ids,
521 },
522 )
524 return (
525 mesh_representation,
526 element_type_tracker.unique_id_to_data,
527 {i: i + 1 for i in range(len(node_set_id_mesh_representation_to_input_file))},
528 )
531def _create_mesh_from_mesh_representation(
532 input_file,
533 mesh_representation,
534 element_type_id_to_data,
535 node_set_id_mesh_representation_to_input_file,
536) -> tuple[_InputFile, _Mesh]:
537 """Extract a BeamMe mesh from a mesh representation.
539 Args:
540 input_file: The input file containing general data.
541 mesh_representation: The mesh representation to convert.
542 node_set_id_mesh_representation_to_input_file: A mapping of the mesh
543 representation node set IDs to input file IDs, which can be used to link
544 the geometry sets in the input file to the node sets in the mesh
545 representation.
547 Returns:
548 A tuple (input_file, mesh). The input_file is modified in place to remove
549 sections converted into the BeamMe mesh.
550 """
551 # convert all sections to native objects and add to a new mesh
552 mesh = _Mesh()
554 # extract materials
555 material_id_map = _extract_materials_from_input_file(input_file)
556 mesh.materials.extend(material_id_map.values())
558 # extract nodes
559 for node_coordinates, node_type in zip(
560 mesh_representation.points, mesh_representation.point_data["point_type"]
561 ):
562 if node_type == _bme.node_type.node.value:
563 mesh.nodes.append(_Node(node_coordinates))
564 else:
565 raise ValueError(
566 f"Mesh conversion for node type {_bme.node_type(node_type).name} is not implemented!"
567 )
569 # extract element types
570 element_type_id_to_element_type: dict[int, type[_Element]] = {}
571 for (
572 element_type_id,
573 element_data,
574 ) in element_type_id_to_data.items():
575 element_type, n_nodes = _INPUT_FILE_MAPPINGS[
576 "four_c_cell_to_element_type_and_n_nodes"
577 ][element_data.four_c_cell]
578 if not element_type == _bme.element_type.solid:
579 raise ValueError(
580 f"Mesh conversion for element type {element_type} is not implemented!"
581 )
582 element_type_id_to_element_type[element_type_id] = _get_four_c_solid(
583 element_type,
584 element_data.four_c_type,
585 n_nodes=n_nodes,
586 element_technology=element_data.element_technology,
587 )
589 # loop over the elements and create the mesh elements with the correct type, connectivity and material.
590 for connectivity, cell_element_type_id_, material_id in zip(
591 mesh_representation.connectivity_iterator(),
592 mesh_representation.cell_data["element_type_id"],
593 mesh_representation.cell_data["material_id"],
594 ):
595 element_type = element_type_id_to_element_type[cell_element_type_id_]
597 reorder_indices = _MESH_REPRESENTATION_MAPPINGS[
598 "element_type_and_n_nodes_to_connectivity_mapping_vtk_to_beamme"
599 ].get((element_type.element_type, len(connectivity)), None)
600 if reorder_indices is not None:
601 nodes = [mesh.nodes[connectivity[i]] for i in reorder_indices]
602 else:
603 nodes = [mesh.nodes[i] for i in connectivity]
605 element = element_type(nodes=nodes)
607 if not material_id == -1:
608 element.material = material_id_map[material_id]
609 mesh.elements.append(element)
611 # extract geometry sets
612 geometry_sets_in_sections: dict[_Geometry, dict[int, _GeometrySetNodes]] = (
613 _defaultdict(dict)
614 )
615 for name in mesh_representation.point_data.keys():
616 info = _string_to_geometry_set_info(name)
617 if info is not None:
618 node_indices = _np.nonzero(mesh_representation.point_data[name])[0]
619 geometry_type = info.geometry_type
620 geometry_set = _GeometrySetNodes(
621 geometry_type,
622 nodes=[mesh.nodes[i] for i in node_indices],
623 name=info.name,
624 )
625 input_file_id = node_set_id_mesh_representation_to_input_file[info.i_global]
626 geometry_sets_in_sections[geometry_type][input_file_id] = geometry_set
627 mesh.add(geometry_set)
629 # extract boundary conditions
630 _standard_bc_types = (
631 _bme.bc.dirichlet,
632 _bme.bc.neumann,
633 _bme.bc.locsys,
634 _bme.bc.beam_to_solid_surface_meshtying,
635 _bme.bc.beam_to_solid_surface_contact,
636 _bme.bc.beam_to_solid_volume_meshtying,
637 )
639 for (bc_key, geometry_type), section_name in _INPUT_FILE_MAPPINGS[
640 "boundary_conditions"
641 ].items():
642 for bc_data in input_file.pop(section_name, []):
643 geometry_set = geometry_sets_in_sections[geometry_type][bc_data.pop("E")]
645 bc_obj: _BoundaryConditionBase
647 if bc_key in _standard_bc_types or isinstance(bc_key, str):
648 bc_obj = _BoundaryCondition(geometry_set, bc_data, bc_type=bc_key)
649 elif bc_key is _bme.bc.point_coupling:
650 bc_obj = _Coupling(
651 geometry_set, bc_key, bc_data, check_overlapping_nodes=False
652 )
653 else:
654 raise ValueError(f"Unexpected boundary condition: {bc_key}")
656 mesh.boundary_conditions.append((bc_key, geometry_type), bc_obj)
658 return input_file, mesh
661def _extract_materials_from_input_file(
662 input_file: _InputFile,
663) -> dict[int, _MaterialSolid]:
664 """Extract all materials from the input file and convert them to BeamMe materials.
666 Args:
667 input_file: The input file containing the material sections.
669 Returns:
670 A mapping of material IDs to BeamMe material objects.
671 """
672 material_id_map_all = {}
674 for mat in input_file.pop("MATERIALS", []):
675 mat_id = mat.pop("MAT") - 1
676 if len(mat) != 1:
677 raise ValueError(
678 f"Could not convert the material data `{mat}` to a BeamMe material!"
679 )
680 mat_name, mat_data = list(mat.items())[0]
681 material = _MaterialSolid(material_string=mat_name, data=mat_data)
682 material_id_map_all[mat_id] = material
684 nested_materials = set()
685 for material in material_id_map_all.values():
686 # Replace the integer IDs in the "MATIDS" list of the material with the actual
687 # material objects.
688 sub_materials = material.data.get("MATIDS", [])
689 sub_material_ids = _np.array(sub_materials) - 1
690 for i_sub_material, sub_material_id in enumerate(sub_material_ids):
691 try:
692 sub_materials[i_sub_material] = material_id_map_all[sub_material_id]
693 except KeyError as key_exception:
694 raise KeyError(
695 f"Material ID {sub_material_id} not in material_id_map_all (available "
696 f"IDs: {list(material_id_map_all.keys())})."
697 ) from key_exception
698 nested_materials.add(sub_material_id)
700 # Get a map of all non-nested materials. We assume that only those are used as
701 # materials for elements. Also, add the non-nested materials to the mesh.
702 material_id_map = {
703 key: val
704 for key, val in material_id_map_all.items()
705 if key not in nested_materials
706 }
708 return material_id_map