Source code for waves._abaqus.odb_extract

#! /usr/bin/env python

"""Extracts data from an Abaqus odb file. Writes two files 'output_file.h5' and 'output_file_datasets.h5'

Calls odbreport feature of Abaqus, parses resultant file, and creates output file. Most simulation data lives in a
group path following the instance and set name, e.g. '/INSTANCE/FieldOutputs/ELEMENT_SET', and can be accessed with
xarray as

.. code-block::

   import xarray
   xarray.open_dataset("sample.h5", group="/INSTANCE/FieldOutputs/ELEMENT_SET")

You can view all group paths with 'h5ls -r sample.h5'. Additional ODB information is available in the '/odb' group
path. The '/xarray/Dataset' group path contains a list of group paths that contain an xarray dataset.

.. code-block::
   :caption: Format of HDF5 file

   /                 # Top level group required in all hdf5 files
   /<instance name>/ # Groups containing data of each instance found in an odb
       FieldOutputs/      # Group with multiple xarray datasets for each field output
           <field name>/  # Group with datasets containing field output data for a specified set or surface
                          # If no set or surface is specified, the <field name> will be 'ALL_NODES' or 'ALL_ELEMENTS'
       HistoryOutputs/    # Group with multiple xarray datasets for each history output
           <region name>/ # Group with datasets containing history output data for specified history region name
                          # If no history region name is specified, the <region name> will be 'ALL NODES'
       Mesh/              # Group written from an xarray dataset with all mesh information for this instance
   /<instance name>_Assembly/ # Group containing data of assembly instance found in an odb
       Mesh/              # Group written from an xarray dataset with all mesh information for this instance
   /odb/             # Catch all group for data found in the odbreport file not already organized by instance
       info/              # Group with datasets that mostly give odb meta-data like name, path, etc.
       jobData/           # Group with datasets that contain additional odb meta-data
       rootAssembly/      # Group with datasets that match odb file organization per Abaqus documentation
       sectionCategories/ # Group with datasets that match odb file organization per Abaqus documentation
   /xarray/          # Group with a dataset that lists the location of all data written from xarray datasets
"""

import os
import sys
import json
import yaml
import shlex
import select
import shutil
import typing
import pathlib
import datetime
import subprocess
from argparse import ArgumentParser, RawDescriptionHelpFormatter

from waves._abaqus import _settings
from waves._abaqus import abaqus_file_parser
from waves._utilities import _quote_spaces_in_path


_exclude_from_namespace = set(globals().keys())


[docs] def get_parser(): """Get parser object for command line options :return: argument parser :rtype: parser """ _program_name = pathlib.Path(__file__).stem example = f""" Example: >> {_program_name} sample.odb\n """ parser = ArgumentParser( description=__doc__, formatter_class=RawDescriptionHelpFormatter, epilog=example, prog=_program_name ) parser.add_argument( nargs=1, dest="input_file", type=str, help="odb or odbreport file for extracting data", metavar="sample.odb" ) parser.add_argument( "-o", "--output-file", dest="output_file", type=str, help="File for printing output. If none is provided, takes the basename of the input file " "(default: %(default)s)", metavar="sample.h5", ) parser.add_argument( "-f", "--output-file-type", dest="output_type", choices=["yaml", "json", "h5"], type=str, default="h5", help="Type of file in which to store output data (default: %(default)s)", metavar="h5", ) parser.add_argument( "-r", "--odb-report-args", dest="odb_report_args", type=str, help="Arguments to give to the odbreport command (default: %(default)s)", metavar='"step=step1 results"', ) parser.add_argument( "-a", "--abaqus-command", dest="abaqus_command", type=str, default=_settings._default_abaqus_command, help="Abaqus command to use (default: %(default)s)", metavar="/path/to/abaqus", ) parser.add_argument( "-d", "--delete-report-file", action="store_true", dest="delete_report_file", default=False, help="Delete after parsing the file created by the odbreport command (default: %(default)s)", ) parser.add_argument( "-v", "--verbose", action="store_true", dest="verbose", default=False, help="Print all messages (default: %(default)s)", ) return parser
[docs] def odb_extract( input_file: list, output_file: str, output_type: str = "h5", odb_report_args: typing.Optional[str] = None, abaqus_command: str = _settings._default_abaqus_command, delete_report_file: bool = False, verbose: bool = False, ): """The odb_extract Abaqus data extraction tool. Most users should use the associated command line interface. :param input_file: A list of ``*.odb`` files to extract. Current implementation only supports extraction on the first file in the list. :param output_file: The output file name to extract to. Extension should match on of the supported output types. :param output_type: Output file type. Defaults to ``h5``. Options are: ``h5``, ``yaml``, ``json``. :param odb_report_args: String of command line options to pass to ``abaqus odbreport``. :param abaqus_command: The abaqus command name or absolute path to the Abaqus exectuble. :param delete_report_file: Boolean to delete the intermediate Abaqus generated report file after producing the ``output_file``. :param verbose: Boolean to print more verbose messages """ # Handle arguments input_file = input_file[0] path_input_file = pathlib.Path(input_file) odbreport_file = False if not path_input_file.exists(): sys.exit(f"{input_file} does not exist.") if path_input_file.suffix != ".odb": print(f"{input_file} is not an odb file. File will be assumed to be an odbreport file.", file=sys.stderr) odbreport_file = True file_base_name = str(path_input_file.with_suffix("")) if not output_file: # If no output file given, use the name and path of odb file, but change the extension output_file = f"{file_base_name}.{output_type}" path_output_file = pathlib.Path(output_file) file_suffix = path_output_file.suffix.replace(".", "") if file_suffix != output_type: # If file ends in different extension than requested output output_file = str(path_output_file.with_suffix(f".{output_type}")) # Change extension print( f"Output specified as {output_type}, but output file extension is {file_suffix}. " f"Changing output file extension. Output file name {output_file}", file=sys.stderr, ) file_suffix = output_type time_stamp = datetime.datetime.now().strftime(_settings._default_timestamp_format) job_name = path_output_file.with_suffix(".csv") if path_output_file.exists(): new_output_file = f"{str(path_output_file.with_suffix(''))}_{time_stamp}.{file_suffix}" print(f"{output_file} already exists. Will use {new_output_file} instead.", file=sys.stderr) output_file = new_output_file if odb_report_args is None: odb_report_args = "" odb_report_args = get_odb_report_args(odb_report_args, input_file, job_name) abaqus_base_command = shutil.which(abaqus_command) if not abaqus_base_command: abaqus_base_command = _settings._default_abaqus_command # try 'abaqus' anyway abaqus_command = f"{abaqus_base_command} odbreport {odb_report_args}" if odbreport_file: job_name = path_input_file call_odbreport = False else: call_odbreport = True if pathlib.Path(job_name).exists() and not odbreport_file: call_odbreport = False # Don't call odbreport again if the report file already exists print(f"Report file {job_name} already exists, would you like to use this file?") i, o, e = select.select([sys.stdin], [], [], 15) # Wait 15 seconds for user input if i: answer = sys.stdin.readline().strip() if answer[:1].lower() != "y": call_odbreport = True # If user input is not yes, run odbreport again if call_odbreport: if verbose: print(f"Running Abaqus command: '{abaqus_command}'") return_code, output, error_code = run_external(abaqus_command) if return_code != 0: sys.exit(f"Abaqus odbreport command failed to execute. Abaqus output: '{output}'") if not pathlib.Path(job_name).exists(): sys.exit(f"{job_name} does not exist.") if output_type == "h5": # If the dataset isn't empty try: abaqus_file_parser.OdbReportFileParser(job_name, "extract", output_file, time_stamp).parse( h5_file=output_file ) # Index error is reached if a line is split and the line is empty (i.e. file is empty), ValueError is reached if # a string is found where an integer is expected except (IndexError, ValueError) as err: sys.exit(f"{job_name} could not be parsed. Please check if file is in expected format. {err}") else: parsed_odb = None # Parse output of odbreport try: parsed_odb = abaqus_file_parser.OdbReportFileParser(job_name, "odb").parsed # Index error is reached if a line is split and the line is empty (i.e. file is empty0) except (IndexError, ValueError) as err: sys.exit(f"{job_name} could not be parsed. Please check if file is in expected format. {err}") # Write parsed output if output_type == "json": with open(output_file, "w") as f: json.dump(parsed_odb, f, indent=4) elif output_type == "yaml": with open(output_file, "w") as f: yaml.safe_dump(parsed_odb, f) # With safe_dump, tuples are converted to lists # Remove odbreport file, don't raise exception if it doesn't exist if delete_report_file: pathlib.Path(job_name).unlink(missing_ok=True)
[docs] def get_odb_report_args(odb_report_args: str, input_file: pathlib.Path, job_name: pathlib.Path): """ Generates odb_report arguments :param odb_report_args: String of command line options to pass to ``abaqus odbreport``. :param input_file: ``.odb`` file. :param job_name: Report file. """ input_file = _quote_spaces_in_path(input_file) job_name = _quote_spaces_in_path(job_name) required_arguments = f"-job {job_name.with_suffix('')} -odb {input_file} -mode CSV -blocked" if not odb_report_args: odb_report_args = f"-all {required_arguments}" else: odb_report_args = f"{odb_report_args} {required_arguments}" if "odbreport" in odb_report_args: odb_report_args = odb_report_args.replace("odbreport", "") if "invariants" in odb_report_args: print( "odb_extract does not work with invariant output. Removing the 'invariants' odbreport option", file=sys.stderr, ) odb_report_args = odb_report_args.replace("invariants", "") odb_report_args = odb_report_args.strip() return odb_report_args
[docs] def run_external(cmd): """ Execute an external command and get its exitcode, stdout and stderr. :param str cmd: command line command to run :returns: output, return_code, error_code """ args = shlex.split(cmd, posix=(os.name == "posix")) process = subprocess.run(args, capture_output=True) return process.returncode, process.stdout.decode(), process.stderr.decode()
def main(): args = get_parser().parse_args() # pragma: no cover sys.exit( odb_extract( args.input_file, args.output_file, args.output_type, args.odb_report_args, args.abaqus_command, args.delete_report_file, args.verbose, ) ) # pragma: no cover if __name__ == "__main__": sys.exit(main()) # pragma: no cover # Limit help() and 'from module import *' behavior to the module's public API _module_objects = set(globals().keys()) - _exclude_from_namespace __all__ = [name for name in _module_objects if not name.startswith("_")]