beamme.four_c.model_importer
This module contains functions to load and parse existing 4C input files.
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.""" 23 24import tempfile as _tempfile 25from collections import defaultdict as _defaultdict 26from pathlib import Path as _Path 27 28import numpy as _np 29 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 66 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 74 75 76class UniqueDataTracker: 77 """Helper class to track unique data dictionaries and assign IDs to them. 78 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 """ 84 85 def __init__(self) -> None: 86 self.unique_id_to_data: dict[int, _FourCElementData] = {} 87 88 def get_unique_id(self, data: _FourCElementData) -> int: 89 """Get the unique ID for the given data. 90 91 If the data has not been seen before, a new ID will be assigned to it. 92 93 Args: 94 data: The data dictionary to get the ID for. 95 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 102 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 107 108 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. 113 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. 121 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 ) 137 138 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. 143 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. 149 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 157 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) 164 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) 171 172 else: 173 mesh_representation = None 174 element_type_id_to_data = None 175 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() 189 190 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. 196 197 This will do an inplace removal of the mesh data from the provided input file. 198 199 Args: 200 input_file: The input file containing the mesh data, will be modified in place. 201 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"] 231 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) 245 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] 252 253 cell_connectivity.extend([len(connectivity), *connectivity.tolist()]) 254 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 ) 263 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 267 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 274 275 items = input_file.pop(section_name, []) 276 if not items: 277 continue 278 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 286 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) 291 292 for input_file_node_set_id, node_ids in geom_dict.items(): 293 node_set_id = len(node_sets) 294 295 node_set_flag = _np.zeros(n_nodes, dtype=int) 296 node_set_flag[list(node_ids)] = 1 297 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 ) 305 306 node_set_id_mesh_representation_to_input_file[node_set_id] = ( 307 input_file_node_set_id 308 ) 309 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 ) 325 326 return ( 327 mesh_representation, 328 element_type_tracker.unique_id_to_data, 329 node_set_id_mesh_representation_to_input_file, 330 ) 331 332 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. 335 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. 339 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") 369 370 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. 375 376 This will do an inplace removal of the extracted sections in the provided input file. 377 378 Args: 379 input_file: The input file containing the mesh data, will be modified in place. 380 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) 402 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} 407 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) 423 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] 445 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) 464 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) 478 479 node_set_flag = _np.zeros(n_points, dtype=int) 480 node_set_flag[exo.variables[f"node_ns{exo_id + 1}"][:] - 1] = 1 481 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 ) 496 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}) 510 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 ) 523 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 ) 529 530 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. 538 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. 546 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() 553 554 # extract materials 555 material_id_map = _extract_materials_from_input_file(input_file) 556 mesh.materials.extend(material_id_map.values()) 557 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 ) 568 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 ) 588 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_] 596 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] 604 605 element = element_type(nodes=nodes) 606 607 if not material_id == -1: 608 element.material = material_id_map[material_id] 609 mesh.elements.append(element) 610 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) 628 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 ) 638 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")] 644 645 bc_obj: _BoundaryConditionBase 646 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}") 655 656 mesh.boundary_conditions.append((bc_key, geometry_type), bc_obj) 657 658 return input_file, mesh 659 660 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. 665 666 Args: 667 input_file: The input file containing the material sections. 668 669 Returns: 670 A mapping of material IDs to BeamMe material objects. 671 """ 672 material_id_map_all = {} 673 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 683 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) 699 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 } 707 708 return material_id_map
77class UniqueDataTracker: 78 """Helper class to track unique data dictionaries and assign IDs to them. 79 80 When importing input files, we need to identify elements of the same type. The type 81 information is given in dictionaries. This class provides a tracker that can be 82 queried with a given element data and return an already matching element type ID or 83 create a new one. 84 """ 85 86 def __init__(self) -> None: 87 self.unique_id_to_data: dict[int, _FourCElementData] = {} 88 89 def get_unique_id(self, data: _FourCElementData) -> int: 90 """Get the unique ID for the given data. 91 92 If the data has not been seen before, a new ID will be assigned to it. 93 94 Args: 95 data: The data dictionary to get the ID for. 96 97 Returns: 98 The unique ID for the given data. 99 """ 100 for unique_id, seen_data in self.unique_id_to_data.items(): 101 if data == seen_data: 102 return unique_id 103 104 # If we reach this point, the data has not been seen before. We assign a new ID to it. 105 new_unique_id = len(self.unique_id_to_data) 106 self.unique_id_to_data[new_unique_id] = data 107 return new_unique_id
Helper class to track unique data dictionaries and assign IDs to them.
When importing input files, we need to identify elements of the same type. The type information is given in dictionaries. This class provides a tracker that can be queried with a given element data and return an already matching element type ID or create a new one.
89 def get_unique_id(self, data: _FourCElementData) -> int: 90 """Get the unique ID for the given data. 91 92 If the data has not been seen before, a new ID will be assigned to it. 93 94 Args: 95 data: The data dictionary to get the ID for. 96 97 Returns: 98 The unique ID for the given data. 99 """ 100 for unique_id, seen_data in self.unique_id_to_data.items(): 101 if data == seen_data: 102 return unique_id 103 104 # If we reach this point, the data has not been seen before. We assign a new ID to it. 105 new_unique_id = len(self.unique_id_to_data) 106 self.unique_id_to_data[new_unique_id] = data 107 return new_unique_id
Get the unique ID for the given data.
If the data has not been seen before, a new ID will be assigned to it.
Arguments:
- data: The data dictionary to get the ID for.
Returns:
The unique ID for the given data.
110def import_cubitpy_model( 111 cubit, convert_input_to_mesh: bool = False 112) -> tuple[_InputFile, _Mesh]: 113 """Convert a CubitPy instance to a BeamMe InputFile. 114 115 Args: 116 cubit (CubitPy): An instance of a cubit model. 117 convert_input_to_mesh: If this is false, the cubit model will be 118 converted to plain FourCIPP input data. If this is true, an input 119 file with all the parameters will be returned and a mesh which 120 contains the mesh information from cubit converted to BeamMe 121 objects. 122 123 Returns: 124 A tuple with the input file and the mesh. If convert_input_to_mesh is 125 False, the mesh will be empty. Note that the input sections which are 126 converted to a BeamMe mesh are removed from the input file object. 127 """ 128 temp_dir: str | _Path 129 with _tempfile.TemporaryDirectory() as temp_dir: 130 temp_dir = _Path(temp_dir) 131 input_file_path = temp_dir / "temp_cubit_input_file.4C.yaml" 132 cubit.dump( 133 input_file_path, mesh_in_exo=True, mesh_in_exo_add_node_set_info=True 134 ) 135 return import_four_c_model( 136 input_file_path, convert_input_to_mesh=convert_input_to_mesh 137 )
Convert a CubitPy instance to a BeamMe InputFile.
Arguments:
- cubit (CubitPy): An instance of a cubit model.
- convert_input_to_mesh: If this is false, the cubit model will be converted to plain FourCIPP input data. If this is true, an input file with all the parameters will be returned and a mesh which contains the mesh information from cubit converted to BeamMe objects.
Returns:
A tuple with the input file and the mesh. If convert_input_to_mesh is False, the mesh will be empty. Note that the input sections which are converted to a BeamMe mesh are removed from the input file object.
140def import_four_c_model( 141 input_file_path: _Path, convert_input_to_mesh: bool = False 142) -> tuple[_InputFile, _Mesh]: 143 """Import an existing 4C input file and optionally convert it into a BeamMe mesh. 144 145 Args: 146 input_file_path: A file path to an existing 4C input file that will be 147 imported. 148 convert_input_to_mesh: If True, the input file will be converted to a 149 BeamMe mesh. 150 151 Returns: 152 A tuple with the input file and the mesh. If convert_input_to_mesh is 153 False, the mesh will be empty. Note that the input sections which are 154 converted to a BeamMe mesh are removed from the input file object. 155 """ 156 input_file = _InputFile().from_4C_yaml(input_file_path=input_file_path) 157 base_path = input_file_path.parent 158 159 if input_file.contains_mesh_based_geometry_exodus(): 160 ( 161 mesh_representation, 162 element_type_id_to_data, 163 node_set_id_mesh_representation_to_input_file, 164 ) = _extract_mesh_representation_from_exo(input_file, base_path) 165 166 elif convert_input_to_mesh: 167 ( 168 mesh_representation, 169 element_type_id_to_data, 170 node_set_id_mesh_representation_to_input_file, 171 ) = _extract_mesh_representation(input_file) 172 173 else: 174 mesh_representation = None 175 element_type_id_to_data = None 176 177 if convert_input_to_mesh: 178 return _create_mesh_from_mesh_representation( 179 input_file, 180 mesh_representation, 181 element_type_id_to_data, 182 node_set_id_mesh_representation_to_input_file, 183 ) 184 else: 185 if mesh_representation is not None: 186 input_file.mesh_representation = mesh_representation 187 if element_type_id_to_data is not None: 188 input_file.element_type_id_to_data = element_type_id_to_data 189 return input_file, _Mesh()
Import an existing 4C input file and optionally convert it into a BeamMe mesh.
Arguments:
- input_file_path: A file path to an existing 4C input file that will be imported.
- convert_input_to_mesh: If True, the input file will be converted to a BeamMe mesh.
Returns:
A tuple with the input file and the mesh. If convert_input_to_mesh is False, the mesh will be empty. Note that the input sections which are converted to a BeamMe mesh are removed from the input file object.