Structural Analysis of a Lathe Cutter#

Summary: Basic walk through PyMAPDL capabilities.

Objective#

The objective of this example is to highlight some regularly used PyMAPDL features via a lathe cutter finite element model. Lathe cutters have multiple avenues of wear and failure, and the analyses supporting their design would most often be transient thermal-structural. However, for simplicity, this simulation example uses a non-uniform load.

Lathe cutter geometry and load description.

Figure 1: Lathe cutter geometry and load description.#

Contents#

  1. Variables and launch Define necessary variables and launch MAPDL.

  2. Geometry, mesh, and MAPDL parameters Import geometry and inspect for MAPDL parameters. Define linear elastic material model with Python variables. Mesh and apply symmetry boundary conditions.

  3. Coordinate system and load Create a local coordinate system for the applied load and verify with a plot.

  4. Pressure load Define the pressure load as a sine function of the length of the application area using numpy arrays. Import the pressure array into MAPDL as a table array. Verify the applied load and solve.

  5. Plotting Show result plotting, plotting with selection, and working with the plot legend.

  6. Postprocessing: List a result two ways: use PyMAPDL and the Pythonic version of APDL. Demonstrate extended methods and writing a list to a file.

  7. Advanced plotting Use of mesh.grid for additional postprocessing.

Step 1: Variables and launch#

Define variables and launch MAPDL.

import os

import numpy as np

from ansys.mapdl.core import launch_mapdl
from ansys.mapdl.core.examples.downloads import download_example_data

# cwd = current working directory
path = os.getcwd()
PI = np.pi
EXX = 1.0e7
NU = 0.27

Often used MAPDL command line options are exposed as Pythonic parameter names in ansys.mapdl.core.launch_mapdl(). For example, -dir has become run_location. You could use run_location to specify the MAPDL run location. For example:

..code:: python3

mapdl = launch_mapdl(run_location=path)

Otherwise, the MAPDL working directory is stored in mapdl.directory. In this directory, MAPDL will create some of the images we will show later.

Options without a Pythonic version can be accessed by the additional_switches parameter. Here -smp is used only to keep the number of solver files to a minimum.

mapdl = launch_mapdl(additional_switches="-smp")

Step 2: Geometry, mesh, and MAPDL parameters#

  • Import geometry and inspect for MAPDL parameters.

  • Define material and mesh, and then create boundary conditions.

# First, reset the MAPDL database.
mapdl.clear()

Import the geometry file and list any MAPDL parameters.

lathe_cutter_geo = download_example_data("LatheCutter.anf", "geometry")
mapdl.input(lathe_cutter_geo)
mapdl.finish()
print(mapdl.parameters)

Out:

MAPDL Parameters
----------------
PRESS_LENGTH                     : 0.055
UNIT_SYSTEM                      : "bin"

Use pressure area per length in the load definition.

pressure_length = mapdl.parameters["PRESS_LENGTH"]

print(mapdl.parameters)

Out:

MAPDL Parameters
----------------
PRESS_LENGTH                     : 0.055
UNIT_SYSTEM                      : "bin"

Change the units and title.

mapdl.units("Bin")
mapdl.title("Lathe Cutter")

Out:

TITLE=
 Lathe Cutter

Set material properties.

mapdl.prep7()
mapdl.mp("EX", 1, EXX)
mapdl.mp("NUXY", 1, NU)

Out:

MATERIAL          1     NUXY =  0.2700000

The MAPDL element type SOLID285 is used for demonstration purposes. Consider using an appropriate element type or mesh density for your actual application.

mapdl.et(1, 285)
mapdl.smrtsize(4)
mapdl.aesize(14, 0.0025)
mapdl.vmesh(1)

mapdl.da(11, "symm")
mapdl.da(16, "symm")
mapdl.da(9, "symm")
mapdl.da(10, "symm")

Out:

CONSTRAINT AT AREA    10
      LOAD LABEL = SYMM

Step 3: Coordinate system and load#

Create a local Coordinate System (CS) for the applied pressure as a function of local X.

Local CS ID is 11

mapdl.cskp(11, 0, 2, 1, 13)
mapdl.csys(1)
mapdl.view(1, -1, 1, 1)
mapdl.psymb("CS", 1)
mapdl.vplot(
    color_areas=True,
    show_lines=True,
    cpos=[-1, 1, 1],
    smooth_shading=True,
)
lathe cutter

VTK plots do not show MAPDL plot symbols. However, to use MAPDL plotting capabilities, you can set the keyword option vtk to False.

mapdl.lplot(vtk=False)
lathe cutter

Step 4: Pressure load#

Create a pressure load, load it into MAPDL as a table array, verify the load, and solve.

# pressure_length = 0.055 inch

pts = 10
pts_1 = pts - 1

length_x = np.arange(0, pts, 1)
length_x = length_x * pressure_length / pts_1

press = 10000 * (np.sin(PI * length_x / pressure_length))

length_x and press are vectors. To combine them into the correct form needed to define the MAPDL table array, you can use numpy.stack.

press = np.stack((length_x, press), axis=-1)
mapdl.load_table("MY_PRESS", press, "X", csysid=11)

mapdl.asel("S", "Area", "", 14)
mapdl.nsla("S", 1)
mapdl.sf("All", "Press", "%MY_PRESS%")
mapdl.allsel()

Out:

SELECT ALL ENTITIES OF TYPE= ALL  AND BELOW

You can open the MAPDL GUI to check the model.

mapdl.open_gui()

Set up the solution.

mapdl.finish()
mapdl.slashsolu()
mapdl.nlgeom("On")
mapdl.psf("PRES", "NORM", 3, 0, 1)
mapdl.view(1, -1, 1, 1)
mapdl.eplot(vtk=False)
lathe cutter

Solve the model.

mapdl.solve()
mapdl.finish()
if mapdl.solution.converged:
    print("The solution has converged.")

Out:

The solution has converged.

Step 5: Plotting#

mapdl.post1()
mapdl.set("last")
mapdl.allsel()

mapdl.post_processing.plot_nodal_principal_stress("1", smooth_shading=False)
lathe cutter

Plotting - Part of Model#

mapdl.csys(1)
mapdl.nsel("S", "LOC", "Z", -0.5, -0.141)
mapdl.esln()
mapdl.nsle()
mapdl.post_processing.plot_nodal_principal_stress(
    "1", edge_color="white", show_edges=True
)
lathe cutter

Plotting - Legend Options#

mapdl.allsel()
sbar_kwargs = {
    "color": "black",
    "title": "1st Principal Stress (psi)",
    "vertical": False,
    "n_labels": 6,
}
mapdl.post_processing.plot_nodal_principal_stress(
    "1",
    cpos="xy",
    background="white",
    edge_color="black",
    show_edges=True,
    scalar_bar_args=sbar_kwargs,
    n_colors=9,
)
lathe cutter

Let’s try out some scalar bar options from the PyVista documentation. For example, let’s set black text on a beige background.

The scalar bar keywords defined as a Python dictionary are an alternate method to using {key:value}’s. You can use the click-and drag method to reposition the scalar bar. Left-click it and hold down the left mouse button while moving the mouse.

sbar_kwargs = dict(
    title_font_size=20,
    label_font_size=16,
    shadow=True,
    n_labels=9,
    italic=True,
    bold=True,
    fmt="%.1f",
    font_family="arial",
    title="1st Principal Stress (psi)",
    color="black",
)

mapdl.post_processing.plot_nodal_principal_stress(
    "1",
    cpos="xy",
    edge_color="black",
    background="beige",
    show_edges=True,
    scalar_bar_args=sbar_kwargs,
    n_colors=256,
    cmap="jet",
)

# cmap names *_r usually reverses values.  Try cmap='jet_r'
lathe cutter

Step 6: Postprocessing#

Results List#

Get all principal nodal stresses.

mapdl.post_processing.nodal_principal_stress("1")

Out:

array([1553.23366186, 1302.78211371,  114.42758876, ..., 1708.49046222,
       1357.52966709, 1400.63152491])

Get the principal nodal stresses of the node subset.

mapdl.nsel("S", "S", 1, 6700, 7720)
mapdl.esln()
mapdl.nsle()

print("The node numbers are:")
print(mapdl.mesh.nnum)  # get node numbers

print("The principal nodal stresses are:")
mapdl.post_processing.nodal_principal_stress("1")

Out:

The node numbers are:
[  15   84   85   86  235  236  237  415  416  417  425  426  460 1104
 1163]
The principal nodal stresses are:

array([6455.64723834, 7591.47120491, 6734.98179097, 6829.4678661 ,
       5348.82458553, 5801.08899115, 6092.58822825, 7151.51926157,
       7022.27252074, 7361.85493455, 7092.45639776, 6572.00376486,
       5708.90915185, 6789.89247424, 5894.51402464])

Results as lists, arrays, and DataFrames#

Using mapdl.prnsol to check

print(mapdl.prnsol("S", "PRIN"))

Out:

PRINT S    NODAL SOLUTION PER NODE

 *** ANSYS - ENGINEERING ANALYSIS SYSTEM  RELEASE 2021 R2          21.2BETA ***
 Ansys Mechanical Enterprise
 00000000  VERSION=LINUX x64     10:27:27  JUN 01, 2022 CP=     87.716

 Lathe Cutter



 ** WARNING: PRE-RELEASE VERSION OF ANSYS 21.2BETA
  ANSYS,INC TESTING IS NOT COMPLETE - CHECK RESULTS CAREFULLY **

  ***** POST1 NODAL STRESS LISTING *****

  LOAD STEP=     1  SUBSTEP=     1
   TIME=    1.0000      LOAD CASE=   0

    NODE     S1           S2           S3           SINT         SEQV
      15   6455.6       135.15      -633.69       7089.3       6737.9
      84   7591.5       381.08       336.29       7255.2       7232.9
      85   6735.0       440.78       173.33       6561.7       6432.1
      86   6829.5       1084.3       460.22       6369.2       6081.3
     235   5348.8       529.17      -108.09       5456.9       5167.8
     236   5801.1       434.95      -21.273       5822.4       5608.2
     237   6092.6       543.80       57.951       6034.6       5807.0
     415   7151.5       636.57       329.05       6822.5       6674.0
     416   7022.3       328.91       131.78       6890.5       6794.1
     417   7361.9       380.66       114.99       7246.9       7117.8
     425   7092.5       308.05      -124.23       7216.7       7010.5
     426   6572.0       285.34      -4.5637       6576.6       6436.5
     460   5708.9       483.65      -115.92       5824.8       5549.4
    1104   6789.9       418.02       157.69       6632.2       6505.9
    1163   5894.5      -128.30      -567.37       6461.9       6253.9

 MINIMUM VALUES
 NODE        235         1163           15          235          235
 VALUE    5348.8      -128.30      -633.69       5456.9       5167.8

 MAXIMUM VALUES
 NODE         84           86           86           84           84
 VALUE    7591.5       1084.3       460.22       7255.2       7232.9

Use this command to obtain the data as a list.

mapdl_s_1_list = mapdl.prnsol("S", "PRIN").to_list()
print(mapdl_s_1_list)

Out:

[[15.0, 6455.0, 135.15, -633.69, 7089.0, 6737.0], [84.0, 7591.0, 381.08, 336.29, 7255.0, 7232.0], [85.0, 6735.0, 440.78, 173.33, 6561.0, 6432.0], [86.0, 6829.0, 1084.0, 460.22, 6369.0, 6081.0], [235.0, 5348.0, 529.17, -108.09, 5456.0, 5167.0], [236.0, 5801.0, 434.95, -21.273, 5822.0, 5608.0], [237.0, 6092.0, 543.8, 57.951, 6034.0, 5807.0], [415.0, 7151.0, 636.57, 329.05, 6822.0, 6674.0], [416.0, 7022.0, 328.91, 131.78, 6890.0, 6794.0], [417.0, 7361.0, 380.66, 114.99, 7246.0, 7117.0], [425.0, 7092.0, 308.05, -124.23, 7216.0, 7010.0], [426.0, 6572.0, 285.34, -4.5637, 6576.0, 6436.0], [460.0, 5708.0, 483.65, -115.92, 5824.0, 5549.0], [1104.0, 6789.0, 418.02, 157.69, 6632.0, 6505.0], [1163.0, 5894.0, -128.3, -567.37, 6461.0, 6253.0]]

Use this command to obtain the data as an array:

mapdl_s_1_array = mapdl.prnsol("S", "PRIN").to_array()
print(mapdl_s_1_array)

Out:

[[ 1.5000e+01  6.4550e+03  1.3515e+02 -6.3369e+02  7.0890e+03  6.7370e+03]
 [ 8.4000e+01  7.5910e+03  3.8108e+02  3.3629e+02  7.2550e+03  7.2320e+03]
 [ 8.5000e+01  6.7350e+03  4.4078e+02  1.7333e+02  6.5610e+03  6.4320e+03]
 [ 8.6000e+01  6.8290e+03  1.0840e+03  4.6022e+02  6.3690e+03  6.0810e+03]
 [ 2.3500e+02  5.3480e+03  5.2917e+02 -1.0809e+02  5.4560e+03  5.1670e+03]
 [ 2.3600e+02  5.8010e+03  4.3495e+02 -2.1273e+01  5.8220e+03  5.6080e+03]
 [ 2.3700e+02  6.0920e+03  5.4380e+02  5.7951e+01  6.0340e+03  5.8070e+03]
 [ 4.1500e+02  7.1510e+03  6.3657e+02  3.2905e+02  6.8220e+03  6.6740e+03]
 [ 4.1600e+02  7.0220e+03  3.2891e+02  1.3178e+02  6.8900e+03  6.7940e+03]
 [ 4.1700e+02  7.3610e+03  3.8066e+02  1.1499e+02  7.2460e+03  7.1170e+03]
 [ 4.2500e+02  7.0920e+03  3.0805e+02 -1.2423e+02  7.2160e+03  7.0100e+03]
 [ 4.2600e+02  6.5720e+03  2.8534e+02 -4.5637e+00  6.5760e+03  6.4360e+03]
 [ 4.6000e+02  5.7080e+03  4.8365e+02 -1.1592e+02  5.8240e+03  5.5490e+03]
 [ 1.1040e+03  6.7890e+03  4.1802e+02  1.5769e+02  6.6320e+03  6.5050e+03]
 [ 1.1630e+03  5.8940e+03 -1.2830e+02 -5.6737e+02  6.4610e+03  6.2530e+03]]

or as a DataFrame:

mapdl_s_1_df = mapdl.prnsol("S", "PRIN").to_dataframe()
mapdl_s_1_df.head()
NODE S1 S2 S3 SINT SEQV
0 15.0 6455.0 135.15 -633.69 7089.0 6737.0
1 84.0 7591.0 381.08 336.29 7255.0 7232.0
2 85.0 6735.0 440.78 173.33 6561.0 6432.0
3 86.0 6829.0 1084.00 460.22 6369.0 6081.0
4 235.0 5348.0 529.17 -108.09 5456.0 5167.0


Use this command to obtain the data as a DataFrame, which is a. Pandas data type. Because the Pandas module is imported, you can use its functions. For example, you can write principal stresses to a file.

# mapdl_s_1_df.to_csv(path + '\prin-stresses.csv')
# mapdl_s_1_df.to_json(path + '\prin-stresses.json')
mapdl_s_1_df.to_html(path + "\prin-stresses.html")

Step 7: Advanced plotting#

mapdl.allsel()
principal_1 = mapdl.post_processing.nodal_principal_stress("1")

Load this result into the VTK grid.

grid = mapdl.mesh.grid
grid["p1"] = principal_1

sbar_kwargs = {
    "color": "black",
    "title": "1st Principal Stress (psi)",
    "vertical": False,
    "n_labels": 6,
}

Generate a single horizontal slice along the XY plane.

Note

We’re using eye_dome_lighting here to enhance the plots of our slices. Read more about it at Eye Dome Lighting

single_slice = grid.slice(normal=[0, 0, 1], origin=[0, 0, 0])
single_slice.plot(
    scalars="p1",
    background="white",
    lighting=False,
    eye_dome_lighting=True,
    show_edges=False,
    cmap="jet",
    n_colors=9,
    scalar_bar_args=sbar_kwargs,
)
lathe cutter

Generate a plot with three slice planes.

slices = grid.slice_orthogonal()
slices.plot(
    scalars="p1",
    background="white",
    lighting=False,
    eye_dome_lighting=True,
    show_edges=False,
    cmap="jet",
    n_colors=9,
    scalar_bar_args=sbar_kwargs,
)
lathe cutter

Generate a grid with multiple slices in the same plane.

slices = grid.slice_along_axis(12, "x")
slices.plot(
    scalars="p1",
    background="white",
    show_edges=False,
    lighting=False,
    eye_dome_lighting=True,
    cmap="jet",
    n_colors=9,
    scalar_bar_args=sbar_kwargs,
)
lathe cutter

Finally, exit MAPDL.

mapdl.exit()

Total running time of the script: ( 0 minutes 8.285 seconds)

Gallery generated by Sphinx-Gallery