Skip to main content
`Ctrl`+`K`

# Tutorial 02: Partition and Mesh

## References

- Adding to PYTHONPATH with SCons PrependENVPath method [32]

- Abaqus Node Sets [42]

- Abaqus Element Sets and Abaqus Elements Guide [42]

- Abaqus Assembly Definition [42]

## 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

   Linux/MacOS

       $ 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

   Windows

       PS > conda create --name waves-tutorial-env --channel conda-forge waves scons matplotlib pandas pyyaml xarray seaborn numpy salib pytest

2. Activate the environment

   Linux/MacOS

       $ conda activate waves-tutorial-env

   Windows

       PS > 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.

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

Linux/MacOS

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

Windows

    PS > New-Item $HOME\waves-tutorials -ItemType "Directory"
    PS > Set-Location $HOME\waves-tutorials
    PS > Get-Location

    Path
    ----
    C:\Users\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.

Linux/MacOS

    $ pwd
    /home/roppenheimer/waves-tutorials
    $ waves fetch --overwrite --tutorial 1 && mv tutorial_01_geometry_SConstruct SConstruct
    WAVES fetch
    Destination directory: '/home/roppenheimer/waves-tutorials'

Windows

    PS > Get-Location

    Path
    ----
    C:\Users\roppenheimer\waves-tutorials

    PS > waves fetch --overwrite --tutorial 1 && Move-Item tutorial_01_geometry_SConstruct SConstruct -Force
    WAVES fetch
    Destination directory: 'C:\Users\roppenheimer\waves-tutorials'

4. Fetch the `tutorial_01_geometry` file and create a new file named `tutorial_02_partition_mesh` using the WAVES Command-Line Utility fetch subcommand.

Linux/MacOS

    $ 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'

Windows

    PS > Get-Location

    Path
    ----
    C:\Users\roppenheimer\waves-tutorials

    PS > waves fetch --overwrite tutorials\tutorial_01_geometry && Copy-Item tutorial_01_geometry tutorial_02_partition_mesh
    WAVES fetch
    Destination directory: 'C:\Users\roppenheimer\waves-tutorials'

## SConscript

5. 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.

6. 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:

- main Functions

- Python Docstrings

- Abaqus Python Code

- Command-Line Interfaces

- Top-Level Code Environment

- Retrieving Exit Codes

7. 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 [42] 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.

8. 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 [42] 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.

9. 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 [42] 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 [42] 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 [42] 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

10. 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

11. Build the new targets

Linux/MacOS

    $ 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 '/usr/projects/ea/abaqus/Commands/abq2024' program exists...no
    Checking whether 'abq2024' program exists.../apps/abaqus/Commands/abq2024
    Checking whether 'abaqus' program exists...no
    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.

Windows

    PS > Get-Location

    Path
    ----
    C:\Users\roppenheimer\waves-tutorials

    PS > scons tutorial_02_partition_mesh
    scons: Reading SConscript files ...
    Checking whether '/apps/abaqus/Commands/abq2024' program exists...no
    Checking whether '/usr/projects/ea/abaqus/Commands/abq2024' program exists...no
    Checking whether 'abq2024' program exists...C:\SIMULIA\Commands\abq2024.BAT
    Checking whether 'abaqus' program exists...C:\SIMULIA\Commands\abaqus.BAT
    scons: done reading SConscript files.
    scons: Building targets ...
    cd C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh && C:\SIMULIA\Commands\abq2024.BAT cae -noGUI  C:\Users\roppenheimer\waves-tutorials\modsim_package\abaqus\rectangle_geometry.py -- > C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh\rectangle_geometry.cae.stdout 2>&1
    cd C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh && C:\SIMULIA\Commands\abq2024.BAT cae -noGUI  C:\Users\roppenheimer\waves-tutorials\modsim_package\abaqus\rectangle_partition.py -- > C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh\rectangle_partition.cae.stdout 2>&1
    cd C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh && C:\SIMULIA\Commands\abq2024.BAT cae -noGUI  C:\Users\roppenheimer\waves-tutorials\modsim_package\abaqus\rectangle_mesh.py -- > C:\Users\roppenheimer\waves-tutorials\build\tutorial_02_partition_mesh\rectangle_mesh.inp.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.

Linux/MacOS

    $ 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

Windows

    PS > Get-Location

       Path
       ----
       C:\Users\roppenheimer\waves-tutorials

    PS > tree build /F
    C:\USERS\ROPPENHEIMER\WAVES-TUTORIALS\BUILD
    ├───tutorial_01_geometry
    │       abaqus.rpy
    │       rectangle_geometry.cae
    │       rectangle_geometry.cae.stdout
    │       rectangle_geometry.jnl
    │
    └───tutorial_02_partition_mesh
            abaqus.rpy
            abaqus.rpy.1
            abaqus.rpy.2
            rectangle_geometry.cae
            rectangle_geometry.cae.stdout
            rectangle_geometry.jnl
            rectangle_mesh.cae
            rectangle_mesh.inp
            rectangle_mesh.inp.stdout
            rectangle_mesh.jnl
            rectangle_partition.cae
            rectangle_partition.cae.stdout
            rectangle_partition.jnl

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.

Linux/MacOS

    $ 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

Windows

    PS > Get-Location

    Path
    ----
    C:\Users\roppenheimer\waves-tutorials

    PS > waves visualize tutorial_02_partition_mesh --output-file tutorial_02_partition_mesh.png --width=28 --height=3 --exclude-list .stdout .jnl

The output should look similar to the figure below.

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.