|
| 1 | + |
| 2 | +import meshio |
| 3 | +from meshio._mesh import CellBlock |
| 4 | +import numpy as np |
| 5 | +import argparse |
| 6 | +import logging |
| 7 | +import sys |
| 8 | + |
| 9 | + |
| 10 | +def convert_abaqus_to_gmsh(input_mesh, output_mesh, logger=None): |
| 11 | + """ |
| 12 | + @brief Convert an abaqus mesh to gmsh 2 format, preserving nodeset information. |
| 13 | + @details If the code encounters any issues with region/element indices, |
| 14 | + the conversion will attempt to continue, with errors |
| 15 | + indicated by -1 values in the output file. |
| 16 | + @param input_mesh path of the input abaqus file |
| 17 | + @param output_mesh path of the output gmsh file |
| 18 | + @param logger an instance of logging.Logger |
| 19 | + """ |
| 20 | + # Initialize the logger if it is empty |
| 21 | + if not logger: |
| 22 | + logging.basicConfig(level=logging.WARNING) |
| 23 | + logger = logging.getLogger(__name__) |
| 24 | + |
| 25 | + # Keep track of the number of warnings |
| 26 | + n_warnings = 0 |
| 27 | + |
| 28 | + # Load the mesh |
| 29 | + logger.info('Reading abaqus mesh...') |
| 30 | + mesh = meshio.read(input_mesh, file_format="abaqus") |
| 31 | + |
| 32 | + # Convert the element regions to tags |
| 33 | + logger.info('Converting region tags...') |
| 34 | + region_list = list(mesh.cell_sets.keys()) |
| 35 | + n_regions = len(region_list) |
| 36 | + cell_ids = [] |
| 37 | + for block_id, block in enumerate(mesh.cells): |
| 38 | + cell_ids.append(np.zeros(len(block[1]), dtype=int) - 1) |
| 39 | + for region_id, region in enumerate(region_list): |
| 40 | + mesh.field_data[region] = [region_id + 1, 3] |
| 41 | + cell_ids[block_id][mesh.cell_sets[region][block_id]] = region_id + 1 |
| 42 | + |
| 43 | + # Check for bad element region conversions |
| 44 | + if (-1 in cell_ids[-1]): |
| 45 | + logger.warning('Some element regions in block %i did not convert correctly to tags!' % (block_id)) |
| 46 | + logger.warning('Note: These will be indicated by a -1 in the output file.') |
| 47 | + n_warnings += 1 |
| 48 | + |
| 49 | + # Add to the meshio datastructure |
| 50 | + # Note: the copy here is required, so that later appends |
| 51 | + # do not break these dicts |
| 52 | + mesh.cell_data['gmsh:physical'] = cell_ids.copy() |
| 53 | + mesh.cell_data['gmsh:geometrical'] = cell_ids.copy() |
| 54 | + |
| 55 | + # Build the face elements |
| 56 | + logger.info('Converting nodesets to face elements, tags...') |
| 57 | + new_tris, tri_nodeset, tri_region = [], [], [] |
| 58 | + new_quads, quad_nodeset, quad_region = [], [], [] |
| 59 | + |
| 60 | + for nodeset_id, nodeset_name in enumerate(mesh.point_sets): |
| 61 | + logger.info(' %s' % (nodeset_name)) |
| 62 | + mesh.field_data[nodeset_name] = [nodeset_id + n_regions + 1, 2] |
| 63 | + nodeset = mesh.point_sets[nodeset_name] |
| 64 | + |
| 65 | + # Search by block, then element |
| 66 | + for block_id, block in enumerate(mesh.cells): |
| 67 | + for element_id, element in enumerate(block[1]): |
| 68 | + # Find any matching nodes |
| 69 | + matching_nodes = [x for x in element if x in nodeset] |
| 70 | + |
| 71 | + # Add a new face element if there are enough nodes |
| 72 | + n_matching = len(matching_nodes) |
| 73 | + if (n_matching >= 3): |
| 74 | + # Find the region |
| 75 | + region_id = -1 |
| 76 | + for region in region_list: |
| 77 | + if (element_id in mesh.cell_sets[region][block_id]): |
| 78 | + region_id = mesh.field_data[region][block_id] |
| 79 | + |
| 80 | + # Test to see if the element is a quad or triangle |
| 81 | + tag_id = mesh.field_data[nodeset_name][0] |
| 82 | + if (n_matching == 3): |
| 83 | + new_tris.append(matching_nodes) |
| 84 | + tri_nodeset.append(tag_id) |
| 85 | + tri_region.append(region_id) |
| 86 | + |
| 87 | + elif (n_matching == 4): |
| 88 | + new_quads.append(matching_nodes) |
| 89 | + quad_nodeset.append(tag_id) |
| 90 | + quad_region.append(region_id) |
| 91 | + |
| 92 | + else: |
| 93 | + logger.warning(' Discarding an element with an unexpected number of nodes') |
| 94 | + logger.warning(' n_nodes=%i, element=%i, set=%s' % (n_matching, element_id, nodeset_name)) |
| 95 | + n_warnings += 1 |
| 96 | + |
| 97 | + # Add new tris |
| 98 | + if new_tris: |
| 99 | + logger.info(' Adding %i new triangles...' % (len(new_tris))) |
| 100 | + if (-1 in tri_region): |
| 101 | + logger.warning('Triangles with empty region information found!') |
| 102 | + logger.warning('Note: These will be indicated by a -1 in the output file.') |
| 103 | + n_warnings += 1 |
| 104 | + mesh.cells.append(CellBlock('triangle', np.array(new_tris))) |
| 105 | + mesh.cell_data['gmsh:geometrical'].append(np.array(tri_region)) |
| 106 | + mesh.cell_data['gmsh:physical'].append(np.array(tri_nodeset)) |
| 107 | + |
| 108 | + # Add new quads |
| 109 | + if new_quads: |
| 110 | + logger.info(' Adding %i new quads...' % (len(new_quads))) |
| 111 | + if (-1 in quad_region): |
| 112 | + logger.warning('Quads with empty region information found!') |
| 113 | + logger.warning('Note: These will be indicated by a -1 in the output file.') |
| 114 | + n_warnings += 1 |
| 115 | + mesh.cells.append(CellBlock('quad', np.array(new_quads))) |
| 116 | + mesh.cell_data['gmsh:geometrical'].append(np.array(quad_region)) |
| 117 | + mesh.cell_data['gmsh:physical'].append(np.array(quad_nodeset)) |
| 118 | + |
| 119 | + # Write the final mesh |
| 120 | + logger.info('Writing gmsh mesh...') |
| 121 | + meshio.write(output_mesh, mesh, file_format="gmsh22", binary=False) |
| 122 | + logger.info('Done!') |
| 123 | + |
| 124 | + return (n_warnings > 0) |
| 125 | + |
| 126 | + |
| 127 | +def main(): |
| 128 | + """ |
| 129 | + @brief Entry point for the abaqus convertor console script |
| 130 | + @arg input_mesh Input abaqus file name |
| 131 | + @arg output_mesh Output gmsh file name |
| 132 | + """ |
| 133 | + |
| 134 | + # Parse the user arguments |
| 135 | + parser = argparse.ArgumentParser() |
| 136 | + parser.add_argument('input', type=str, help='Input abaqus mesh file name') |
| 137 | + parser.add_argument('output', type=str, help='Output gmsh mesh file name') |
| 138 | + parser.add_argument('-v', '--verbose', help='Increase verbosity level', action="store_true") |
| 139 | + args = parser.parse_args() |
| 140 | + |
| 141 | + # Set up a logger |
| 142 | + logging.basicConfig(level=logging.WARNING) |
| 143 | + logger = logging.getLogger(__name__) |
| 144 | + if args.verbose: |
| 145 | + logger.setLevel(logging.INFO) |
| 146 | + |
| 147 | + # Call the converter |
| 148 | + err = convert_abaqus_to_gmsh(args.input, args.output, logger) |
| 149 | + if err: |
| 150 | + sys.exit('Warnings detected: check the output file for potential errors!') |
| 151 | + |
0 commit comments