import ast
import inspect
import os
import sys
import glob
import numpy
filename = inspect.getfile(lambda: None)
basename = os.path.basename(filename)
parent = os.path.dirname(filename)
grandparent = os.path.dirname(parent)
sys.path.insert(0, grandparent)
from turbo_turtle_abaqus import parsers
from turbo_turtle_abaqus import vertices
from turbo_turtle_abaqus import _mixed_utilities
from turbo_turtle_abaqus import _abaqus_utilities
from turbo_turtle_abaqus import _mixed_settings
[docs]
def main(
input_file,
output_file,
planar=parsers.geometry_defaults["planar"],
model_name=parsers.geometry_defaults["model_name"],
part_name=parsers.geometry_defaults["part_name"],
unit_conversion=parsers.geometry_defaults["unit_conversion"],
euclidean_distance=parsers.geometry_defaults["euclidean_distance"],
delimiter=parsers.geometry_defaults["delimiter"],
header_lines=parsers.geometry_defaults["header_lines"],
revolution_angle=parsers.geometry_defaults["revolution_angle"],
y_offset=parsers.geometry_defaults["y_offset"],
rtol=parsers.geometry_defaults["rtol"],
atol=parsers.geometry_defaults["atol"],
):
"""Create 2D planar, 2D axisymmetric, or 3D revolved geometry from an array of XY coordinates.
This script takes an array of XY coordinates from a text file and creates a 2D sketch or 3D body of
revolution about the global Y-axis. Note that 2D axisymmetric sketches and sketches for 3D bodies of revolution
about the global Y-axis must lie entirely on the positive-X side of the global Y-axis. In general, a 2D sketch can
lie in all four quadrants; this is referred to as a "planar" sketch and requires that the ``planar`` boolean
arugment be set to ``True``. This script can accept multiple input files to create multiple parts in the same Abaqus
model. The ``part_name`` parameter allows explicit naming of part(s) in the model. If omitted from the command line
arguments, the default is to use the input file basename(s) as the part name(s).
:param str input_file: input text file(s) with coordinates to draw
:param str output_file: Abaqus CAE database to save the part(s)
:param bool planar: switch to indicate that 2D model dimensionality is planar, not axisymmetric
:param str model_name: name of the Abaqus model in which to create the part
:param list part_name: name(s) of the part(s) being created
:param float unit_conversion: multiplication factor applies to all coordinates
:param float euclidean_distance: if the distance between two coordinates is greater than this, draw a straight line.
Distance should be provided in units *after* the unit conversion
: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
:param float revolution_angle: angle of solid revolution for ``3D`` geometries
:param float y_offset: vertical offset along the global Y-axis. Offset should be provided in units *after* the unit
conversion.
:param float rtol: relative tolerance for vertical/horizontal line checks
:param float atol: absolute tolerance for vertical/horizontal line checks
:returns: writes ``{output_file}.cae``
"""
import abaqus
output_file = os.path.splitext(output_file)[0] + ".cae"
try:
geometry(
input_file=input_file,
planar=planar,
model_name=model_name,
part_name=part_name,
revolution_angle=revolution_angle,
delimiter=delimiter,
header_lines=header_lines,
euclidean_distance=euclidean_distance,
unit_conversion=unit_conversion,
y_offset=y_offset,
rtol=rtol,
atol=atol,
)
except RuntimeError as err:
_mixed_utilities.sys_exit(err.message)
abaqus.mdb.saveAs(pathName=output_file)
[docs]
def geometry(
input_file,
planar,
model_name,
part_name,
revolution_angle,
delimiter,
header_lines,
euclidean_distance,
unit_conversion,
y_offset,
rtol,
atol,
):
"""Create 2D planar, 2D axisymmetric, or 3D revolved geometry from an array of XY coordinates.
This function drive the geometry creation of 2D planar, 2D axisymetric, or 3D revolved bodies and operates on a new
Abaqus moddel database object.
:param str input_file: input text file(s) with coordinates to draw
:param str output_file: Abaqus CAE database to save the part(s)
:param bool planar: switch to indicate that 2D model dimensionality is planar, not axisymmetric
:param str model_name: name of the Abaqus model in which to create the part
:param list part_name: name(s) of the part(s) being created
:param float unit_conversion: multiplication factor applies to all coordinates
:param float euclidean_distance: if the distance between two coordinates is greater than this, draw a straight line.
Distance should be provided in units *after* the unit conversion
: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
:param float revolution_angle: angle of solid revolution for ``3D`` geometries
:param float y_offset: vertical offset along the global Y-axis. Offset should be provided in units *after* the unit
conversion.
:param float rtol: relative tolerance for vertical/horizontal line checks
:param float atol: absolute tolerance for vertical/horizontal line checks
:raises RuntimeError: failure to create a sketch or part from a CSV file.
"""
import abaqus
_abaqus_utilities._conditionally_create_model(model_name)
failed_parts = [] # List of Tuples keeping track of parts that failed and their input files
part_name = _mixed_utilities.validate_part_name_or_exit(input_file, part_name)
for file_name, new_part in zip(input_file, part_name):
coordinates = _mixed_utilities.return_genfromtxt_or_exit(
file_name, delimiter, header_lines, expected_dimensions=2, expected_columns=2
)
coordinates = vertices.scale_and_offset_coordinates(coordinates, unit_conversion, y_offset)
lines, splines = vertices.lines_and_splines(coordinates, euclidean_distance, rtol=rtol, atol=atol)
try:
draw_part_from_splines(
lines,
splines,
planar=planar,
model_name=model_name,
part_name=new_part,
euclidean_distance=euclidean_distance,
revolution_angle=revolution_angle,
rtol=rtol,
atol=atol,
)
except abaqus.AbaqusException:
failed_parts += [(new_part, file_name)]
if failed_parts:
error_message = [
"Error: failed to create the following parts from input files. Check the XY coordinates "
"for inadmissible Abaqus sketch connectivity. The ``turbo-turtle geometry-xyplot`` "
"subcommand can plot points to aid in troubleshooting."
]
error_message += [" {}, {}".format(this_part, this_file) for this_part, this_file in failed_parts]
raise RuntimeError("\n".join(error_message))
[docs]
def draw_part_from_splines(
lines,
splines,
planar=parsers.geometry_defaults["planar"],
model_name=parsers.geometry_defaults["model_name"],
part_name=parsers.geometry_defaults["part_name"],
euclidean_distance=parsers.geometry_defaults["euclidean_distance"],
revolution_angle=parsers.geometry_defaults["revolution_angle"],
rtol=parsers.geometry_defaults["rtol"],
atol=parsers.geometry_defaults["atol"],
):
"""Given a series of line/spline definitions, draw lines/splines in an Abaqus sketch and generate either a 2D part
or a 3D body of revolution about the global Y-axis using the sketch. A 2D part can be either axisymmetric or planar
depending on the ``planar`` and ``revolution_angle`` parameters.
If ``planar`` is ``False`` and ``revolution_angle`` is equal (``numpy.isclose()``) to zero, this script will
attempt to create a 2D axisymmetric model.
If ``planar`` is ``False`` and ``revolution_angle`` is **not** zero, this script will attempt to create a 3D body of
revolution about the global Y-axis.
The default behavior of assuming ``planar=False`` implies that the sketch must lie entirely on the positive-X
side of the global Y-axis, which is the constraint for both 2D axisymmetric and 3D revolved bodies.
If ``planar`` is ``True``, this script will attempt to create a 2D planar model, which can be sketched in any/all
four quadrants.
**Note:** This function will always connect the first and last coordinates
:param list lines: list of [2, 2] shaped arrays of (x, y) coordinates defining a line segment
:param list splines: list of [N, 2] shaped arrays of (x, y) coordinates defining a spline
:param bool planar: switch to indicate that 2D model dimensionality is planar, not axisymmetric
:param str model_name: name of the Abaqus model in which to create the part
:param str part_name: name of the part being created
:param float euclidean_distance: if the distance between two coordinates is greater than this, draw a straight line.
:param float revolution_angle: angle of solid revolution for ``3D`` geometries
:returns: creates ``{part_name}`` within an Abaqus CAE database, not yet saved to local memory
"""
import abaqus
import abaqusConstants
revolution_direction = _abaqus_utilities.revolution_direction(revolution_angle)
revolution_angle = abs(revolution_angle)
sketch = abaqus.mdb.models[model_name].ConstrainedSketch(name="__profile__", sheetSize=200.0)
sketch.sketchOptions.setValues(viewStyle=abaqusConstants.AXISYM)
sketch.setPrimaryObject(option=abaqusConstants.STANDALONE)
sketch.ConstructionLine(point1=(0.0, -100.0), point2=(0.0, 100.0))
sketch.FixedConstraint(entity=sketch.geometry[2])
sketch.ConstructionLine(point1=(0.0, 0.0), point2=(1.0, 0.0))
sketch.FixedConstraint(entity=sketch.geometry[3])
for spline in splines:
spline = tuple(map(tuple, spline))
sketch.Spline(points=spline)
for point1, point2 in lines:
point1 = tuple(point1)
point2 = tuple(point2)
sketch.Line(point1=point1, point2=point2)
if planar:
part = abaqus.mdb.models[model_name].Part(
name=part_name, dimensionality=abaqusConstants.TWO_D, type=abaqusConstants.DEFORMABLE_BODY
)
part.BaseShell(sketch=sketch)
elif numpy.isclose(revolution_angle, 0.0):
part = abaqus.mdb.models[model_name].Part(
name=part_name, dimensionality=abaqusConstants.AXISYMMETRIC, type=abaqusConstants.DEFORMABLE_BODY
)
part.BaseShell(sketch=sketch)
else:
part = abaqus.mdb.models[model_name].Part(
name=part_name, dimensionality=abaqusConstants.THREE_D, type=abaqusConstants.DEFORMABLE_BODY
)
part.BaseSolidRevolve(sketch=sketch, angle=revolution_angle, flipRevolveDirection=revolution_direction)
sketch.unsetPrimaryObject()
del abaqus.mdb.models[model_name].sketches["__profile__"]
[docs]
def _gui():
"""Function with no inputs required for driving the plugin"""
_abaqus_utilities.gui_wrapper(
inputs_function=_gui_get_inputs, subcommand_function=geometry, post_action_function=_abaqus_utilities._view_part
)
if __name__ == "__main__":
if "caeModules" in sys.modules: # All Abaqus CAE sessions immediately load caeModules
_gui()
else:
parser = parsers.geometry_parser(basename=basename)
try:
args, unknown = parser.parse_known_args()
except SystemExit as err:
sys.exit(err.code)
sys.exit(
main(
input_file=args.input_file,
output_file=args.output_file,
planar=args.planar,
model_name=args.model_name,
part_name=args.part_name,
unit_conversion=args.unit_conversion,
euclidean_distance=args.euclidean_distance,
delimiter=args.delimiter,
header_lines=args.header_lines,
revolution_angle=args.revolution_angle,
y_offset=args.y_offset,
rtol=args.rtol,
atol=args.atol,
)
)