Tutorial 02: Partition and Mesh#

References#

Environment#

SCons and WAVES can be installed in a Conda environment with the Conda package manager. See the Conda installation and Conda environment management documentation for more details about using Conda.

Note

The SALib and numpy versions may not need to be this strict for most tutorials. However, Tutorial: Sensitivity Study uncovered some undocumented SALib version sensitivity to numpy surrounding the numpy v2 rollout.

  1. Create the tutorials environment if it doesn’t exist

    $ conda create --name waves-tutorial-env --channel conda-forge waves 'scons>=4.6' matplotlib pandas pyyaml xarray seaborn 'numpy>=2' 'salib>=1.5.1' pytest
    
  2. Activate the environment

    $ conda activate waves-tutorial-env
    

Some tutorials require additional third-party software that is not available for the Conda package manager. This software must be installed separately and either made available to SConstruct by modifying your system’s PATH or by modifying the SConstruct search paths provided to the waves.scons_extensions.add_program() method.

Warning

STOP! Before continuing, check that the documentation version matches your installed package version.

  1. You can find the documentation version in the upper-left corner of the webpage.

  2. You can find the installed WAVES version with waves --version.

If they don’t match, you can launch identically matched documentation with the WAVES Command-Line Utility docs subcommand as waves docs.

Directory Structure#

Instead of incrementally modifying a single SConscript workflow file, each tutorial will create a copy of the SConscript file and build on the previous tutorial. Each tutorial will be a separate, stand-alone, fully functional workflow. This is intended to help compare output file and behavior changes between tutorials and to emphasize that a simulation project may contain more than one simulation workflow.

  1. Create and change to a new project root directory to house the tutorial files if you have not already done so. For example

$ mkdir -p ~/waves-tutorials
$ cd ~/waves-tutorials
$ pwd
/home/roppenheimer/waves-tutorials

Note

If you skipped any of the previous tutorials, run the following commands to create a copy of the necessary tutorial files.

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --overwrite --tutorial 1 && mv tutorial_01_geometry_SConstruct SConstruct
WAVES fetch
Destination directory: '/home/roppenheimer/waves-tutorials'
  1. Fetch the tutorial_01_geometry file and create a new file named tutorial_02_partition_mesh using the WAVES Command-Line Utility fetch subcommand.

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --overwrite tutorials/tutorial_01_geometry && cp tutorial_01_geometry tutorial_02_partition_mesh
WAVES fetch
Destination directory: '/home/roppenheimer/waves-tutorials'

SConscript#

  1. Modify your tutorial_02_partition_mesh file by adding the contents below immediately after the code pertaining to # Geometry from the previous tutorial, and above the # Collector alias code.

waves-tutorials/tutorial_02_partition_mesh

36# Partition
37workflow.extend(
38    env.AbaqusJournal(
39        target=["rectangle_partition.cae", "rectangle_partition.jnl"],
40        source=["#/modsim_package/abaqus/rectangle_partition.py", "rectangle_geometry.cae"],
41        subcommand_options="",
42    )
43)
44
45# Mesh
46workflow.extend(
47    env.AbaqusJournal(
48        target=["rectangle_mesh.inp", "rectangle_mesh.cae", "rectangle_mesh.jnl"],
49        source=["#/modsim_package/abaqus/rectangle_mesh.py", "rectangle_partition.cae"],
50        subcommand_options="",
51    )
52)

Just like building the geometry in Tutorial 01: Geometry, the code you just added instructs SCons on how to build the targets for partitioning and meshing our rectangle part.

In the code pertaining to # Partition, we will again pass an empty string for the subcommand_options. We will re-open the discussion of using the journal file’s command-line interface via the subcommand_options variable in Tutorial 05: Parameter Substitution. Next, the workflow list is extended once again to include the action to use the waves.scons_extensions.abaqus_journal_builder_factory() builder. The target list specifies the files created by the waves.scons_extensions.abaqus_journal_builder_factory() builder’s action, and the source list specifies on which files to act in order to produce the targets.

Keen readers will note that this source-target definition is different from that in Tutorial 01: Geometry. Here, we again specify two targets, rectangle_partition.cae and rectangle_partition.jnl, each of which are now generated by performing an action on not one, but two sources. The first source is similar to that in Tutorial 01: Geometry, where we run the rectangle_partition.py file in the Abaqus kernel, but now the default behavior of the journal is different.

  1. In the rectangle_partition.py file, you’ll notice that a new parameter is defined here that was absent in rectangle_geometry.py. This parameter is defined in short with -i or verbosely by --input-file.

The --input-file command-line argument defaults to the string 'rectangle_geometry' and does not require a file extension. So, we simply need to make sure that the rectangle_geometry.cae file (which is an output from the code we wrote in Tutorial 01: Geometry) be included in the source list. If rectangle_geometry.cae were left out of the source list, the SCons build system would not be able to determine that the partition target depends on the geometry target. This would result in an indeterminate race condition in target execution order. Incomplete source and target lists also make it impossible for the build system to automatically determine when a target needs to be re-built. If not specified as a source, the rectangle_geometry.cae file could change and the build system would not know that the rectangle_partition.cae target needs to be re-built.

With the two sources defined, the waves.scons_extensions.abaqus_journal_builder_factory() builder has all the information it needs to build the rectangle_partition.cae target.

In the code pertaining to # Mesh, the trend continues. We will re-assign the journal_file variable to the meshing journal file name to reduce hard-coded duplication of strings. We define an empty string for subcommand_options, as nothing other than the default is required for this task. We finally extend the workflow to utilize the waves.scons_extensions.abaqus_journal_builder_factory() builder on the source list. Just like the code for # Partition, we have two sources. In this tutorial, we rely on the rectangle_mesh.py CLI default arguments which will use the rectangle_partition.cae file as the input model file. Readers are encouraged to return to the WAVES-TUTORIAL CLI to become familiar with the command-line arguments available for the journal files in this tutorial.

The mesh task produces more than one output file, so the target list has changed size. You should now be familiar with the behavior that generates the rectangle_mesh.cae and rectangle_mesh.jnl targets. The new target is the rectangle_mesh.inp file. This file is called an orphan mesh file. When the waves.scons_extensions.abaqus_journal_builder_factory() builder acts on the rectangle_mesh.py file, our three target files are created. The orphan mesh file is created by calling the export_mesh() function within the rectangle_mesh.py file. See the abaqus_utilities.py file for more information about the export_mesh() function.

In summary of the changes you just made to the tutorial_02_partition_mesh file, a diff against the SConscript file from Tutorial 01: Geometry is included below to help identify the changes made in this tutorial.

waves-tutorials/tutorial_02_partition_mesh

--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_01_geometry
+++ /home/runner/work/waves/waves/build/docs/tutorials_tutorial_02_partition_mesh
@@ -32,6 +32,26 @@
     )
 )
 
+# Comment used in tutorial code snippets: marker-2
+
+# Partition
+workflow.extend(
+    env.AbaqusJournal(
+        target=["rectangle_partition.cae", "rectangle_partition.jnl"],
+        source=["#/modsim_package/abaqus/rectangle_partition.py", "rectangle_geometry.cae"],
+        subcommand_options="",
+    )
+)
+
+# Mesh
+workflow.extend(
+    env.AbaqusJournal(
+        target=["rectangle_mesh.inp", "rectangle_mesh.cae", "rectangle_mesh.jnl"],
+        source=["#/modsim_package/abaqus/rectangle_mesh.py", "rectangle_partition.cae"],
+        subcommand_options="",
+    )
+)
+
 # Comment used in tutorial code snippets: marker-3
 
 # Collector alias based on parent directory name

Abaqus Journal File#

Recall from Tutorial 01: Geometry that you created an Abaqus journal file called rectangle_geometry.py. You will now create two more for the partitioning and meshing workflows. The reader is referred to the following sections in Tutorial 01: Geometry for a reminder of different aspects of these journal files:

  1. In the modsim_package/abaqus directory, create a file called rectangle_partition.py using all the contents below.

waves-tutorials/modsim_package/abaqus/rectangle_partition.py

  1import os
  2import sys
  3import shutil
  4import inspect
  5import argparse
  6
  7import abaqus
  8
  9
 10def main(input_file, output_file, model_name, part_name, width, height):
 11    """Partition the simple rectangle geometry created by ``rectangle_geometry.py``
 12
 13    This script partitions a simple Abaqus model with a single rectangle part.
 14
 15    **Feature labels:**
 16
 17    * ``bottom_left`` - bottom left vertex
 18    * ``bottom_right`` - bottom right vertex
 19    * ``top_right`` - top right vertex
 20    * ``top_left`` - top left vertex
 21    * ``left`` - left edge
 22    * ``top`` - top edge
 23    * ``right`` - right edge
 24    * ``bottom`` - bottom edge
 25
 26    :param str input_file: The Abaqus model file created by ``rectangle_geometry.py``. Will be stripped of the extension
 27        and ``.cae`` will be used.
 28    :param str output_file: The output file for the Abaqus model. Will be stripped of the extension and ``.cae`` will be
 29        used.
 30    :param str model_name: The name of the Abaqus model
 31    :param str part_name: The name of the Abaqus part
 32    :param float width: The rectangle width
 33    :param float height: The rectangle height
 34
 35    :returns: writes ``output_file``.cae and ``output_file``.jnl
 36    """
 37    input_file = os.path.splitext(input_file)[0] + ".cae"
 38    output_file = os.path.splitext(output_file)[0] + ".cae"
 39
 40    # Avoid modifying the contents or timestamp on the input file.
 41    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 42    if input_file != output_file:
 43        shutil.copyfile(input_file, output_file)
 44
 45    abaqus.openMdb(pathName=output_file)
 46
 47    part = abaqus.mdb.models[model_name].parts[part_name]
 48
 49    vertices = part.vertices.findAt(
 50        ((0, 0, 0),),
 51    )
 52    part.Set(vertices=vertices, name="bottom_left")
 53
 54    vertices = part.vertices.findAt(
 55        ((width, 0, 0),),
 56    )
 57    part.Set(vertices=vertices, name="bottom_right")
 58
 59    vertices = part.vertices.findAt(
 60        ((width, height, 0),),
 61    )
 62    part.Set(vertices=vertices, name="top_right")
 63
 64    vertices = part.vertices.findAt(
 65        ((0, height, 0),),
 66    )
 67    part.Set(vertices=vertices, name="top_left")
 68
 69    side1Edges = part.edges.findAt(
 70        ((0, height / 2.0, 0),),
 71    )
 72    part.Set(edges=side1Edges, name="left")
 73
 74    side1Edges = part.edges.findAt(
 75        ((width / 2.0, height, 0),),
 76    )
 77    part.Set(edges=side1Edges, name="top")
 78
 79    side1Edges = part.edges.findAt(
 80        ((width, height / 2.0, 0),),
 81    )
 82    part.Set(edges=side1Edges, name="right")
 83
 84    side1Edges = part.edges.findAt(
 85        ((width / 2.0, 0, 0),),
 86    )
 87    part.Set(edges=side1Edges, name="bottom")
 88
 89    abaqus.mdb.save()
 90
 91
 92def get_parser():
 93    """Return parser for CLI options
 94
 95    All options should use the double-hyphen ``--option VALUE`` syntax to avoid clashes with the Abaqus option syntax,
 96    including flag style arguments ``--flag``. Single hyphen ``-f`` flag syntax often clashes with the Abaqus command
 97    line options and should be avoided.
 98
 99    :returns: parser
100    :rtype: argparse.ArgumentParser
101    """
102    # The global '__file__' variable doesn't appear to be set when executing from Abaqus CAE
103    filename = inspect.getfile(lambda: None)
104    basename = os.path.basename(filename)
105    basename_without_extension, extension = os.path.splitext(basename)
106    # Construct a part name from the filename less the workflow step
107    default_part_name = basename_without_extension
108    suffix = "_partition"
109    if default_part_name.endswith(suffix):
110        default_part_name = default_part_name[: -len(suffix)]
111    # Set default parameter values
112    default_input_file = "{}_geometry".format(default_part_name)
113    default_output_file = "{}".format(basename_without_extension)
114    default_width = 1.0
115    default_height = 1.0
116
117    prog = "abaqus cae -noGui {} --".format(basename)
118    cli_description = (
119        "Partition the simple rectangle geometry created by ``rectangle_geometry.py`` "
120        "and write an ``output_file``.cae Abaqus model file."
121    )
122    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
123    parser.add_argument(
124        "--input-file",
125        type=str,
126        default=default_input_file,
127        # fmt: off
128        help="The Abaqus model file created by ``rectangle_geometry.py``. "
129             "Will be stripped of the extension and ``.cae`` will be used, e.g. ``input_file``.cae",
130        # fmt: on
131    )
132    parser.add_argument(
133        "--output-file",
134        type=str,
135        default=default_output_file,
136        # fmt: off
137        help="The output file for the Abaqus model. "
138             "Will be stripped of the extension and ``.cae`` will be used, e.g. ``output_file``.cae",
139        # fmt: on
140    )
141    parser.add_argument(
142        "--model-name",
143        type=str,
144        default=default_part_name,
145        help="The name of the Abaqus model",
146    )
147    parser.add_argument(
148        "--part-name",
149        type=str,
150        default=default_part_name,
151        help="The name of the Abaqus part",
152    )
153    parser.add_argument(
154        "--width",
155        type=float,
156        default=default_width,
157        help="The rectangle width. Positive float.",
158    )
159    parser.add_argument(
160        "--height",
161        type=float,
162        default=default_height,
163        help="The rectangle height. Positive float.",
164    )
165    return parser
166
167
168if __name__ == "__main__":
169    parser = get_parser()
170    # Abaqus does not strip the CAE options, so we have to skip the unknown options related to the CAE CLI.
171    try:
172        args, unknown = parser.parse_known_args()
173    except SystemExit as err:
174        sys.exit(err.code)
175    # Check for typos in expected arguments. Assumes all arguments use ``--option`` syntax, which is unused by Abaqus.
176    possible_typos = [argument for argument in unknown if argument.startswith("--")]
177    if len(possible_typos) > 0:
178        raise RuntimeError("Found possible typos in CLI option(s) {}".format(possible_typos))
179
180    sys.exit(
181        main(
182            input_file=args.input_file,
183            output_file=args.output_file,
184            model_name=args.model_name,
185            part_name=args.part_name,
186            width=args.width,
187            height=args.height,
188        )
189    )

The rectangle_partition.py file is laid out in a very similar fashion to rectangle_geometry.py. It contains a main() function with PEP-287 formatted docstrings. Within that main() function is Abaqus python code that does a few specific tasks:

  • Format the --input-file and --output-file command-line argument values with .cae file extensions

  • Copy the input_file to an identical output_file with a new name. This is necessary because Abaqus changes the contents of *.cae files on open. The content change will cause the build system to always re-run the task that generated the input_file.

  • Within the new output_file, do the following:

    • Create node sets at four corners of the rectangle part. See the Abaqus Node Sets documentation [37] for more information about node sets.

    • Create node sets for the four sides of the rectangle part.

  • Save the output_file with the changes made

The rectangle_partition.py script also contains an argument parser function which implements the documented command line interface. The argument parser functions in a very similar way to that in the rectangle_geometry.py file, but a new command-line argument --input-file is added. This command-line argument is how the script knows which file to copy and then modify in the Abaqus python code.

Lastly, the execution of the main() function is protected within the context of a if __name__ == "__main__": statement, and the main() function is called within sys.exit() for exit code retrieval.

  1. In the modsim_package/abaqus directory, create a file called abaqus_utilities.py using the contents below.

waves-tutorial/modsim_package/abaqus/abaqus_utilities.py

 1import os
 2import re
 3
 4import abaqusConstants
 5
 6
 7def export_mesh(model_object, part_name, orphan_mesh_file):
 8    """Export an orphan mesh for the specified part instance in an Abaqus model
 9
10    Using an abaqus model object (``model_object = abaqus.mdb.models[model_name]``) with part(s) that are meshed and
11    instanced in an assembly, get the ``*.inp`` keyword blocks and save an orphan mesh file, ``orphan_mesh_file``.inp,
12    for the specific ``part_name``.
13
14    :param abaqus.mdb.models[model_name] model_object: Abaqus model object
15    :param str part_name: Part name to export as an orphan mesh
16    :param str orphan_mesh_file: File name to write for the orphan mesh. Will be stripped of the extension and ``.cae``
17        will be used, e.g. ``orphan_mesh_file``.inp
18
19    :returns: writes ``orphan_mesh_file``.inp
20    """
21    orphan_mesh_file = os.path.splitext(orphan_mesh_file)[0] + ".inp"
22    model_object.keywordBlock.synchVersions()
23    block = model_object.keywordBlock.sieBlocks
24    block_string = "\n".join(block)
25    orphan_mesh = re.findall(
26        r".*?\*Part, name=({})$\n(.*?)\*End Part".format(part_name), block_string, re.DOTALL | re.I | re.M
27    )
28    part_definition = orphan_mesh[0]
29    with open(orphan_mesh_file, "w") as output:
30        output.write(part_definition[1].strip())
31
32
33def return_abaqus_constant(search):
34    """If search is found in the abaqusConstants module, return the abaqusConstants object.
35
36    :param str search: string to search in the abaqusConstants module attributes
37
38    :return value: abaqusConstants attribute
39    :rtype: abaqusConstants.<search>
40
41    :raises ValueError: If the search string is not found.
42    """
43    search = search.upper()
44    if hasattr(abaqusConstants, search):
45        attribute = getattr(abaqusConstants, search)
46    else:
47        raise ValueError("The abaqusConstants module does not have a matching '{}' object".format(search))
48    return attribute

The abaqus_utilities.py script’s purpose is to contain commonly used functions that we do not want to duplicate. At the moment, we have only created two functions - export_mesh() and return_abaqus_constant(). The export_mesh function utilizes an Abaqus Model Object [37] along with a part_name and orphan_mesh_file name to create an orphan mesh file. Orphan mesh files define the entire part’s mesh in a text-based file. The node and element locations and labels are listed in a tabular format that the Abaqus file parser understands. The return_abaqus_constant function utilizes a search string and will return the matching abaqusConstants attribute if it exists. The return_abaqus_constant is not relevant to this tutorial but will be utilized in Tutorial: Part Image.

  1. In the modsim_package/abaqus directory, create a file called rectangle_mesh.py using all the contents below.

waves-tutorials/modsim_package/abaqus/rectangle_mesh.py

  1import os
  2import sys
  3import shutil
  4import inspect
  5import argparse
  6
  7import abaqus
  8import abaqusConstants
  9import mesh
 10
 11# Import the shared abaqus utilities, trying both tutorial directory structures.
 12# Most end-users will implement only one of these structures and should replace
 13# the try/except structure with a single import line, e.g.
 14#
 15# import modsim_package.abaqus.abaqus_utilities as abaqus_utilities
 16try:
 17    import modsim_package.abaqus.abaqus_utilities as abaqus_utilities
 18except ImportError:
 19    import abaqus_utilities
 20
 21
 22def main(input_file, output_file, model_name, part_name, global_seed):
 23    """Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py``
 24
 25    This script meshes a simple Abaqus model with a single rectangle part.
 26
 27    **Feature labels:**
 28
 29    * ``NODES`` - all part nodes
 30    * ``ELEMENTS`` - all part elements
 31
 32    :param str input_file: The Abaqus model file created by ``rectangle_partition.py``. Will be stripped of the
 33        extension and ``.cae`` will be used.
 34    :param str output_file: The output file for the Abaqus model. Will be stripped of the extension and ``.cae`` and
 35        ``.inp`` will be used for the model and orphan mesh output files, respectively.
 36    :param str model_name: The name of the Abaqus model
 37    :param str part_name: The name of the Abaqus part
 38    :param float global_seed: The global mesh seed size
 39
 40    :returns: ``output_file``.cae, ``output_file``.jnl, ``output_file``.inp
 41    """
 42    input_file = os.path.splitext(input_file)[0] + ".cae"
 43    output_file = os.path.splitext(output_file)[0] + ".cae"
 44
 45    # Avoid modifying the contents or timestamp on the input file.
 46    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 47    if input_file != output_file:
 48        shutil.copyfile(input_file, output_file)
 49
 50    abaqus.openMdb(pathName=output_file)
 51
 52    part = abaqus.mdb.models[model_name].parts[part_name]
 53    assembly = abaqus.mdb.models[model_name].rootAssembly
 54    assembly.Instance(name=part_name, part=part, dependent=abaqusConstants.ON)
 55
 56    part.seedPart(size=global_seed, deviationFactor=0.1, minSizeFactor=0.1)
 57    part.generateMesh()
 58
 59    elemType1 = mesh.ElemType(elemCode=abaqusConstants.CPS4R, elemLibrary=abaqusConstants.STANDARD)
 60
 61    faces = part.faces
 62    pickedRegions = (faces,)
 63
 64    part.setElementType(regions=pickedRegions, elemTypes=(elemType1,))
 65    part.Set(faces=faces, name="ELEMENTS")
 66    part.Set(faces=faces, name="NODES")
 67
 68    model_object = abaqus.mdb.models[model_name]
 69    abaqus_utilities.export_mesh(model_object, part_name, output_file)
 70
 71    abaqus.mdb.save()
 72
 73
 74def get_parser():
 75    """Return parser for CLI options
 76
 77    All options should use the double-hyphen ``--option VALUE`` syntax to avoid clashes with the Abaqus option syntax,
 78    including flag style arguments ``--flag``. Single hyphen ``-f`` flag syntax often clashes with the Abaqus command
 79    line options and should be avoided.
 80
 81    :returns: parser
 82    :rtype: argparse.ArgumentParser
 83    """
 84    # The global '__file__' variable doesn't appear to be set when executing from Abaqus CAE
 85    filename = inspect.getfile(lambda: None)
 86    basename = os.path.basename(filename)
 87    basename_without_extension, extension = os.path.splitext(basename)
 88    # Construct a part name from the filename less the workflow step
 89    default_part_name = basename_without_extension
 90    suffix = "_mesh"
 91    if default_part_name.endswith(suffix):
 92        default_part_name = default_part_name[: -len(suffix)]
 93    # Set default parameter values
 94    default_input_file = "{}_partition".format(default_part_name)
 95    default_output_file = "{}".format(basename_without_extension)
 96    default_global_seed = 1.0
 97
 98    prog = "abaqus cae -noGui {} --".format(basename)
 99    cli_description = (
100        "Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py`` "
101        "and write an ``output_file``.cae Abaqus model file and ``output_file``.inp orphan mesh file."
102    )
103    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
104    parser.add_argument(
105        "--input-file",
106        type=str,
107        default=default_input_file,
108        # fmt: off
109        help="The Abaqus model file created by ``rectangle_partition.py``. "
110             "Will be stripped of the extension and ``.cae`` will be used, e.g. ``input_file``.cae",
111        # fmt: on
112    )
113    parser.add_argument(
114        "--output-file",
115        type=str,
116        default=default_output_file,
117        # fmt: off
118        help="The output file for the Abaqus model. "
119             "Will be stripped of the extension and ``.cae`` will be used, e.g. ``output_file``.cae",
120        # fmt: on
121    )
122    parser.add_argument(
123        "--model-name",
124        type=str,
125        default=default_part_name,
126        help="The name of the Abaqus model",
127    )
128    parser.add_argument(
129        "--part-name",
130        type=str,
131        default=default_part_name,
132        help="The name of the Abaqus part",
133    )
134    parser.add_argument(
135        "--global-seed",
136        type=float,
137        default=default_global_seed,
138        help="The global mesh seed size. Positive float.",
139    )
140    return parser
141
142
143if __name__ == "__main__":
144    parser = get_parser()
145    # Abaqus does not strip the CAE options, so we have to skip the unknown options related to the CAE CLI.
146    try:
147        args, unknown = parser.parse_known_args()
148    except SystemExit as err:
149        sys.exit(err.code)
150    # Check for typos in expected arguments. Assumes all arguments use ``--option`` syntax, which is unused by Abaqus.
151    possible_typos = [argument for argument in unknown if argument.startswith("--")]
152    if len(possible_typos) > 0:
153        raise RuntimeError("Found possible typos in CLI option(s) {}".format(possible_typos))
154
155    sys.exit(
156        main(
157            input_file=args.input_file,
158            output_file=args.output_file,
159            model_name=args.model_name,
160            part_name=args.part_name,
161            global_seed=args.global_seed,
162        )
163    )

The rectangle_mesh.py file will have many similarities in code structure to the rectangle_geometry.py and rectangle_partition.py files. The first significant change is within the import statements at the top of the file. The rectangle_mesh.py file uses the export_mesh() function that is imported from the abaqus_utilities.py file you just created. abaqus_utilities.py exists in the modsim_package/abaqus directory, and is never copied to the build directory.

It is possible to use a normal looking import statement because we will modify PYTHONPATH in the project SConstruct configuration file. Abaqus Python and Python 3 environments will both inherit the PYTHONPATH and search for packages on the paths in this environment variable. While this path modification would be bad practice for a Python package, since we aren’t packaging and deploying our modsim Python modules this path modification is required. Care should still be taken to avoid naming conflicts between the modsim package directory and any Python packages that may exist in the active Conda environment.

Note

The rectangle_mesh.py script is also never copied to the build directory, so we can utilize the path of the rectangle_mesh.py file to point to the location of the abaqus_utilities file as well. The journal files are executed via absolute path from within the build directory, so the output from these scripts is placed in the build directory.

From this point, the main() function proceeds to copy the input file just like in rectangle_partition.py. The code that follows performs the following tasks within the new output_file:

  • Create a part instance that can be meshed. See the Abaqus Assembly Definition documentation [37] for more information about defining parts, part instances, and assemblies.

  • Seed the part using the --global-seed command-line argument value to define the global meshing size

  • Mesh the part

  • Assign an element type to the part. See the Abaqus Elements Guide [37] for more information about defining element types.

  • Define element and node sets for elements and nodes that may require output requests in the model. See the Abaqus Element Sets documentation [37] for more information about element sets.

  • Create an orphan mesh file by calling the export_mesh() function that was imported from abaqus_utilities.py

  • Save the output_file with the changes made

The rectangle_mesh.py script also contains an argument parser function. This command-line interface has yet another new argument --global-seed. This argument defines global mesh sizing for the model and has a default value that is assigned to the global_seed variable if not specified when calling the script.

All other aspects of the rectangle_mesh.py file are the same as rectangle_partition.py.

SConstruct#

  1. Add tutorial_02_partition_mesh to the workflow_configurations list in the waves-tutorials/SConscruct file.

A diff against the SConstruct file from Tutorial 01: Geometry is included below to help identify the changes made in this tutorial.

waves-tutorials/SConstruct

--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_01_geometry_SConstruct
+++ /home/runner/work/waves/waves/build/docs/tutorials_tutorial_02_partition_mesh_SConstruct
@@ -84,6 +84,9 @@
 for key, value in project_variables.items():
     env[key] = value
 
+# Make the project package importable for Python and Abaqus Python environments
+env.PrependENVPath("PYTHONPATH", project_dir)
+
 # Comments used in tutorial code snippets: marker-5
 
 # Add builders and pseudo-builders
@@ -94,6 +97,7 @@
 # Add simulation targets
 workflow_configurations = [
     "tutorial_01_geometry",
+    "tutorial_02_partition_mesh",
 ]
 for workflow in workflow_configurations:
     build_dir = env["variant_dir_base"] / workflow

Note the PYTHONPATH modification by SCons PrependENVPath. This modification to the project’s construction environment will allow Abaqus Python to import the project module files used by rectangle_mesh.py.

Build Targets#

  1. Build the new targets

$ pwd
/home/roppenheimer/waves-tutorials
$ scons tutorial_02_partition_mesh
scons: Reading SConscript files ...
Checking whether /apps/abaqus/Commands/abq2024 program exists.../apps/abaqus/Commands/abq2024
Checking whether abq2024 program exists.../apps/abaqus/Commands/abq2024
scons: done reading SConscript files.
scons: Building targets ...
cd /home/roppenheimer/waves-tutorials/build/tutorial_02_partition_mesh && /apps/abaqus/Commands/abq2024 cae -noGui
/home/roppenheimer/waves-tutorials/modsim_package/abaqus/rectangle_geometry.py -- > rectangle_geometry.cae.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/build/tutorial_02_partition_mesh && /apps/abaqus/Commands/abq2024 -information
/home/roppenheimer/waves-tutorials/modsim_package/abaqus/rectangle_partition.py -- > rectangle_partition.cae.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/build/tutorial_02_partition_mesh && /apps/abaqus/Commands/abq2024 cae -noGui
/home/roppenheimer/waves-tutorials/modsim_package/abaqus/rectangle_mesh.py -- > rectangle_mesh.cae.stdout 2>&1
scons: done building targets.

Output Files#

Explore the contents of the build directory using the tree command against the build directory, as shown below.

$ pwd
/home/roppenheimer/waves-tutorials
$ tree build/tutorial_01_geometry/ build/tutorial_02_partition_mesh/
build/tutorial_01_geometry/
|-- abaqus.rpy
|-- rectangle_geometry.cae
|-- rectangle_geometry.jnl
`-- rectangle_geometry.cae.stdout
build/tutorial_02_partition_mesh/
|-- abaqus.rpy
|-- abaqus.rpy.1
|-- abaqus.rpy.2
|-- rectangle_geometry.cae
|-- rectangle_geometry.jnl
|-- rectangle_geometry.cae.stdout
|-- rectangle_mesh.cae
|-- rectangle_mesh.inp
|-- rectangle_mesh.jnl
|-- rectangle_mesh.cae.stdout
|-- rectangle_partition.cae
|-- rectangle_partition.jnl
`-- rectangle_partition.cae.stdout

0 directories, 16 files

Examine the contents of the build/tutorial_01_geometry and a build/tutorial_02_partition_mesh directories. Recall from the earlier note that we require the geometry target introduced in Tutorial 01: Geometry to build the partition and mesh targets. There is an important distinction to be made here. This tutorial is NOT using the outputs from Tutorial 01: Geometry’s Building Targets section when we executed the $ scons tutorial_01_geometry command. This tutorial is using the outputs generated from executing the same code, but from our new tutorial_02_partition_mesh file. For this reason, we see the same outputs from the build/tutorial_01_geometry directory in the build/tutorial_02_partition_mesh directory (along with other Tutorial 02: Partition and Mesh output files).

The new output files pertain to the partitioning and meshing steps we added to the workflow. The file extensions are the same as when we ran the geometry workflow, but now we have an added rectangle_mesh.inp orphan mesh file.

Workflow Visualization#

View the workflow directed graph by running the following command and opening the image in your preferred image viewer.

$ pwd
/home/roppenheimer/waves-tutorials
$ waves visualize tutorial_02_partition_mesh --output-file tutorial_02_partition_mesh.png --width=28 --height=3 --exclude-list /usr/bin .stdout .jnl

The output should look similar to the figure below.

_images/tutorial_02_partition_mesh.png

As in Tutorial 01: Geometry, the visualization of the workflow directed graph looks relatively simple to manage. In part, this is because we’ve removed some of the automanaged output files with the --exclude-list option of the WAVES visualize subcommand. Probably you would not try to manage all of these files in a manual workflow; however, files like the *.stdout are important for debugging errors and lose significant value if they can’t be tied uniquely to the most recent execution.

There are two important features of the new workflow graph. First, notice that the rectangle_partition.cae and rectangle_mesh.cae files have two dependencies. As defined in the task definitions, they depend on their respective Abaqus journal file and the previous script’s output *.cae file. While we defined these tasks in the order they need to be executed, this was not strictly necessary and the tasks could be defined in any order within the SConscript file. The actual directed graph is assembled automatically by SCons from the target and source lists that define each task. For a simple workflow, this is relatively trivial. As the workflow grows, and especially after adding parameter studies, assembling the task execution order will become increasingly difficult and the benefits of the build system will become more clear.

Second, notice that all output *.cae and *.inp files are tied directly to the tutorial_02_partition_mesh alias. This is a result of appending all targets of the workflow to the workflow alias. This is useful if, for instance, there are some images produced by an intermediate step which aren’t strictly required by the final simulation results file. Such files may be useful for plotting geometric images used in documentation, but not necessary for the simulation or analysis results. The target alias can capture all relevant intermediate output which isn’t captured by the simulation results file target and simplify the scons command to execute this workflow.

As the tutorial workflow grows in size and complexity, or for workflows of mature modsim projects, it will become harder to view the shape of the directed graph with legible file names. Users are encouraged to build local copies and zoom in and out to explore the directed graph. The visualize options for --width, --height, and --font-size as well as vector graphic file formats (e.g. *.pdf output in place of *.png) can help make workflow graphs more useful beyond the general impressions of workflow shape and complexity.