Tutorial: Cubit+Abaqus#

This tutorial implements the same workflow introduced in Tutorial 04: Simulation, but uses Cubit [57] for the geometry, partition, and meshing tasks. It is included as an example for how to use Cubit with a build system and include best practices, such as an API and CLI.

Note

Depending on your Cubit installation and operating system, it may not be possible to import Cubit into arbitrary Python 3 environments. Instead, it may be necessary to execute Cubit Python scripts using the Python interpreter bundled with Cubit. An alternate Cubit tutorial using waves.scons_extensions.add_cubit_python() is provided as tutorial_cubit_alternate

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --destination tutorial_cubit_alternate tutorials/tutorial_cubit_alternate

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.

Directory Structure#

  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
  1. Create a new tutorial_cubit directory with the waves fetch command below

$ pwd
/home/roppenheimer/waves-tutorials
$ waves fetch --destination tutorial_cubit tutorials/tutorial_cubit
$ ls tutorial_cubit
modsim_package/  abaqus  cubit  SConstruct  sierra
  1. Make the new tutorial_cubit directory the current working directory

$ pwd
/home/roppenheimer/waves-tutorials
$ cd tutorial_cubit
$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ ls
modsim_package/  abaqus  cubit  SConstruct  sierra

SConscript#

Note that the tutorial_cubit directory has three SConscript files: cubit, abaqus, sierra. The first two are relevant to the current tutorial. The sierra workflow is described in the complementary Tutorial: Cubit+Sierra.

  1. Review the cubit and abaqus tutorials and compare them against the Tutorial 04: Simulation files.

The structure has changed enough that a diff view is not as useful. Instead the contents of the new SConscript files are duplicated below.

waves-tutorials/tutorial_cubit/cubit

  1#! /usr/bin/env python
  2"""Rectangle compression workflow: geometry, partition, mesh
  3
  4Requires the following ``SConscript(..., exports={})``
  5
  6* ``env`` - The SCons construction environment with the following required keys
  7
  8  * ``unconditional_build`` - Boolean flag to force building of conditionally ignored targets
  9  * ``cubit`` - String path for the Cubit executable
 10
 11* ``element_type`` - The Cubit 4 node quadrilateral element type
 12* ``solver`` - The target solver to use when writing a mesh file
 13"""
 14
 15import pathlib
 16
 17import waves
 18
 19# Inherit the parent construction environment
 20Import("env", "element_type", "solver")
 21
 22# Simulation variables
 23build_directory = pathlib.Path(Dir(".").abspath)
 24workflow_name = build_directory.name
 25
 26# Collect the target nodes to build a concise alias for all targets
 27workflow = []
 28
 29# Rectangle 2D
 30# Geometry
 31workflow.extend(
 32    env.PythonScript(
 33        target=["rectangle_geometry.cub"],
 34        source=["#/modsim_package/cubit/rectangle_geometry.py"],
 35        subcommand_options="",
 36    )
 37)
 38
 39# Partition
 40workflow.extend(
 41    env.PythonScript(
 42        target=["rectangle_partition.cub"],
 43        source=["#/modsim_package/cubit/rectangle_partition.py", "rectangle_geometry.cub"],
 44        subcommand_options="",
 45    )
 46)
 47
 48# Mesh
 49if solver.lower() == "abaqus":
 50    mesh_extension = "inp"
 51elif solver.lower() in ["sierra", "adagio"]:
 52    mesh_extension = "g"
 53else:
 54    raise RuntimeError(f"Unknown solver '{solver}'")
 55workflow.extend(
 56    env.PythonScript(
 57        target=[f"rectangle_mesh.{mesh_extension}", "rectangle_mesh.cub"],
 58        source=["#/modsim_package/cubit/rectangle_mesh.py", "rectangle_partition.cub"],
 59        subcommand_options="--element-type ${element_type} --solver ${solver}",
 60        element_type=element_type,
 61        solver=solver,
 62    )
 63)
 64
 65
 66# Cube 3D
 67# Geometry
 68workflow.extend(
 69    env.PythonScript(
 70        target=["cube_geometry.cub"],
 71        source=["#/modsim_package/cubit/cube_geometry.py"],
 72        subcommand_options="",
 73    )
 74)
 75
 76# Partition
 77workflow.extend(
 78    env.PythonScript(
 79        target=["cube_partition.cub"],
 80        source=["#/modsim_package/cubit/cube_partition.py", "cube_geometry.cub"],
 81        subcommand_options="",
 82    )
 83)
 84
 85# Mesh
 86if solver.lower() == "abaqus":
 87    mesh_extension = "inp"
 88elif solver.lower() in ["sierra", "adagio"]:
 89    mesh_extension = "g"
 90else:
 91    raise RuntimeError(f"Unknown solver '{solver}'")
 92workflow.extend(
 93    env.PythonScript(
 94        target=[f"cube_mesh.{mesh_extension}", "cube_mesh.cub"],
 95        source=["#/modsim_package/cubit/cube_mesh.py", "cube_partition.cub"],
 96        subcommand_options="--element-type ${element_type} --solver ${solver}",
 97        element_type=element_type,
 98        solver=solver,
 99    )
100)
101
102# Collector alias based on parent directory name
103env.Alias(f"{workflow_name}_cubit", workflow)
104
105if not env["unconditional_build"] and not env["CUBIT_PROGRAM"]:
106    print(f"Program 'cubit' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
107    Ignore([".", workflow_name], workflow)

waves-tutorials/tutorial_cubit/abaqus

  1#! /usr/bin/env python
  2"""Rectangle compression workflow: Abaqus solve
  3
  4Requires the following ``SConscript(..., exports={})``
  5
  6* ``env`` - The SCons construction environment with the following required keys
  7
  8  * ``unconditional_build`` - Boolean flag to force building of conditionally ignored targets
  9  * ``abaqus`` - String path for the Abaqus executable
 10  * ``cubit`` - String path for the Cubit executable
 11"""
 12
 13import pathlib
 14
 15# Inherit the parent construction environment
 16Import("env")
 17
 18# Simulation variables
 19build_directory = pathlib.Path(Dir(".").abspath)
 20workflow_name = build_directory.name
 21
 22# Collect the target nodes to build a concise alias for all targets
 23workflow = []
 24datacheck = []
 25
 26element_type = "QUAD"
 27solver = "abaqus"
 28SConscript("cubit", exports={"env": env, "element_type": element_type, "solver": solver}, duplicate=False)
 29
 30# Modify mesh target(s) element type.
 31# Linux style sed command options must be modified for macOS. Command must be modified if on Windows.
 32env.AddPostAction("rectangle_mesh.inp", "sed -i 's/CPE4/CPS4/g' ${TARGET.abspath}")
 33
 34# SolverPrep
 35copy_source_list = [
 36    "#/modsim_package/abaqus/rectangle_compression.inp",
 37    "#/modsim_package/abaqus/assembly.inp",
 38    "#/modsim_package/abaqus/boundary.inp",
 39    "#/modsim_package/abaqus/field_output.inp",
 40    "#/modsim_package/abaqus/materials.inp",
 41    "#/modsim_package/abaqus/parts.inp",
 42    "#/modsim_package/abaqus/history_output.inp",
 43]
 44workflow.extend(
 45    env.CopySubstfile(
 46        copy_source_list,
 47    )
 48)
 49
 50# Abaqus Solve
 51solve_source_list = [
 52    "rectangle_compression.inp",
 53    "assembly.inp",
 54    "boundary.inp",
 55    "field_output.inp",
 56    "materials.inp",
 57    "parts.inp",
 58    "history_output.inp",
 59    "rectangle_mesh.inp",
 60]
 61
 62datacheck.extend(
 63    env.AbaqusSolver(
 64        target=[
 65            "rectangle_compression_DATACHECK.odb",
 66            "rectangle_compression_DATACHECK.dat",
 67            "rectangle_compression_DATACHECK.msg",
 68            "rectangle_compression_DATACHECK.com",
 69            "rectangle_compression_DATACHECK.prt",
 70            "rectangle_compression_DATACHECK.023",
 71            "rectangle_compression_DATACHECK.mdl",
 72            "rectangle_compression_DATACHECK.sim",
 73            "rectangle_compression_DATACHECK.stt",
 74        ],
 75        source=solve_source_list,
 76        job="rectangle_compression_DATACHECK",
 77        program_options="-double both -datacheck",
 78    )
 79)
 80
 81workflow.extend(
 82    env.AbaqusSolver(
 83        target=[
 84            "rectangle_compression.odb",
 85            "rectangle_compression.dat",
 86            "rectangle_compression.msg",
 87            "rectangle_compression.com",
 88            "rectangle_compression.prt",
 89            "rectangle_compression.sta",
 90        ],
 91        source=solve_source_list,
 92        job="rectangle_compression",
 93        program_options="-double both",
 94    )
 95)
 96
 97# Collector alias based on parent directory name
 98env.Alias(workflow_name, workflow)
 99env.Alias(f"{workflow_name}_datacheck", datacheck)
100
101if not env["unconditional_build"] and not env["ABAQUS_PROGRAM"]:
102    print(f"Program 'abaqus' was not found in construction environment. Ignoring '{workflow_name}' target(s)")
103    Ignore([".", workflow_name], workflow)
104    Ignore([".", f"{workflow_name}_datacheck"], datacheck)

Note that Cubit does not support the Abaqus plane stress element CPS4, so we must add a post-action to the orphan mesh target to change the element type. A post-action is used to avoid generating intermediate target files, which would be required if we created a separate task for the file modification. This post-action is written to apply to a list, so if additional orphan mesh files needed the same modification, the post-action would be added to each targets’ build signature definition with a single AddPostAction definition.

The sed command is not available on all systems, but a Conda packaged version, conda-forge sed, of the GNU sed program can be used to provide system-to-system consistency with Conda environment management. See the Conda documentation for more information about virtual environment management with Conda.

Cubit Journal Files#

  1. Review the following journal files in the waves-tutorials/modsim_package/cubit directory.

The Cubit journal files include the same CLI introduced in Tutorial 02: Partition and Mesh for the Abaqus journal files. Besides the differences in Abaqus and Cubit commands, the major difference between the Abaqus and Cubit journal files is the opportunity to use Python 3 with Cubit, where Abaqus journal files must use the Abaqus controlled installation of Python 2. The API and CLI built from the Cubit journal files’ docstrings may be found in the WAVES-TUTORIAL API for cubit and the WAVES-TUTORIAL CLI for cubit, respectively.

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_geometry.py

 1import sys
 2import pathlib
 3import argparse
 4
 5import cubit
 6
 7
 8def main(output_file, width, height):
 9    """Create a simple rectangle geometry.
10
11    This script creates a simple Cubit model with a single rectangle part.
12
13    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` will be
14        used.
15    :param float width: The rectangle width
16    :param float height: The rectangle height
17
18    :returns: writes ``output_file``.cub
19    """
20    output_file = pathlib.Path(output_file).with_suffix(".cub")
21
22    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
23    cubit.cmd("new")
24    cubit.cmd("reset")
25
26    cubit.cmd(f"create vertex 0 0 0")
27    cubit.cmd(f"create vertex {width} 0 0")
28    cubit.cmd(f"create vertex {width} {height} 0")
29    cubit.cmd(f"create vertex 0 {height} 0")
30    cubit.cmd(f"create surface vertex 1,2,3,4")
31
32    cubit.cmd(f"save as '{output_file}' overwrite")
33
34
35def get_parser():
36    script_name = pathlib.Path(__file__)
37    # Set default parameter values
38    default_output_file = script_name.with_suffix(".cub").name
39    default_width = 1.0
40    default_height = 1.0
41
42    prog = f"python {script_name.name} "
43    cli_description = "Create a simple rectangle geometry and write an ``output_file``.cub Cubit model file."
44    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
45    parser.add_argument(
46        "--output-file",
47        type=str,
48        default=default_output_file,
49        # fmt: off
50        help="The output file for the Cubit model. "
51             "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub "
52             "(default: %(default)s",
53        # fmt: on
54    )
55    parser.add_argument(
56        "--width",
57        type=float,
58        default=default_width,
59        help="The rectangle width",
60    )
61    parser.add_argument(
62        "--height",
63        type=float,
64        default=default_height,
65        help="The rectangle height",
66    )
67    return parser
68
69
70if __name__ == "__main__":
71    parser = get_parser()
72    args = parser.parse_args()
73    sys.exit(
74        main(
75            output_file=args.output_file,
76            width=args.width,
77            height=args.height,
78        )
79    )

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_partition.py

  1import sys
  2import shutil
  3import pathlib
  4import argparse
  5
  6import cubit
  7
  8
  9def main(input_file, output_file, width, height):
 10    """Partition the simple rectangle geometry created by ``rectangle_geometry.py``
 11
 12    This script partitions a simple Cubit model with a single rectangle part.
 13
 14    **Feature labels:**
 15
 16    * ``bottom_left`` - bottom left vertex
 17    * ``bottom_right`` - bottom right vertex
 18    * ``top_right`` - top right vertex
 19    * ``top_left`` - top left vertex
 20    * ``left`` - left edge nodes
 21    * ``top`` - top edge nodes
 22    * ``right`` - right edge nodes
 23    * ``bottom`` - bottom edge nodes
 24    * ``elset_left`` - left edge elements
 25    * ``elset_top`` - top edge elements
 26    * ``elset_right`` - right edge elements
 27    * ``elset_bottom`` - bottom edge elements
 28
 29    :param str input_file: The Cubit model file created by ``rectangle_geometry.py``. Will be stripped of the extension
 30        and ``.cub`` will be used.
 31    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` will be
 32        used.
 33    :param float width: The rectangle width
 34    :param float height: The rectangle height
 35
 36    :returns: writes ``output_file``.cub
 37    """
 38    input_file = pathlib.Path(input_file).with_suffix(".cub")
 39    output_file = pathlib.Path(output_file).with_suffix(".cub")
 40
 41    # Avoid modifying the contents or timestamp on the input file.
 42    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 43    if input_file != output_file:
 44        shutil.copyfile(input_file, output_file)
 45
 46    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
 47    cubit.cmd("new")
 48    cubit.cmd("reset")
 49
 50    cubit.cmd(f"open '{output_file}'")
 51
 52    cubit.cmd("nodeset 1 add vertex 1")
 53    cubit.cmd("nodeset 1 name 'bottom_left'")
 54    cubit.cmd("nodeset 2 add vertex 2")
 55    cubit.cmd("nodeset 2 name 'bottom_right'")
 56    cubit.cmd("nodeset 3 add vertex 3")
 57    cubit.cmd("nodeset 3 name 'top_right'")
 58    cubit.cmd("nodeset 4 add vertex 4")
 59    cubit.cmd("nodeset 4 name 'top_left'")
 60
 61    cubit.cmd("nodeset 5 add curve 4")
 62    cubit.cmd("nodeset 5 name 'left'")
 63    cubit.cmd("nodeset 6 add curve 3")
 64    cubit.cmd("nodeset 6 name 'top'")
 65    cubit.cmd("nodeset 7 add curve 2")
 66    cubit.cmd("nodeset 7 name 'right'")
 67    cubit.cmd("nodeset 8 add curve 1")
 68    cubit.cmd("nodeset 8 name 'bottom'")
 69
 70    cubit.cmd("sideset 1 add curve 4")
 71    cubit.cmd("sideset 1 name 'elset_left'")
 72    cubit.cmd("sideset 2 add curve 3")
 73    cubit.cmd("sideset 2 name 'elset_top'")
 74    cubit.cmd("sideset 3 add curve 2")
 75    cubit.cmd("sideset 3 name 'elset_right'")
 76    cubit.cmd("sideset 4 add curve 1")
 77    cubit.cmd("sideset 4 name 'elset_bottom'")
 78
 79    cubit.cmd(f"save as '{output_file}' overwrite")
 80
 81
 82def get_parser():
 83    script_name = pathlib.Path(__file__)
 84    # Set default parameter values
 85    default_input_file = script_name.with_suffix(".cub").name.replace("_partition", "_geometry")
 86    default_output_file = script_name.with_suffix(".cub").name
 87    default_width = 1.0
 88    default_height = 1.0
 89
 90    prog = f"python {script_name.name} "
 91    cli_description = (
 92        "Partition the simple rectangle geometry created by ``rectangle_geometry.py`` "
 93        "and write an ``output_file``.cub Cubit model file."
 94    )
 95    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
 96    parser.add_argument(
 97        "--input-file",
 98        type=str,
 99        default=default_input_file,
100        # fmt: off
101        help="The Cubit model file created by ``rectangle_geometry.py``. "
102             "Will be stripped of the extension and ``.cub`` will be used, e.g. ``input_file``.cub "
103             "(default: %(default)s",
104        # fmt: on
105    )
106    parser.add_argument(
107        "--output-file",
108        type=str,
109        default=default_output_file,
110        # fmt: off
111        help="The output file for the Cubit model. "
112             "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub "
113             "(default: %(default)s",
114        # fmt: on
115    )
116    parser.add_argument(
117        "--width",
118        type=float,
119        default=default_width,
120        help="The rectangle width",
121    )
122    parser.add_argument(
123        "--height",
124        type=float,
125        default=default_height,
126        help="The rectangle height",
127    )
128    return parser
129
130
131if __name__ == "__main__":
132    parser = get_parser()
133    args = parser.parse_args()
134    sys.exit(
135        main(
136            input_file=args.input_file,
137            output_file=args.output_file,
138            width=args.width,
139            height=args.height,
140        )
141    )

waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_mesh.py

  1import sys
  2import shutil
  3import pathlib
  4import argparse
  5
  6import cubit
  7
  8
  9def main(input_file, output_file, global_seed, element_type="QUAD", solver="abaqus"):
 10    """Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py``
 11
 12    This script meshes a simple Cubit model with a single rectangle part.
 13
 14    **Feature labels:**
 15
 16    * ``NODES`` - all part nodes
 17    * ``ELEMENTS`` - all part elements
 18
 19    :param str input_file: The Cubit model file created by ``rectangle_partition.py``. Will be stripped of the extension
 20        and ``.cub`` will be used.
 21    :param str output_file: The output file for the Cubit model. Will be stripped of the extension and ``.cub`` and
 22        ``.inp`` will be used for the model and orphan mesh output files, respectively.
 23    :param float global_seed: The global mesh seed size
 24    :param str element_type: The model element type. Must be a supported Cubit 4 node element type.
 25    :param str solver: The solver type to use when exporting the mesh
 26
 27    :returns: writes ``output_file``.cub and ``output_file``.inp
 28
 29    :raises RuntimeError: If the solver is not supported
 30    """
 31    input_file = pathlib.Path(input_file).with_suffix(".cub")
 32    output_file = pathlib.Path(output_file).with_suffix(".cub")
 33    abaqus_mesh_file = output_file.with_suffix(".inp")
 34    sierra_mesh_file = output_file.with_suffix(".g")
 35
 36    # Avoid modifying the contents or timestamp on the input file.
 37    # Required to get conditional re-builds with a build system such as GNU Make, CMake, or SCons
 38    if input_file != output_file:
 39        shutil.copyfile(input_file, output_file)
 40
 41    cubit.init(["cubit", "-noecho", "-nojournal", "-nographics", "-batch"])
 42    cubit.cmd("new")
 43    cubit.cmd("reset")
 44
 45    cubit.cmd(f"open '{output_file}'")
 46
 47    cubit.cmd(f"surface 1 size {global_seed}")
 48    cubit.cmd("mesh surface 1")
 49    cubit.cmd("set duplicate block elements off")
 50
 51    cubit.cmd("nodeset 9 add surface 1")
 52    cubit.cmd("nodeset 9 name 'NODES'")
 53
 54    cubit.cmd("block 1 add surface 1")
 55    cubit.cmd(f"block 1 name 'ELEMENTS' Element type {element_type}")
 56
 57    cubit.cmd(f"save as '{output_file}' overwrite")
 58
 59    if solver.lower() == "abaqus":
 60        # Export Abaqus orphan mesh for Abaqus workflow
 61        cubit.cmd(f"export abaqus '{abaqus_mesh_file}' partial dimension 2 block 1 overwrite everything")
 62    elif solver.lower() in ["sierra", "adagio"]:
 63        # Export Genesis file for Sierra workflow
 64        cubit.cmd(f"export mesh '{sierra_mesh_file}' overwrite")
 65    else:
 66        raise RuntimeError(f"Uknown solver '{solver}'")
 67
 68
 69def get_parser():
 70    script_name = pathlib.Path(__file__)
 71    # Set default parameter values
 72    default_input_file = script_name.with_suffix(".cub").name.replace("_mesh", "_partition")
 73    default_output_file = script_name.with_suffix(".cub").name
 74    default_global_seed = 1.0
 75    default_element_type = "QUAD"
 76    default_solver = "abaqus"
 77
 78    prog = f"python {script_name.name} "
 79    cli_description = (
 80        "Mesh the simple rectangle geometry partitioned by ``rectangle_partition.py`` "
 81        "and write an ``output_file``.cub Cubit model file and ``output_file``.inp orphan mesh file."
 82    )
 83    parser = argparse.ArgumentParser(description=cli_description, prog=prog)
 84    parser.add_argument(
 85        "--input-file",
 86        type=str,
 87        default=default_input_file,
 88        # fmt: off
 89        help="The Cubit model file created by ``rectangle_partition.py``. "
 90             "Will be stripped of the extension and ``.cub`` will be used, e.g. ``input_file``.cub "
 91             "(default: %(default)s",
 92        # fmt: on
 93    )
 94    parser.add_argument(
 95        "--output-file",
 96        type=str,
 97        default=default_output_file,
 98        # fmt: off
 99        help="The output file for the Cubit model. "
100             "Will be stripped of the extension and ``.cub`` will be used, e.g. ``output_file``.cub",
101        # fmt: on
102    )
103    parser.add_argument(
104        "--global-seed",
105        type=float,
106        default=default_global_seed,
107        help="The global mesh seed size (default: %(default)s)",
108    )
109    parser.add_argument(
110        "--element-type",
111        type=str,
112        default=default_element_type,
113        help="The model element type. Must be a supported Cubit 4 node element type. " "(default: %(default)s)",
114    )
115    parser.add_argument(
116        "--solver",
117        type=str,
118        default=default_solver,
119        choices=["abaqus", "sierra", "adagio"],
120        help="The target solver for the mesh file. (default: %(default)s)",
121    )
122
123    return parser
124
125
126if __name__ == "__main__":
127    parser = get_parser()
128    args = parser.parse_args()
129    sys.exit(
130        main(
131            input_file=args.input_file,
132            output_file=args.output_file,
133            global_seed=args.global_seed,
134            element_type=args.element_type,
135            solver=args.solver,
136        )
137    )

SConstruct#

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

waves-tutorials/tutorial_cubit/SConstruct

--- /home/runner/work/waves/waves/build/docs/tutorials_tutorial_04_simulation_SConstruct
+++ /home/runner/work/waves/waves/build/docs/tutorial_cubit_SConstruct
@@ -1,7 +1,10 @@
 #! /usr/bin/env python
 
 import os
+import sys
+import typing
 import pathlib
+import subprocess
 
 import waves
 
@@ -48,6 +51,20 @@
     metavar="COMMAND",
     help=f"Override for the Abaqus command. Repeat to specify more than one (default: {default_abaqus_commands})",
 )
+default_cubit_commands = [
+    "/apps/Cubit-16.12/cubit",
+    "/usr/projects/ea/Cubit/Cubit-16.12/cubit",
+    "cubit",
+]
+AddOption(
+    "--cubit-command",
+    dest="cubit_command",
+    nargs=1,
+    type="string",
+    action="append",
+    metavar="COMMAND",
+    help=f"Override for the Cubit command. Repeat to specify more than one (default: {default_cubit_commands})",
+)
 
 # Comments used in tutorial code snippets: marker-2
 
@@ -58,6 +75,7 @@
     unconditional_build=GetOption("unconditional_build"),
     print_build_failures=GetOption("print_build_failures"),
     abaqus_commands=GetOption("abaqus_command"),
+    cubit_commands=GetOption("cubit_command"),
 )
 
 # Conditionally print failed task *.stdout files
@@ -69,6 +87,28 @@
 env["ABAQUS_PROGRAM"] = env.AddProgram(
     env["abaqus_commands"] if env["abaqus_commands"] is not None else default_abaqus_commands
 )
+env["CUBIT_PROGRAM"] = env.AddCubit(
+    env["cubit_commands"] if env["cubit_commands"] is not None else default_cubit_commands
+)
+env["FIERRO_IMPLICIT_PROGRAM"] = env.AddProgram(["fierro-parallel-implicit"])
+
+
+# Sierra requires a separate construction environment
+def return_modulepath(modules: typing.Iterable[str]) -> pathlib.Path:
+    """Return parent path of first found module or the current working directory"""
+    return next((pathlib.Path(path).parent for path in modules if pathlib.Path(path).exists()), pathlib.Path("."))
+
+
+sierra_modules = [
+    "/projects/aea_compute/modulefiles/sierra/5.21.6",
+]
+sierra_modulefile_parent = return_modulepath(sierra_modules)
+try:
+    envSierra = waves.scons_extensions.shell_environment(f"module use {sierra_modulefile_parent} && module load sierra")
+except subprocess.CalledProcessError:
+    print("Failed to initialize the Sierra environment", file=sys.stderr)
+    envSierra = Environment()
+envSierra["sierra"] = waves.scons_extensions.add_program(envSierra, ["sierra"])
 
 # Comments used in tutorial code snippets: marker-4
 
@@ -84,26 +124,33 @@
 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
 env.Append(BUILDERS={})
 
+envSierra.Append(
+    BUILDERS={
+        "Sierra": waves.scons_extensions.sierra_builder_factory(program=envSierra["sierra"]),
+    }
+)
+
 # Comments used in tutorial code snippets: marker-6
 
 # Add simulation targets
 workflow_configurations = [
-    "tutorial_01_geometry",
-    "tutorial_02_partition_mesh",
-    "tutorial_03_solverprep",
-    "tutorial_04_simulation",
+    "abaqus",
+    "sierra",
+    "fierro",
 ]
 for workflow in workflow_configurations:
     build_dir = env["variant_dir_base"] / workflow
-    SConscript(workflow, variant_dir=build_dir, exports={"env": env}, duplicate=False)
+    SConscript(
+        workflow,
+        variant_dir=build_dir,
+        exports={"env": env, "envSierra": envSierra},
+        duplicate=False,
+    )
 
 # Comments used in tutorial code snippets: marker-7
 

Note that the Cubit Python files don’t perform any imports from the current modsim project package, so the PYTHONPATH modification is no longer required. This tutorial is created in a new, stand-alone subdirectory, so the previous tutorial workflow configurations are no longer available. Only the sierra and abaqus workflow configurations will be found by SCons at execution time. Finally, note that the cubit SConscript file is not called by the SConstruct file. Instead, the cubit configuration is reused by the sierra and abaqus workflows, so the Cubit tasks only need to be defined once. To handle this task reuse some additional variable import and export statements are required by the cubit configuration file.

Build Targets#

  1. Build the new targets

$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ scons abaqus
scons: Reading SConscript files ...
Checking whether /apps/abaqus/Commands/abq2024 program exists.../apps/abaqus/Commands/abq2024
Checking whether abq2024 program exists...no
Checking whether /apps/Cubit-16.12/cubit program exists.../apps/Cubit-16.12/cubit
Checking whether cubit program exists...no
Checking whether sierra program exists...no
scons: done reading SConscript files.
scons: Building targets ...
Copy("build/abaqus/rectangle_compression.inp", "modsim_package/abaqus/rectangle_compression.inp")
Copy("build/abaqus/assembly.inp", "modsim_package/abaqus/assembly.inp")
Copy("build/abaqus/boundary.inp", "modsim_package/abaqus/boundary.inp")
Copy("build/abaqus/field_output.inp", "modsim_package/abaqus/field_output.inp")
Copy("build/abaqus/materials.inp", "modsim_package/abaqus/materials.inp")
Copy("build/abaqus/parts.inp", "modsim_package/abaqus/parts.inp")
Copy("build/abaqus/history_output.inp", "modsim_package/abaqus/history_output.inp")
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/abaqus && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_geometry.py > rectangle_geometry.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/abaqus && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_partition.py > rectangle_partition.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/abaqus && python /home/roppenheimer/waves-tutorials/tutorial_cubit/modsim_package/cubit/rectangle_mesh.py > rectangle_mesh.stdout 2>&1
cd /home/roppenheimer/waves-tutorials/tutorial_cubit/build/abaqus && /apps/abaqus/Commands/abq2024 -job rectangle_compression -input rectangle_compression -double both -interactive -ask_delete no > rectangle_compression.stdout 2>&1
scons: done building targets.

Output Files#

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

$ pwd
/home/roppenheimer/waves-tutorials/tutorial_cubit
$ tree build/abaqus
build/abaqus
|-- assembly.inp
|-- boundary.inp
|-- field_output.inp
|-- history_output.inp
|-- materials.inp
|-- parts.inp
|-- rectangle_compression.com
|-- rectangle_compression.dat
|-- rectangle_compression.inp
|-- rectangle_compression.msg
|-- rectangle_compression.odb
|-- rectangle_compression.prt
|-- rectangle_compression.sta
|-- rectangle_compression.stdout
|-- rectangle_geometry.cub
|-- rectangle_geometry.stdout
|-- rectangle_mesh.cub
|-- rectangle_mesh.inp
|-- rectangle_mesh.stdout
|-- rectangle_partition.cub
`-- rectangle_partition.stdout

0 directories, 21 files