Note

Go to the bottom of this page to download the ZIP file for the PyAnsys cantilever example.

PyAnsys workflow: Cantilever#

This example shows how to run PyAnsys scripts on HPS. It simulates a cantilever using a chain of PyAnsys packages:

  1. Use PyAnsys Geometry to draw a cantilever design.

  2. Use PyPrimeMesh to apply a swept mesh to the cantilever design.

  3. Use PyMAPDL to calculate eigenfrequencies of the cantilever and then display them as output parameters.

The example parametrizes the physical cantilever dimensions, as well as several mesh and simulation properties.

Prerequisites#

Several packages need to be installed for the example. The paths and versions in the following setup instructions are for an Ansys 2025 R2 installation on a Linux system. Be sure to adjust them to your installation.

uv#

The example uses the uv package manager to run Python scripts. You can find more information, including installation instructions, in the example_python_uv example.

Ansys Geometry Service#

The PyAnsys Geometry package requires the Ansys Geometry Service. For installation instructions, see How is the Ansys Geometry Service installed? in the Frequently asked questions section of PyAnsys Geometry documentation. Once installed, the package must be registered in the scaler/evaluator as follows:

Property

Value

Name

Ansys Geometry Service

Version

2025 R2

Installation Path

/ansys_inc/v252/GeometryService

You must also ensure that these environment variables are defined:

Environment variable

Value

ANSRV_GEO_LICENSE_SERVER

<LICENSE@SERVER>

ANSYS_GEOMETRY_SERVICE_ROOT

/ansys_inc/v252/GeometryService

Ansys Prime Server#

The PyPrimeMesh package requires Ansys Prime Server, which is automatically installed with Ansys 2023 R1 or later. Ansys Prime Server must be registered in the scaler/evaluator as follows:

Property

Value

Name

Ansys Prime Server

Version

2025 R2

Installation Path

/ansys_inc/v252/meshing/Prime

You must also ensure that these environment variables are defined:

Environment variable

Value

AWP_ROOT252

/ansys_inc/v252

ANSYS_PRIME_ROOT

/ansys_inc/v252/meshing/Prime

Ansys Mechanical APDL#

Ansys Mechanical APDL is installed with the Ansys unified installer. The scaler/evaluator automatically detects this package, but you must ensure that these two environment variables are added to its registration for PyMAPDL:

Environment variable

Value

AWP_ROOT252

/ansys_inc/v252

PYMAPDL_MAPDL_EXEC

/ansys_inc/v252/ansys/bin/ansys252

HPS Python Client#

The example uses unmapped HPS parameters and therefore requires PyHPS 0.11 or later.

Run the example#

To run the example, execute the project_setup.py script:

uv run project_setup.py

This command sets up a project with a number of jobs. Each job samples a different cantilever design point.

Options#

The example supports the following command line arguments:

Flag

Example

Description

-U, --url

--url=https://localhost:8443/hps

URL of the target HPS instance

-u, --username

--username=repuser

Username to log into HPS

-p, --password

--password=topSecret

Password to log into HPS

-n, --num-jobs

--num-jobs=50

Number of design points to generate

-m, --num-modes

--num-modes=3

Number of lowest eigenfrequencies to calculate

-f, --target-frequency

--target-frequency=100.0

Frequency in Hertz to target for the lowest cantilever mode

-s, --split-tasks

--split-tasks

Split each step into a different task

Parameters#

The example defines the following HPS parameters:

Parameter

Description

canti_length

Length of the cantilever [um]

canti_width

Width of the cantilever [um]

canti_thickness

Thickness of the cantilever [um]

arm_cutoff_width

Amount to thin the cantilever arm by [um]

arm_cutoff_length

Length of the cantilever arm [um]

arm_slot_width

Width of the slot cut into the cantilever arm [um]

arm_slot

Whether there is a slot in the cantilever arm

young_modulus

Young Modulus of the cantilever material [Pa]

density

Density of the cantilever material [kg/m^3]

poisson_ratio

Poisson ratio of the cantilever material

mesh_swept_layers

Number of layers to generate when sweeping the mesh

num_modes

Number of eigenfrequencies to calculate

popup_plots

Whether to show popup plots (requires a framebuffer)

port_geometry

Port used by Ansys GeometryService

port_mesh

Port used by Ansys Prime Server

port_mapdl

Port used by the Ansys Mechanical APDL service

freq_mode_i

Frequency of i-th eigenmode [Hz], iϵ{1,…,num_modes}

clean_venv

Whether to delete the virtual environments after execution

Logic of the example#

The example comprises several files. Their roles are as follows:

The project_setup.py script handles all communication with the HPS instance. It sets up a project, uploads files, defines parameters, and applies settings.

The exec_script script is the execution script that starts the evaluation scripts. It first writes all HPS parameters to an input_parameters.json file, discovers the available software, and then runs the active evaluation script with uv. Finally, the execution script fetches parameters that may have been written to the output_parameters.json file by the evaluation script and sends them back to the evaluator. Optionally, the execution script cleans up the ephemeral virtual environments created by uv.

The eval_scripts folder contains the PyAnsys evaluation scripts. Each of the evaluation scripts first reads the parameters supplied by the execution script in the input_parameters.json file, then starts a PyAnsys service, and finally runs the PyAnsys program. The eval_combined.py script combines all stages in one monolithic task. The other three evaluation scripts split the three stages into three successive tasks. You can find more information on the content of these scripts in the User guide of PyAnsys Geometry, in the User guide of PyPrimeMesh, and in the User guide of PyMAPDL.

Code#

The files relevant for the single task version follow.

Here is the project_setup.py script for project creation:

project_setup.py#
# /// script
# requires-python = "==3.10"
# dependencies = [
#     "ansys-hps-client @ git+https://github.com/ansys/pyhps.git@main",
#     "packaging",
#     "typer",
# ]
# ///

import logging
import os
import random
import sys

import typer
from packaging import version

import ansys.hps.client
from ansys.hps.client import Client, HPSError
from ansys.hps.client.jms import (
    BoolParameterDefinition,
    File,
    FitnessDefinition,
    FloatParameterDefinition,
    IntParameterDefinition,
    JmsApi,
    Job,
    JobDefinition,
    Project,
    ProjectApi,
    ResourceRequirements,
    Software,
    SuccessCriteria,
    TaskDefinition,
)

log = logging.getLogger(__name__)


def main(client, num_jobs, num_modes, target_frequency, split_tasks):
    log.debug("=== Create Project")
    jms_api = JmsApi(client)
    tasks_config = "Split" if split_tasks else "Combined"

    proj = jms_api.create_project(
        Project(
            name=f"Cantilever - {num_jobs} Designs - {tasks_config}",
            priority=1,
            active=True,
        ),
        replace=True,
    )
    project_api = ProjectApi(client, proj.id)

    log.debug("=== Define Files")
    # Input files
    cwd = os.path.dirname(__file__)
    step_names = ["geometry", "mesh", "mapdl"]
    if not split_tasks:
        step_names = ["combined"]
    files = []
    for step_name in step_names:
        name = f"eval_{step_name}"
        files.append(
            File(
                name=f"{name}",
                evaluation_path=f"{name}.py",
                type="text/plain",
                src=os.path.join(cwd, "eval_scripts", f"{name}.py"),
            )
        )
    files.append(
        File(
            name="exec_script",
            evaluation_path="exec_script.py",
            type="text/plain",
            src=os.path.join(cwd, "exec_script.py"),
        )
    )
    # Output files
    files.extend(
        [
            File(
                name="cantilever_geometry",
                evaluation_path="cantilever.x_t",
                collect=True,
                type="text/plain",
            ),
            File(
                name="cantilever_mesh",
                evaluation_path="cantilever.cdb",
                collect=True,
                type="application/cdb",
            ),
        ]
    )
    files.append(
        File(name="canti_plot", evaluation_path="canti_plot.png", collect=True, type="image/png")
    )
    files = project_api.create_files(files)
    file_ids = {f.name: f.id for f in files}

    log.debug("== Define Parameters")
    # Input parameters
    params = [
        FloatParameterDefinition(
            name="canti_length",
            lower_limit=20.0,
            upper_limit=1000.0,
            default=350.0,
            units="um",
            mode="input",
        ),
        FloatParameterDefinition(
            name="canti_width",
            lower_limit=10.0,
            upper_limit=500.0,
            default=50,
            units="um",
            mode="input",
        ),
        FloatParameterDefinition(
            name="canti_thickness",
            lower_limit=0.05,
            upper_limit=5.0,
            default=0.5,
            units="um",
            mode="input",
        ),
        FloatParameterDefinition(
            name="arm_cutoff_width",
            lower_limit=0.0,
            upper_limit=250.0,
            default=0.0,
            units="um",
            mode="input",
        ),
        FloatParameterDefinition(
            name="arm_cutoff_length",
            lower_limit=20.0,
            upper_limit=1000.0,
            default=0.0,
            units="um",
            mode="input",
        ),
        FloatParameterDefinition(
            name="arm_slot_width",
            lower_limit=0.0,
            upper_limit=200.0,
            default=0.0,
            units="um",
            mode="input",
        ),
        BoolParameterDefinition(name="arm_slot", default=False, mode="input"),
        FloatParameterDefinition(
            name="young_modulus",
            lower_limit=1e6,
            upper_limit=1e13,
            default=3.0e11,
            units="Pa",
            mode="input",
        ),
        FloatParameterDefinition(
            name="density",
            lower_limit=1e3,
            upper_limit=6e3,
            default=3200,
            units="kg/m^3",
            mode="input",
        ),
        FloatParameterDefinition(
            name="poisson_ratio",
            lower_limit=0.1,
            upper_limit=0.6,
            default=0.23,
            units="",
            mode="input",
        ),
        IntParameterDefinition(
            name="mesh_swept_layers",
            lower_limit=2,
            upper_limit=100,
            default=10,
            units="",
            mode="input",
        ),
        IntParameterDefinition(
            name="num_modes",
            default=num_modes,
            lower_limit=num_modes,
            upper_limit=num_modes,
            units="",
            mode="input",
        ),
        BoolParameterDefinition(name="popup_plots", default=False, mode="input"),
        IntParameterDefinition(name="port_geometry", default=50052, mode="input"),
        IntParameterDefinition(name="port_mesh", default=50052, mode="input"),
        IntParameterDefinition(name="port_mapdl", default=50052, mode="input"),
        BoolParameterDefinition(name="clean_venv", default=False, mode="input"),
    ]
    # Output parameters
    for i in range(num_modes):
        params.append(
            FloatParameterDefinition(name=f"freq_mode_{i + 1}", units="Hz", mode="output")
        )
    params = project_api.create_parameter_definitions(params)

    log.debug("=== Define Tasks")
    if split_tasks:
        task_def_geometry = TaskDefinition(
            name="geometry",
            software_requirements=[Software(name="uv"), Software(name="Ansys GeometryService")],
            resource_requirements=ResourceRequirements(
                num_cores=1.0,
                memory=2 * 1024 * 1024 * 1024,  # 2 GB
                disk_space=500 * 1024 * 1024,  # 500 MB
            ),
            execution_level=0,
            max_execution_time=500.0,
            num_trials=3,
            use_execution_script=True,
            execution_script_id=file_ids["exec_script"],
            input_file_ids=[file_ids["eval_geometry"]],
            output_file_ids=[file_ids["cantilever_geometry"], file_ids["canti_plot"]],
            success_criteria=SuccessCriteria(
                return_code=0,
                require_all_output_files=True,
            ),
        )
        task_def_mesh = TaskDefinition(
            name="mesh",
            software_requirements=[Software(name="uv"), Software(name="Ansys Prime Server")],
            resource_requirements=ResourceRequirements(
                num_cores=1.0,
                memory=8 * 1024 * 1024 * 1024,  # 8 GB
                disk_space=500 * 1024 * 1024,  # 500 MB
            ),
            execution_level=1,
            max_execution_time=500.0,
            num_trials=3,
            use_execution_script=True,
            execution_script_id=file_ids["exec_script"],
            input_file_ids=[file_ids["eval_mesh"], file_ids["cantilever_geometry"]],
            output_file_ids=[file_ids["cantilever_mesh"]],
            success_criteria=SuccessCriteria(
                return_code=0,
                require_all_output_files=True,
                # hpc_resources=HpcResources(exclusive=True),
            ),
        )
        task_def_mapdl = TaskDefinition(
            name="mapdl",
            software_requirements=[
                Software(name="uv"),
                Software(name="Ansys Mechanical APDL", version="2025 R2"),
            ],
            resource_requirements=ResourceRequirements(
                num_cores=2.0,
                memory=8 * 1024 * 1024 * 1024,  # 8 GB
                disk_space=500 * 1024 * 1024,  # 500 MB
                # hpc_resources=HpcResources(exclusive=True),
            ),
            execution_level=2,
            max_execution_time=500.0,
            num_trials=3,
            use_execution_script=True,
            execution_script_id=file_ids["exec_script"],
            input_file_ids=[file_ids["eval_mapdl"], file_ids["cantilever_mesh"]],
            success_criteria=SuccessCriteria(
                return_code=0,
                require_all_output_parameters=True,
            ),
        )
        task_defs = project_api.create_task_definitions(
            [task_def_geometry, task_def_mesh, task_def_mapdl]
        )
    else:
        task_def_combined = TaskDefinition(
            name="combined",
            software_requirements=[
                Software(name="uv"),
                Software(name="Ansys GeometryService"),
                Software(name="Ansys Prime Server"),
                Software(name="Ansys Mechanical APDL", version="2025 R2"),
            ],
            resource_requirements=ResourceRequirements(
                num_cores=2.0,
                memory=8 * 1024 * 1024 * 1024,  # 8 GB
                disk_space=1024 * 1024 * 1024,  # 1 GB
                # hpc_resources=HpcResources(exclusive=True),
            ),
            execution_level=0,
            max_execution_time=1200.0,
            num_trials=3,
            use_execution_script=True,
            execution_script_id=file_ids["exec_script"],
            input_file_ids=[file_ids["eval_combined"]],
            output_file_ids=[
                file_ids["cantilever_mesh"],
                file_ids["cantilever_geometry"],
                file_ids["canti_plot"],
            ],
            success_criteria=SuccessCriteria(
                return_code=0,
                require_all_output_parameters=True,
                require_all_output_files=True,
            ),
        )
        task_defs = project_api.create_task_definitions([task_def_combined])

    log.debug("== Define Fitness")
    fd = FitnessDefinition(error_fitness=2.0)
    fd.add_fitness_term(
        name="frequency",
        type="target_constraint",
        weighting_factor=1.0,
        expression=f"map_target_constraint(values['freq_mode_1'], \
            {target_frequency}, {0.01 * target_frequency}, {0.5 * target_frequency})",
    )

    log.debug("== Define Job")
    job_def = JobDefinition(
        name="JobDefinition.1",
        active=True,
        parameter_definition_ids=[p.id for p in params],
        task_definition_ids=[td.id for td in task_defs],
        fitness_definition=fd,
    )
    job_def = project_api.create_job_definitions([job_def])[0]

    # Refresh parameters
    params = project_api.get_parameter_definitions(job_def.parameter_definition_ids)
    params_by_name = {p["name"]: p for p in params}

    log.debug(f"== Create {num_jobs} Jobs")
    jobs = []
    for i in range(num_jobs):
        values = generate_parameter_values_for_job(i, params_by_name)
        jobs.append(
            Job(name=f"Job.{i}", values=values, eval_status="pending", job_definition_id=job_def.id)
        )
    jobs = project_api.create_jobs(jobs)

    log.info(f"Created project '{proj.name}', ID='{proj.id}")
    return proj


def entrypoint(
    url: str = typer.Option("https://localhost:8443/hps", "-U", "--url"),
    username: str = typer.Option("repuser", "-u", "--username"),
    password: str = typer.Option("repuser", "-p", "--password"),
    num_jobs: int = typer.Option(20, "-n", "--num-jobs"),
    num_modes: int = typer.Option(3, "-m", "--num-modes"),
    freq_tgt: float = typer.Option(100.0, "-f", "--target-frequency"),
    split_tasks: bool = typer.Option(False, "-s", "--split-tasks"),
):
    log.debug("Arguments:")
    log.debug(
        f"{url}\n{username}, {password}\njobs: {num_jobs}\nmodes: {num_modes}\nfreq: {freq_tgt}"
    )

    logging.basicConfig(format="[%(asctime)s | %(levelname)s] %(message)s", level=logging.DEBUG)

    client = Client(url=url, username=username, password=password)

    try:
        main(client, num_jobs, num_modes, freq_tgt, split_tasks)
    except HPSError as e:
        log.error(str(e))


def sample_parameter(p, minval=None, maxval=None):
    if minval is None:
        minval = p.lower_limit
    if maxval is None:
        maxval = p.upper_limit
    return minval + random.random() * (maxval - minval)


def generate_parameter_values_for_job(i, params_by_name):
    values = {}
    values["canti_length"] = sample_parameter(params_by_name["canti_length"])
    values["canti_width"] = sample_parameter(params_by_name["canti_width"])
    values["canti_thickness"] = sample_parameter(params_by_name["canti_thickness"])
    values["arm_cutoff_width"] = sample_parameter(None, 0.0, values["canti_width"] / 2.5)
    values["arm_cutoff_length"] = sample_parameter(None, 10.0, values["canti_length"])
    if random.random() > 0.5:
        values["arm_slot"] = True
        values["arm_slot_width"] = sample_parameter(
            None,
            (values["canti_width"] - 2 * values["arm_cutoff_width"]) * 0.2,
            (values["canti_width"] - 2 * values["arm_cutoff_width"]) * 0.25,
        )
    values["port_geometry"] = random.randint(49153, 59999)
    values["port_mesh"] = random.randint(49153, 59999)
    values["port_mapdl"] = random.randint(49153, 59999)
    values["clean_venv"] = True
    return values


if __name__ == "__main__":
    # Verify that ansys-hps-client version is > 0.10.1 to ensure support for unmapped parameters
    if version.parse(ansys.hps.client.__version__) <= version.parse("0.10.1"):
        print(
            f"ERR: ansys-hps-client version {ansys.hps.client.__version__} \
                does not satisfy '>0.10.1'"
        )
        sys.exit(1)
    typer.run(entrypoint)

Here is the exec_script.py script:

exec_script.py#
"""Simplistic execution script for Python with uv.

Command formed: uv run <script_file> <input_file>
"""

import json
import os
import shutil

from ansys.rep.common.logging import log
from ansys.rep.evaluator.task_manager import ApplicationExecution


class PythonExecution(ApplicationExecution):
    def execute(self):
        log.info("Start uv execution script")

        # Identify files
        script_file = next(
            (f for f in self.context.input_files if f["name"].startswith("eval_")), None
        )
        assert script_file, "Python script file missing"
        input_filename = "input_parameters.json"
        output_filename = "output_parameters.json"

        # Use assigned resources
        self.context.parameter_values["num_cores"] = self.context.resource_requirements["num_cores"]
        self.context.parameter_values["memory_b"] = self.context.resource_requirements["memory"]

        # Pass parameters
        with open(input_filename, "w") as in_file:
            json.dump(self.context.parameter_values, in_file, indent=4)

        # Identify applications
        app_uv = next((a for a in self.context.software if a["name"] == "uv"), None)
        # app_xvfb = next((a for a in self.context.software if a["name"] == "Xvfb-run"), None)
        assert app_uv, "Cannot find app uv"

        # Add " around exe if needed for Windows
        exes = {"uv": app_uv["executable"]}
        for k, v in exes.items():
            if " " in v and not v.startswith('"'):
                exes[k] = f'"{v}"'  # noqa

        # Invoke xvfb if available
        # if app_xvfb != None:
        #    exes['uv'] = "pkill Xvfb; " + app_xvfb['executable'] + " " + exes['uv']

        # Use properties from resource requirements
        # None currently

        # Pass env vars correctly
        env = dict(os.environ)
        env.update(self.context.environment)

        ## Run evaluation script
        cmd = f"{exes['uv']} run {script_file['path']} {input_filename}"
        self.run_and_capture_output(cmd, shell=True, env=env)

        # Extract parameters if needed
        try:
            log.debug(f"Loading output parameters from {output_filename}")
            with open(output_filename) as out_file:
                output_parameters = json.load(out_file)
            self.context.parameter_values.update(output_parameters)
            log.debug(f"Loaded output parameters: {output_parameters}")
        except Exception as ex:
            log.info("No output parameters found.")
            log.debug(f"Failed to read output parameters from file: {ex}")

        # Clean up venv cache
        if "exe" in output_parameters.keys() and self.context.parameter_values.get("clean_venv"):
            try:
                venv_cache = os.path.abspath(os.path.join(output_parameters["exe"], "..", ".."))
                venv_cache_parent = os.path.join(venv_cache, "..")
                if os.path.exists(venv_cache):
                    log.debug(f"Current venv cache: {os.listdir(venv_cache_parent)}")
                    log.debug(f"Cleaning venv cache at {venv_cache}...")
                    shutil.rmtree(venv_cache)
                else:
                    log.debug(f"Venv cache path {venv_cache} does not exist.")
            except Exception as ex:
                log.debug(f"Couldn't clean venv cache at {venv_cache}: {ex}")

        log.info("End Python execution script")

Here is the eval_combined.py script for combined evaluation:

eval_combined.py#
# /// script
# requires-python = "==3.10"
# dependencies = [
#     "ansys-geometry-core[all]",
#     "ansys-meshing-prime[all]==0.7",
#     "ansys.mapdl.core",
#     "matplotlib"
# ]
# ///

import json
import os
import sys

import matplotlib.patches as patches
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

import ansys.meshing.prime as prime
from ansys.geometry.core import launch_modeler
from ansys.geometry.core.designer import DesignFileFormat
from ansys.geometry.core.math import Point2D
from ansys.geometry.core.sketch import Sketch
from ansys.mapdl.core import launch_mapdl
from ansys.meshing.prime.graphics import PrimePlotter


def geometry(params):
    # Read parameters, convert to meters
    um2m = 1e-6
    width = params["canti_width"] * um2m
    length = params["canti_length"] * um2m
    thickness = params["canti_thickness"] * um2m
    arm_cutoff_width = params["arm_cutoff_width"] * um2m
    arm_cutoff_length = params["arm_cutoff_length"] * um2m
    arm_slot = params["arm_slot"]
    arm_slot_width = params["arm_slot_width"] * um2m
    popup_plots = params["popup_plots"]
    port = params["port_geometry"]
    arm_width = width - 2 * arm_cutoff_width

    # Check input
    if (
        arm_cutoff_length > length
        or arm_width <= 0.0
        or (arm_slot and arm_slot_width > arm_width)
        or (arm_slot and arm_slot_width > width)
    ):
        print("SanityError: Cantilever dimensions are not sane.")

    # Draw Cantilever in 2D Sketch
    canti_sketch = Sketch()
    canti_sketch.segment(
        start=Point2D([0.0, -arm_width / 2.0]), end=Point2D([0.0, arm_width / 2.0])
    )
    canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, arm_width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([length, width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([length, -width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, -width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, -arm_width / 2.0]))
    canti_sketch.segment_to_point(end=Point2D([0.0, -arm_width / 2.0]))

    # Add arm slot if it exists
    if arm_slot:
        canti_sketch.box(
            Point2D([arm_cutoff_length / 2.0 + 2.5e-6, 0.0]),
            arm_cutoff_length - 5e-6,
            arm_slot_width,
        )

    # Create a modeler, extrude sketches, union bodies
    try:
        modeler = launch_modeler(port=port)
        print(modeler)

        design = modeler.create_design("cantilever")
        design.extrude_sketch("cantilever", canti_sketch, thickness)

        # Plot if requested
        if popup_plots:
            design.plot()

        # Draw matplotlib figure
        points = [
            [0.0, -arm_width / 2.0],
            [0.0, arm_width / 2.0],
            [arm_cutoff_length, arm_width / 2.0],
            [arm_cutoff_length, width / 2.0],
            [length, width / 2.0],
            [length, -width / 2.0],
            [arm_cutoff_length, -width / 2.0],
            [arm_cutoff_length, -arm_width / 2.0],
            [0.0, -arm_width / 2.0],
        ]
        xs, ys = zip(*points, strict=False)
        fig, ax = plt.subplots()
        ax.fill(xs, ys, color="green", alpha=1, edgecolor="black", linewidth=0.3)
        if arm_slot:
            slot_points = [
                [2.5e-6, arm_slot_width / 2.0],
                [arm_cutoff_length - 2.5e-6, arm_slot_width / 2.0],
                [arm_cutoff_length - 2.5e-6, -arm_slot_width / 2.0],
                [2.5e-6, -arm_slot_width / 2.0],
            ]
            slot_xs, slot_ys = zip(*slot_points, strict=False)
            ax.fill(slot_xs, slot_ys, color="white", alpha=1, edgecolor="black", linewidth=0.3)
        wall = patches.Rectangle(
            xy=(-length, -max(width, length)),
            width=length,
            height=2 * max(width, length),
            linewidth=0.3,
            edgecolor="black",
            facecolor="#B16100",
            hatch="///",
            alpha=1.0,
        )
        ax.add_patch(wall)
        ax.set_aspect("equal", adjustable="box")
        ax_length = 0.05 * length + 1.1 * max(length, width)
        ax.set_xlim(-0.05 * length, 1.1 * max(length, width))
        ax.set_ylim(-ax_length / 2.0, ax_length / 2.0)
        ax.set_title("Cantilever")
        formatter = FuncFormatter(lambda x, _: f"{x * 1e6:.1f}")
        ax.xaxis.set_major_formatter(formatter)
        ax.yaxis.set_major_formatter(formatter)
        ax.set_xlabel("x [um]")
        ax.set_ylabel("y [um]")
        plt.savefig("canti_plot.png", dpi=500)

        # export has different extensions on different OSs (.x_t, .xmt_txt)
        # design.export_to_parasolid_text(os.getcwd())
        design.download(
            os.path.join(os.getcwd(), design.name + ".x_t"), DesignFileFormat.PARASOLID_TEXT
        )
    except Exception as e:
        print(f"Exception in geometry: {e}")
    finally:
        modeler.exit()


def mesh(params, ansys_prime_root):
    # Read relevant dimensions from parameters
    um2mm = 1e-3
    swept_layers = params["mesh_swept_layers"]
    thickness = params["canti_thickness"] * um2mm
    length = min(params["canti_length"], params["arm_cutoff_length"]) * um2mm
    width = (params["canti_width"] - 2 * params["arm_cutoff_width"]) * um2mm
    if params["arm_slot"]:
        width -= params["arm_slot_width"] * um2mm
        width /= 2.0
    min_size = min(length, width) * 0.05
    max_size = min(length, width) * 0.3
    popup_plots = params["popup_plots"]
    port = params["port_mesh"]

    cad_file = "cantilever.x_t"
    if not os.path.isfile(cad_file):
        print(f"ERROR: Input file {cad_file} does not exist.")
        return 1

    with prime.launch_prime(prime_root=ansys_prime_root, port=port) as prime_client:
        model = prime_client.model
        mesh_util = prime.lucid.Mesh(model=model)

        # Import geometry
        mesh_util.read(file_name=cad_file, cad_reader_route=prime.CadReaderRoute.PROGRAMCONTROLLED)

        # Set mesh size
        sizing_params = prime.GlobalSizingParams(model=model, min=min_size, max=max_size)
        model.set_global_sizing_params(params=sizing_params)

        part = model.parts[0]
        sweeper = prime.VolumeSweeper(model)

        stacker_params = prime.MeshStackerParams(
            model=model,
            direction=[0, 0, 1],
            max_offset_size=thickness / swept_layers,
            delete_base=True,
        )

        # Create the base face
        createbase_results = sweeper.create_base_face(
            part_id=part.id,
            topo_volume_ids=part.get_topo_volumes(),
            params=stacker_params,
        )

        base_faces = createbase_results.base_face_ids

        part.add_labels_on_topo_entities(["base_faces"], base_faces)

        if popup_plots:
            scope = prime.ScopeDefinition(model=model, label_expression="base_faces")
            display = PrimePlotter()
            display.plot(model, scope=scope)
            display.show()

        base_scope = prime.lucid.SurfaceScope(
            entity_expression="base_faces",
            part_expression=part.name,
            scope_evaluation_type=prime.ScopeEvaluationType.LABELS,
        )

        # Create mesh on base face
        mesh_util.surface_mesh(
            min_size=min_size, max_size=max_size, scope=base_scope, generate_quads=True
        )

        if popup_plots:
            display = PrimePlotter()
            display.plot(model, scope=scope, update=True)
            display.show()

        # Sweep base mesh through body
        sweeper.stack_base_face(
            part_id=part.id,
            base_face_ids=base_faces,
            topo_volume_ids=part.get_topo_volumes(),
            params=stacker_params,
        )

        if popup_plots:
            display = PrimePlotter()
            display.plot(model, update=True)
            display.show()

        mesh_file = "cantilever.cdb"
        mesh_util.write(os.path.join(os.getcwd(), mesh_file))


def extract_frequencies(output_string, num_freqs):
    mode_freqs = {}
    lines = output_string.split("\n")
    for line in lines:
        split = line.split()
        if len(split) < 2 or len(split[0]) == 0:
            continue
        print(split)
        if split[0][0].isdigit():
            index = int(split[0])
            mode_freqs[f"freq_mode_{index}"] = float(split[1])
            if index == num_freqs:
                break
    return mode_freqs


def mapdl(params):
    # Read Parameters
    num_modes = params["num_modes"]
    young_modulus = params["young_modulus"]
    density = params["density"]
    poisson_ratio = params["poisson_ratio"]
    popup_plots = params["popup_plots"]
    port = params["port_mapdl"]
    num_cores = params["num_cores"]
    memory_mb = params["memory_b"] / 1024.0**2

    input_filename = "cantilever.cdb"
    if not os.path.isfile(input_filename):
        print(f"ERROR: Input file {input_filename} does not exist.")
        return 1

    try:
        # Launch MAPDL as a service
        mapdl = launch_mapdl(
            start_instance=True,
            mode="grpc",
            loglevel="DEBUG",
            run_location=f"{os.getcwd()}",
            port=port,
            nproc=num_cores,
            ram=memory_mb,
            running_on_hpc=False,
        )
        print(mapdl)
        mapdl.mute = True
        mapdl.clear()

        # Load input file
        mapdl.input("cantilever.cdb")
        mapdl.prep7()

        # Define material properties
        mapdl.mp("EX", 1, young_modulus)
        mapdl.mp("EY", 1, young_modulus)
        mapdl.mp("EZ", 1, young_modulus)
        mapdl.mp("PRXY", 1, poisson_ratio)
        mapdl.mp("PRYZ", 1, poisson_ratio)
        mapdl.mp("PRXZ", 1, poisson_ratio)
        mapdl.mp("DENS", 1, density)

        # Apply prestress
        mapdl.allsel()
        mapdl.inistate("DEFINE", val5=100.0e6, val6=100.0e6, val7=0.0)

        # Apply cantilever boundary conditions
        mapdl.allsel()
        mapdl.emodif("ALL", "MAT", i1=1)
        mapdl.nsel("S", "LOC", "X", 0)
        print(f"Fixing {len(mapdl.get_array('NODE', item1='NLIST'))} nodes")
        mapdl.d("ALL", "ALL")

        # Set keyopt properties
        mapdl.allsel()
        mapdl.etlist()
        element_type_id = int(mapdl.get("ETYPE", "ELEM", "1", "ATTR", "TYPE"))
        mapdl.keyopt(f"{element_type_id}", "2", "3", verbose=True)

        # Solve modal
        mapdl.slashsolu()
        mapdl.antype("MODAL")
        mapdl.modopt("LANB", num_modes)
        mapdl.mxpand(num_modes)
        output = mapdl.solve(verbose=False)

        # Extract calculated Eigenfrequencies
        mapdl.post1()
        output = mapdl.set("LIST", mute=False)
        mode_freqs = extract_frequencies(output, 20)
        if popup_plots:
            mode_num = 1
            mapdl.set(1, mode_num)
            mapdl.plnsol("u", "sum")

        mode_freqs["exe"] = sys.executable
        with open("output_parameters.json", "w") as out_file:
            json.dump(mode_freqs, out_file, indent=4)

    except Exception as e:
        print(f"Exception in mapdl: {e}")
    finally:
        mapdl.exit()


if __name__ == "__main__":
    print(f"python exe: {sys.executable}")
    # Fetch parameters
    input_file_name = sys.argv[1]
    input_file_path = os.path.abspath(input_file_name)
    with open(input_file_path) as input_file:
        params = json.load(input_file)

    ansys_prime_root = os.environ.get("ANSYS_PRIME_ROOT", None)

    # Run program step by step
    print("===Designing Geometry")
    geometry(params)
    print("===Drawing Mesh")
    mesh(params, ansys_prime_root)
    print("===Computing Eigenfrequencies")
    mapdl(params)

Download the ZIP file for the PyAnsys cantilever example and use a tool such as 7-Zip to extract the files.

Download ZIP file