"""Python 2/3 compatible utilities for use in both Abaqus Python scripts and Turbo-Turtle Python 3 modules"""
import os
import re
import sys
import functools
import numpy
[docs]
def sys_exit(err):
"""Thin wrapper on ``sys.exit`` to force print to STDERR from Abaqus Python
Preserves Python 3 compatibility by performing a Python major version check prior to the Python 2 syntax call to
``print``.
:param Exception err: The exception object to print and pass to ``sys.exit``
"""
if sys.version_info.major == 2:
print >> sys.__stderr__, "{}".format(err) # pragma: no cover
sys.exit(str(err))
[docs]
def print_exception_message(function):
"""Decorate a function to catch bare exception and instead call sys.exit with the message
:param function: function to decorate
"""
@functools.wraps(function)
def wrapper(*args, **kwargs):
try:
output = function(*args, **kwargs)
except Exception as err:
sys_exit(err)
return output
return wrapper
[docs]
def validate_part_name(input_file, part_name):
"""Validate the structure of the ``part_name`` list to the following rules:
* If ``part_name`` is ``[None]``, assign the base names of ``input_file`` to ``part_name``
* Else if the length of ``part_name`` is not equal to the length of ``input_file``, raise an exception
:param list input_file: input text file(s) with coordinates to draw
:param list part_name: name(s) of part(s) being created
:return: part name(s)
:rtype: list
"""
if part_name[0] is None:
part_name = [os.path.splitext(os.path.basename(part_file))[0] for part_file in input_file]
elif len(input_file) != len(part_name):
message = "Error: The part name length '{}' must match the input file length '{}'\n".format(
len(part_name), len(input_file)
)
raise RuntimeError(message)
return part_name
@print_exception_message
def validate_part_name_or_exit(*args, **kwargs):
return validate_part_name(*args, **kwargs)
[docs]
def validate_element_type(length_part_name, element_type):
"""Validate the structure of the ``element_type`` list to the following rules:
* If the length of ``element_type`` is 1, propagate to match ``length_part_name``
* Raise a RuntimeError if ``element_type`` is greater than 1, but not equal to the length of ``part_name``
:param int length_part_name: length of the ``part_name`` list
:param list element_type: list of element types
:return: element types
:rtype: list
"""
length_element_type = len(element_type)
if length_element_type == 1:
element_type = element_type * length_part_name
elif length_element_type != length_part_name:
message = "The element type length '{}' must match the part name length '{}'\n".format(
length_element_type, length_part_name
)
raise RuntimeError(message)
return element_type
@print_exception_message
def validate_element_type_or_exit(*args, **kwargs):
return validate_element_type(*args, **kwargs)
[docs]
def return_genfromtxt(
file_name,
delimiter=",",
header_lines=0,
expected_dimensions=None,
expected_columns=None,
):
"""Parse a text file of XY coordinates into a numpy array
If the resulting numpy array doesn't have the specified dimensions or column count, return an error exit code
:param str file_name: input text file with coordinates to draw
:param str delimiter: character to use as a delimiter when reading the input file
:param int header_lines: number of lines in the header to skip when reading the input file
:return: 2D array of XY coordinates with shape [N, 2]
:rtype: numpy.array
"""
with open(file_name, "r") as points_file:
coordinates = numpy.genfromtxt(points_file, delimiter=delimiter, skip_header=header_lines)
shape = coordinates.shape
dimensions = len(shape)
if expected_dimensions is not None and dimensions != expected_dimensions:
message = "Expected coordinates with '{}' dimensions. Found '{}' dimensions\n".format(
expected_dimensions, dimensions
)
raise RuntimeError(message)
columns = shape[1]
if expected_columns is not None and columns != expected_columns:
message = "Expected coordinates with '{}' columns. Found '{}' columns\n".format(expected_columns, columns)
raise RuntimeError(message)
return coordinates
@print_exception_message
def return_genfromtxt_or_exit(*args, **kwargs):
return return_genfromtxt(*args, **kwargs)
[docs]
def remove_duplicate_items(string_list):
"""Remove duplicates from ``string_list`` and print a warning to STDERR of all duplicates removed
:param list string_list: list of strings to remove duplicates
:returns: unique strings
:rtype: list
"""
unique = []
duplicate = []
[unique.append(x) if x not in unique else duplicate.append(x) for x in string_list]
if duplicate:
message = "WARNING: removing '{}' duplicates: '{}'".format(len(duplicate), ", ".join(duplicate))
if sys.version_info.major == 2:
print >> sys.__stderr__, "{}".format(message) # pragma: no cover
sys.stderr.write(message)
return unique
[docs]
def intersection_of_lists(requested, available):
"""Return sorted intersection of available and requested items or all available items if none requested
:param list requested: requested items
:param list available: available items
:returns: intersection of requested and available items. All available items if None requested.
:ttype: list
"""
if requested[0] is not None and len(requested) > 0:
intersection = list(set(requested) & set(available))
else:
intersection = available
return sorted(intersection)
[docs]
def _element_type_regex(content, element_type):
"""Place element type in Abaqus element keywords. RegEx uses MULTILINE and IGNORECASE
:param str content: String of Abaqus keyword text
:param str element_type: New element type to place in the ``*element, type=`` text
:returns: substituted element type keyword text
:rtype: str
"""
regex = r"(\*element,\s+type=)([a-zA-Z0-9]*)"
subst = "\\1{}".format(element_type)
return re.sub(regex, subst, content, 0, re.MULTILINE | re.IGNORECASE)
[docs]
def substitute_element_type(mesh_file, element_type):
"""Use regular expressions to substitute element types in an existing orphan mesh file via the
``*Element`` keyword.
:param str mesh_file: existing orphan mesh file
:param str element_type: element type to substitute into the ``*Element`` keyword phrase
:returns: re-writes ``mesh_file`` if element type changes have been made
"""
with open(mesh_file, "r") as orphan_mesh:
old_content = orphan_mesh.read()
new_content = _element_type_regex(old_content, element_type)
if new_content != old_content:
with open(mesh_file, "w") as orphan_mesh:
orphan_mesh.write(new_content)
[docs]
def cubit_part_names(part_name):
"""Replace hyphens with underscores in strings for ACIS name compliance
:param list part_name: list of strings for character replacement(s)
:returns: modified list of part names
:rtype: list
"""
if isinstance(part_name, str):
return part_name.replace("-", "_")
else:
return [name.replace("-", "_") for name in part_name]